从一段 JSON 字符串,到对象实例化、setter / getter 触发,再到 RCE 的完整内部调用链。行号均对应真实源码。
殊途同归到 parse 核心,差别只在最底部的“收尾回转”。
JSON.parseObject(text)← 单参,末尾会 toJSON 回转 ★JSON.parseObject(text, Clazz)JSON.parse(text)JSON.parse(text, features)new DefaultJSONParser(...) → parser.parse(),构建词法器并进入解析。
DefaultJSONParser.parseObject()大状态机循环:读一个 key → 判断是否特殊键(@type / $ref)→ 分发。
"@type" ?是 → 取出类名 lexer.scanSymbol() :317 送安检;否 → 普通字段,直接汇入下方【赋值】。
ParserConfig.checkAutoType()DefaultJSONParser:343 处调用autoTypeSupport = false:普通类走到 :1537 抛 autoType is not support —— 你的 Test 例子就死在这里。放行需开启 autoType 或命中白名单/绕过。config.getDeserializer(clazz)ParserConfig.createJavaBeanDeserializer() :935 决定用哪种反序列化器。
asmEnable ?asmFactory.create…运行时拼字节码生成专用类,临时类加载器加载 —— IDEA 断点断不到。
new JavaBeanDeserializer纯反射版。有 getOnly 字段(如 getMap())命中 :1011 强制退化到这里 —— 你的调试技巧。
JavaBeanInfo.build()反射扫描目标类,登记 setXxx / getXxx 到 FieldInfo[],供下一步逐字段匹配。
JavaBeanDeserializer.deserialze()调用点 deserializer.deserialze(...) DefaultJSONParser:405
createInstance()constructor.newInstance() :214 —— 无参构造被调用(gadget 类需可访问无参构造)。
FieldDeserializer.setValue()入口 DefaultFieldDeserializer.setValue() :126 —— 这里分出两条触发路径。
setXxx(value) 被反射调用。
setDataSourceName + setAutoCommit → JNDI lookup为把元素塞进集合,先调 getter 拿容器再 putAll。
getOutputProperties() → 加载字节码deserialze 返回 一个具体类实例(注意:不是 JSONObject)JSONObject ?因 @type 造出了具体 bean,类型对不上 → 走 否 分支,强制转回 JSONObject。
JSON.toJSON(obj)getFieldValuesMap() :1221 遍历每一个 getter → FieldSerializer.getPropertyValue → FieldInfo.get → method.invoke(obj)
parseObject(text) 下 getter 型 gadget 的主触发路径(你文档 §5/§6)checkAutoType唯一防线。默认 autoTypeSupport=false,普通类在 :1537 被拒。所有 RCE 本质都是“如何让墙放行一个能打的类”。
:936ASM 动态类(默认,断点断不到)vs 反射版(可调试)。加一个 getOnly 字段命中 :1011 强制走反射版 —— 只是调试技巧,不改漏洞逻辑。
A·填充字段时 FieldDeserializer:74-132;B·收尾回转 JSON:258→1221。B 是主路径,你 §5/§6 正是这条。
checkAutoTypenewInstance :214invoke(obj,value) :167parseObject(text)newInstance :214toJSON :258 → getFieldValuesMap :1221Feature.SupportNonPublicField 给私有字段赋值。