Spring Cloud Function SpEL注入漏洞深度剖析:从原理到防御实践

发布时间:2026/7/6 22:32:37

Spring Cloud Function SpEL注入漏洞深度剖析:从原理到防御实践 1. 项目概述一次对Spring Cloud Function SpEL注入的深度剖析最近在整理内部安全审计的案例库又翻到了CVE-2022-22963这个老熟人。这是一个发生在Spring Cloud Function组件中的SpEL表达式注入漏洞攻击者可以通过构造特定的HTTP请求头在未授权的情况下远程执行任意命令。虽然官方补丁发布已久但复盘其原理和复现过程对于理解现代Java框架的安全风险、掌握代码审计的思路依然极具价值。今天我就从一个安全研究者和开发者的双重角度带大家从头到尾拆解这个漏洞。我们不仅会复现攻击过程更会深入Spring Cloud Function的源码看看漏洞究竟是如何产生的以及修复方案是如何堵上这个安全缺口的。无论你是想入门安全研究还是作为开发者想避免写出有类似问题的代码这篇文章都能给你带来直接的收获。2. 漏洞背景与核心原理拆解2.1 Spring Cloud Function与“函数即服务”的上下文要理解这个漏洞首先得知道Spring Cloud Function是干什么的。它本质上是Spring生态中对于“函数即服务”FaaS理念的一个实现抽象。它允许开发者将业务逻辑编写成普通的java.util.Function、Consumer或Supplier接口的实现然后由框架负责将这些函数与各种消息传递或HTTP端点绑定起来。比如你可以写一个处理字符串的函数然后通过简单的配置让它既能监听HTTP POST请求也能处理来自RabbitMQ或Kafka的消息。这种设计带来了极大的灵活性和解耦。在漏洞涉及的版本Spring Cloud Function 3.1.6, 3.2.2 以及更早的旧版本中框架提供了一个关键特性动态路由。简单说就是客户端可以通过一个HTTP头默认是spring.cloud.function.routing-expression来指定本次请求应该由哪个具体的函数来处理。这个特性的本意是好的提供了运行时动态选择处理逻辑的能力。但问题就出在框架为了支持更灵活的路由逻辑允许这个头的值是一个SpEL表达式。2.2 祸根SpEL表达式注入SpEL全称Spring Expression Language是Spring框架内建的一种强大的表达式语言。它允许在运行时查询和操作对象图功能非常强大可以调用方法、访问属性、进行逻辑运算等。在Spring生态中SpEL被广泛用于注解如Value、XML配置和Spring Security的权限控制中。漏洞的核心就在于Spring Cloud Function在处理spring.cloud.function.routing-expression这个头时直接将其值作为SpEL表达式进行解析和执行而且没有做任何的安全过滤或沙箱限制。这意味着攻击者可以将任意恶意SpEL表达式通过这个HTTP头传递给服务器。由于SpEL表达式在解析时默认就具有执行任意Java代码的能力尤其是在某些上下文环境下这就等同于开放了一个远程代码执行RCE的后门。这里需要理解一个关键点为什么执行SpEL就能导致RCE这依赖于SpEL的一个特性——它可以访问Spring应用上下文中的Bean并调用其方法。Java中有一个Runtime类它的exec方法可以执行系统命令。虽然SpEL默认可能无法直接访问Runtime但通过一连串的反射调用可以最终达到执行命令的目的。攻击者常用的一个Payload原型是T(java.lang.Runtime).getRuntime().exec(\calc.exe\)。这里的T()操作符就是SpEL中用于访问类静态方法的语法。2.3 影响范围与严重性这个漏洞的CVSS评分高达9.8临界级别其严重性主要体现在以下几点利用门槛极低攻击者只需要能发送HTTP请求即可无需任何身份认证。危害极大直接导致服务器被远程控制可以执行任意系统命令窃取数据、植入后门、破坏服务器等。影响面广所有使用了受影响版本Spring Cloud Function并开启了HTTP适配器的应用都暴露在风险之下。由于Spring Cloud的普及度潜在受影响的应用数量非常可观。注意很多开发者在引入Spring Cloud Function时可能并不直接显式使用它而是通过引入Spring Cloud Stream等依赖间接引入。因此即使你没有主动配置Function只要依赖在类路径上且应用是一个Web应用就可能存在风险。3. 环境搭建与漏洞复现实操理论讲完了我们动手搭建一个漏洞环境真实地感受一下攻击过程。这里我选择使用Vulhub这个漏洞靶场集成环境它已经为我们准备好了所有配置可以快速聚焦于漏洞本身。3.1 靶场环境准备首先确保你的机器上安装了Docker和Docker Compose。然后从Vulhub官网下载或克隆项目找到Spring CVE-2022-22963的目录。# 进入漏洞环境目录 cd vulhub/spring/CVE-2022-22963 # 使用docker-compose一键启动漏洞环境 docker-compose up -d执行成功后使用docker ps命令查看容器是否正常启动通常会有一个Tomcat容器运行在8080端口。此时一个存在漏洞的Spring Boot应用就已经在本地运行起来了。3.2 构造并发送攻击请求漏洞的利用点在于spring.cloud.function.routing-expression这个HTTP头。我们需要构造一个恶意的SpEL表达式作为这个头的值。这里以在Linux靶机中执行touch /tmp/success命令为例创建一个文件作为攻击成功的标志。我们可以使用curl命令来发送攻击请求curl -X POST http://your-target-ip:8080/functionRouter \ -H \spring.cloud.function.routing-expression:T(java.lang.Runtime).getRuntime().exec(touch /tmp/success)\ \ --data-raw \任意数据\请求拆解说明-X POST: 指定使用POST方法。实际上对于这个漏洞GET请求通常也有效但POST更为通用。http://your-target-ip:8080/functionRouter: 这是漏洞应用默认的Function路由端点。functionRouter是Spring Cloud Function用于处理动态路由的默认路径。-H \...\: 这是设置HTTP头。我们将恶意的SpEL表达式放在了spring.cloud.function.routing-expression这个头里。--data-raw \任意数据\: POST请求需要请求体内容可以为空或任意不影响漏洞利用。3.3 验证攻击结果发送请求后如果返回的HTTP状态码是500内部服务器错误这很可能意味着命令执行成功了。为什么是错误因为我们的SpEL表达式执行了exec()方法但整个表达式并没有返回一个合法的函数名给框架进行路由所以框架处理流程会抛出异常但这并不妨碍命令已经执行。我们进入Docker容器内部验证# 进入正在运行的容器容器名可以通过docker ps查看 docker exec -it [容器ID] /bin/bash # 检查文件是否被创建 ls -la /tmp/success如果看到/tmp/success这个文件恭喜你漏洞复现成功这证明我们通过一个HTTP头就在远程服务器上执行了任意命令。实操心得在实际渗透测试中命令执行后可能需要回显结果。由于直接执行命令的返回输出无法通过HTTP响应直接看到攻击者通常会采用一些技巧比如将命令结果写入Web目录下的一个文件然后通过HTTP访问该文件或者使用curl、wget将结果发送到自己的服务器更高级的会直接反弹一个Shell。例如一个常见的反弹Shell的Payload会利用bash -i或nc但在构造时需要注意SpEL字符串中的引号转义和命令中的特殊字符处理这往往是利用成功的关键也是容易踩坑的地方。4. 源码深度剖析漏洞如何产生复现成功只是第一步作为开发者或安全研究员我们更关心漏洞在代码层面到底是怎么发生的。下面我们就深入Spring Cloud Function的源码以3.1.6版本为例一探究竟。4.1 请求入口与路由表达式提取Spring Cloud Function的Web适配器核心处理类是FunctionComponent和FunctionController。当请求到达/functionRouter端点时最终会由RoutingFunction来处理。关键代码位于org.springframework.cloud.function.context.config.RoutingFunction类中。在RoutingFunction的apply方法或相关调用链中框架会从请求的Message封装了HTTP请求的Headers中查找spring.cloud.function.routing-expression这个键。找到后直接获取其字符串值。这里没有任何过滤或白名单校验。// 伪代码示意流程 String expression (String) message.getHeaders().get(\spring.cloud.function.routing-expression\); if (expression ! null) { // 直接使用该表达式 Object functionName evaluateExpression(expression); // ... 根据functionName路由到具体函数 }4.2 SpEL表达式的解析与执行获取到表达式字符串后框架会使用Spring的StandardEvaluationContext和SpelExpressionParser来解析并执行它。// 伪代码关键在EvaluationContext的类型 ExpressionParser parser new SpelExpressionParser(); StandardEvaluationContext context new StandardEvaluationContext(); // 注意这里可能将包含请求信息的Message对象等设置为RootObject或变量 context.setRootObject(message); // ... 可能设置其他变量 Expression exp parser.parseExpression(expression); Object result exp.getValue(context); // 危险操作在此发生致命点在于使用了StandardEvaluationContext。这是SpEL中功能最全、但也最危险的上下文环境。它允许表达式拥有几乎完整的Java反射和执行能力。与之相对的是SimpleEvaluationContext它是为了安全而设计的受限上下文默认不支持Java类型引用T()操作符、Bean引用和构造函数调用等危险特性。4.3 从表达式到命令执行当攻击者传入T(java.lang.Runtime).getRuntime().exec(touch /tmp/success)时SpEL解析器会解析T(java.lang.Runtime)获取java.lang.Runtime类的引用。调用getRuntime()这是一个静态方法调用返回Runtime的单例对象。调用exec(String)实例方法调用执行系统命令。由于StandardEvaluationContext的强大能力这一切都畅通无阻。表达式执行的结果理论上应该是一个函数名被返回但框架此时可能已经因为流程异常而崩溃不过命令早已在exec()调用时被执行。4.4 官方修复方案解读Spring官方在后续版本中修复了此漏洞。修复的核心思路非常清晰彻底移除或禁用动态SpEL路由在某些修复版本中直接移除了通过HTTP头进行SpEL表达式路由的功能。使用安全的EvaluationContext如果保留相关功能则必须将StandardEvaluationContext替换为SimpleEvaluationContext并严格限制可访问的属性和方法。输入校验与白名单对来自客户端的路由表达式进行严格的校验只允许简单的字符串如函数名或使用一套严格的白名单机制来控制允许的操作。查看修复后的代码你会发现相关处理逻辑中要么不再从header中读取该表达式要么在解析前进行了严格的检查并使用了受限的上下文环境。这给我们一个重要的启示在任何用户输入被当作代码或表达式执行的地方必须使用最小权限原则和最安全的解析模式。5. 漏洞防御与安全开发实践分析完漏洞我们更应该思考如何避免在自己的项目中引入类似问题。5.1 立即修复升级与验证对于使用Spring Cloud Function的项目最直接有效的措施是立即升级将Spring Cloud Function升级到官方修复的安全版本3.1.7 或 3.2.3。依赖检查使用mvn dependency:tree或gradle dependencies命令检查所有传递依赖确保整个依赖树中不存在有漏洞的版本。有时你的直接依赖可能没问题但一个间接依赖引入了危险版本。安全扫描集成OWASP Dependency-Check、Snyk或GitHub Dependabot等工具到CI/CD流程中自动检查项目依赖的已知漏洞。5.2 安全编码准则避免表达式注入作为开发者在编码时应牢记以下原则永远不要信任用户输入任何来自客户端HTTP请求参数、头、Cookie、Body的数据都应视为不可信的。避免动态执行用户输入的代码尽量避免使用Runtime.exec()、ProcessBuilder、JavaScript引擎如ScriptEngine、OGNL、SpEL、MVEL等来执行用户可控的字符串。如果业务必须则需要建立严格的沙箱环境。使用安全的API如果必须使用SpEL在不需要完整功能的情况下优先使用SimpleEvaluationContext替代StandardEvaluationContext。这是防止SpEL注入最有效的一招。实施白名单机制如果业务需要动态性应设计一套白名单机制。例如只允许用户从预定义的几个函数名中选择而不是传入任意表达式。对输入进行严格的格式校验和内容过滤。5.3 架构设计层面的思考从更高层面看这个漏洞也提醒我们默认安全原则框架的默认配置应该是安全的。像动态路由这种高风险功能默认应关闭或处于最严格的模式下由开发者显式开启并了解风险。功能与安全的平衡为框架添加强大灵活的功能时必须同步评估其安全影响。文档中应明确警示危险配置的使用方法。纵深防御即使应用层存在漏洞也可以通过网络层的WAFWeb应用防火墙设置规则拦截包含可疑spring.cloud.function.routing-expression头或类似攻击特征的请求作为一道额外的防线。6. 拓展与关联漏洞思考CVE-2022-22963并非孤例它与安全史上一些著名的漏洞有相似之处与Log4Shell (CVE-2021-44228) 的对比两者都是通过用户可控的输入触发某种“表达式”的解析和执行。Log4Shell是JNDI查找触发的远程类加载和代码执行而CVE-2022-22963是直接的SpEL表达式执行。它们的共同点在于一个原本用于提供灵活性的功能Log4j的日志消息查找、Spring的动态路由因为对用户输入缺乏控制而变成了致命的漏洞。Spring框架历史上的其他SpEL漏洞Spring框架本身及其组件历史上也出现过多次SpEL注入问题例如在某些版本的Spring Data Commons、Spring Security OAuth中。审计Spring应用时需要重点关注所有使用StandardEvaluationContext和SpelExpressionParser解析外部数据的地方。表达式注入漏洞的通用挖掘思路这类漏洞的挖掘模式相对固定。首先在项目中全局搜索SpelExpressionParser、StandardEvaluationContext、Value动态解析时、parseExpression、getValue等关键词。然后逆向追踪这些表达式解析时所使用的字符串来源如果来源最终可追溯到用户输入HTTP请求、数据库、文件等且没有经过充分的安全处理那么就存在潜在的风险。通过对CVE-2022-22963的源码级分析和亲手复现我们不仅掌握了一个具体漏洞的利用方法更重要的是建立起了一种针对“表达式注入”类漏洞的分析模型和防御意识。在软件开发中强大的功能往往伴随着潜在的风险作为技术人员我们既要善于利用工具提升效率也必须时刻对安全保持敬畏将安全编码的原则内化为开发习惯。

相关新闻