CVE-2016-4977 Spring Security OAuth代码执行学习过程
背景描述
Spring Security OAuth 2016年以前有出一个CVE,这里写一个记录学习的东西。
漏洞详细
Spring Security OAuth官网有CVE-2016-4977的介绍,但是讲的不怎么清楚,就只是说了一下漏洞原理,我们就只知道是因为执行一个SpEL表达式https://pivotal.io/de/security/cve-2016-4977而产生的。看看利用的poc,大概了解一下整个过程。
http://localhost:8080/oauth/authorize?responsetype=token&clientid=acme&redirect_uri=${1-65535}
复现截图
在这里表达式被成功执行了。
漏洞分析
对比一下补丁的前后,
可以看到在org.springframework.security.oauth2.provider.endpoint.SelView.java这里是问题之所在。
在程序里断点调试运行,然后发现位于代码String result = this.helper.replacePlaceholders(this.template, this.resolver)运行之后,${1-65535}就执行了
接着在此处来一个断点,里面是spring框架定义的模版
,然后的全部异常,网页上都是根据该模版来修改的,看这函数定位org.springframework.util.PropertyPlaceholderHelper中,parseStringValue函数为全部异常网页制造函数。
然后给整个函数进行调试,大概理出函数的流程:
protected String parseStringValue(String strVal, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(strVal);/////////////////// 新建string buffer ,先默认的 <html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>**/
int startIndex = strVal.indexOf(this.placeholderPrefix);/////////////得到result中${位置,然后后面全部流程就是把异常信息换掉${}中东西,接着把它封装为html流
while(startIndex != -1) {
int endIndex = this.findPlaceholderEndIndex(result, startIndex);////////得到}结束点
if(endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);////////提取${}之间字符串
String originalPlaceholder = placeholder;
if(!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException("Circular placeholder reference \'" + placeholder + "\' in property definitions");
}
placeholder = this.parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);///////////再解析一次通过递归调用把${}中的字符一次全部提取
String propVal = placeholderResolver.resolvePlaceholder(placeholder);/////////////把以上提到${}之间字符串当作参数传递resolvePlaceholder产生异常
if(propVal == null && this.valueSeparator != null) {
...
}
if(propVal != null) {
propVal = this.parseStringValue(propVal, placeholderResolver, visitedPlaceholders);/////////////////然后把生成的异常信息再调用parseStringValue函数一次
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
...
}
return result.toString();
}
SpEL表达式,若函数error="invalid_grant", error_description="Invalid redirect: ${1-65535} does not match one of the registered values: [http://google.com]"里1-65535取出来然后解析。接着1-65535就运行了。这一点和官方补丁描述相同。
总结流程:
第一次进入,系统用默认模版<html><body><h1>OAuth Error</h1><p>${error Summary}</p></body></html>作为函数parseStringValue参数。
函数再把参数里被${}包裹的字符串换成具体异常信息,然后再展现到web前端,具体异常信息就在函数resolvePlaceholder中。
先通过resolvePlaceholder处理,报错信息如下error="invalid_grant", error_description="Invalid redirect: ${1-65535} does not match one of the registered values: [http://google.com]"
系统接着调用parseStringValue函数,把error="invalid_grant", error_description="Invalid redirect: ${1-65535} does not match one of the registered values: [http://google.com]"当作参数交给parseStringValue函数。
函数然后解析${} 的东东1-65535,在传送到resolvePlaceholder函数,resolvePlaceholder就是 SpEL表达式,然后被解析,最终展示到到web前端。
补丁理解:
它把this.helper = new PropertyPlaceholderHelper("${", “}”);转换成 this.helper = new PropertyPlaceholderHelper( new RandomValueStringGenerator().generate() + “{”, “}”)也就是将$先转换为一个随机数,然后${1-65535}不能解析成SpEL,但RandomValueStringGenerator().generate()就是随机六位数的可以被爆破。比如我们爆出来198232然后http://x.x.x.x:80/oauth/authoriz ... redirect_uri=198232{1-65535}就执行命令了
|