先知安全技术社区独家发文,如需转载,请先联系社区授权;未经授权请勿转载。
前言
最近学习java安全,在分析s2-001的时候发现了一些问题和心得。
一方面网上关于s2-001的漏洞分析很少,基本上都是poc+利用而已。
另一方面在调试过程中感觉apache官方通告有不准确的地方,这点见后面的部分。
有不准确的地方望各位师傅指出,谢谢。
漏洞信息
漏洞信息页面: https://cwiki.apache.org/confluence/display/WW/S2-001
漏洞成因官方概述:Remote code exploit on form validation error
漏洞影响:
WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8
环境搭建
源码结构:
几个主要文件(待会用到):
index.jsp
struts.xml:
web.xml:
完整源码见附件。
漏洞利用
最简单poc:
任意命令执行:
将中的替换为对应的命令,即可执行。
漏洞分析
在经过tomcat容器的处理后,http请求会到达struts2,从这里开始调试吧。
在 /com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:158 接受我们输入的参数值,之后会调用对应的set方法(这个省略):
继续执行,执行完interceptor部分,也即对进行step over,到达/com/opensymphony/xwork2/DefaultActionInvocation.java:252
跟进,到达 /com/opensymphony/xwork2/DefaultActionInvocation.java:343
跟进,到达 /org/apache/struts2/dispatcher/StrutsResultSupport.java:175:
此后在跟进,此处省略一些过程,相关调用栈如下:
进入 /org/apache/struts2/views/jsp/ComponentTagSupport.java:47,
这里会对jsp标签进行解析,但这时的标签并不包含我们的payload,我们可以在这里step over,直到解析到对应的标签:
如上图,我们提交的password值为,因此着重关注对解析。回到,执行完后会再次回到,此时遇到了相应的闭合标签,会跳转到:
跟进,到达 /org/apache/struts2/components/UIBean.java:486:
跟入后一直执行到如下图
由于开启了,expr变为为
跟入中的,来到 /org/apache/struts2/components/Component.java:318
开启了且为,跟入,在/com/opensymphony/xwork2/util/TextParseUtil.java:
继续跟入,源码如下:
此时为
经过while循环,确定start和end定位后,此时为,到达:
会返回password的值,这个就是我们传入的payload:
此后为,再对进行了一番处理后,payload经过变量,最终成为的值:
在完成后,进入下一个循环:
并且在中完成了对payload的执行
因此究其原因,在于在中,递归解析了表达式,在处理完后将的值直接取出并继续在循环中解析,若用户输入的password是恶意的ognl表达式,比如,则得以解析执行。
漏洞修复
XWork 2.0.4中,改变了ognl表达式的解析方法从而不会产生递归解析,用户的输入也不会再解析执行。
对应源码如下:
当解析完一层表达式后,如图,此时,从而执行break,不再继续解析:
diff的结果如下:
一个说明
回到漏洞信息和开头环境搭建部分,漏洞信息中的图有画上了几个红圈。基于此问一个问题:表单验证错误是在哪里触发的?
查阅apache2官方文档(https://struts.apache.org/core-developers/validation.html)提到%E6%8F%90%E5%88%B0)
就本次测试源码而言,并没有相应的或其他validation方法。为更直观起见,我们修改源码:
当submit时,会执行相应的set方法,直接设置username和password分别为与。修改后的源码中的方法中若为则返回,根据struts.xml配置文件,当返回时,会返回,即这里不算存在逻辑上的验证错误,因为username被硬编码为了,恒成立。
所以这里没有涉及到我们传入的参数,不存在表单验证失败,也不存在逻辑上的验证失败。执行poc如下:
所以可以说表单验证错误并不是该漏洞的产生的原因,但表单验证错误是这个漏洞出现的场景之一。在struts2框架中,配置了Validation,倘若验证出错会往往会原样返回用户输入的值而且不会跳转到新的页面,而在最后解析页面时区解析了用户输入的值,从而执行payload。在实际场景中,比如登陆等处,往往会配置了Validation,比如限制用户名长度,数字的范围等等,从而成为了该漏洞的高发区。
以struts2的showcase为例:
在本测试环境的源码中,没有表单验证,但同样把用户的输入留在了页面里,从而在解析的时候执行了。
领取专属 10元无门槛券
私享最新 技术干货