FastJson 拒绝服务攻击分析 (<=1.2.59)
最近在翻资料的时候发现了这样一个有意思的漏洞,简而言之,漏洞产生的原因是开发对输入数据考虑不周全,致使一个索引指针越界,导致拒绝服务的问题。比如我们输入16进制\x0a
,而开发未考虑到恶意攻击者如果只输入\x
,将会导致索引指针往后移动了两个指向了数据之外(越界)的地方.
漏洞复现
pom.xml1
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
8import com.alibaba.fastjson.JSON;
public class test {
public static void main(String[] args){
String DEATH_STRING = "{\"a\":\"\\x";//输入字符串长度为8
JSON.parse(DEATH_STRING);
}
}
漏洞分析
在这篇文章里介绍了fastjson解析json串的机制:fastjson源码解析:JSON Token解析,这里我们记住bp
为读取字符串的指针、sp
为字符缓存区索引就好了:
直接来看解析16进制的代码位置:/parser/JSONLexerBase.java#scanString
跟进next
函数:JSONScanner.java#next()
先给索引指针进行了自增赋值,接着判断索引和实际长度的比较,如果索引比实际长度长或者相等则返回EOI
,否则返回当前索引指向的字符:
经过第一次的next
处理,已经返回EOI(0x1A)
了:
但是他又调用了一次next
(即默认信任用户输入\x
后跟两位字符),此时索引的指针bp
为9了,已经越界了:
然后经过putChar
函数,break
了switch
分支,继续进行这个循环:
跟进isEOF
函数:JSONScanner.java#isEOF:bp+1
已经远大于len了,这个条件永远只能返回false
。
跟进putChar
函数,如果sp
和缓存字符长度相对后,则申请一个char
占用当前sbuf.length
的两倍:
所以最后的结果就是进入一个死循环且成倍申请内存:
最后
在新版本1.2.60中,修改了isEOF
函数的判断条件:
并且增加了x1
和x2
的校验:
其次,在实际的测试中并没有理想中的拒绝服务效果,使用多线程占用也就从100多M涨到2.5G的样子。1
2
3
4
5
6
7
8
9
10
11
12
13
14import 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);
}
}
后来学习到,java启动的时候可以通过-Xmx
参数为jvm设置最大内存占用,默认为主机的四分之一。
其次Java的OutOfMemoryError是JVM内部的异常,是一个可捕获异常,并不会直接导致java进程被Kill掉,顶多线程挂掉。
在Linux下当应用程序内存超出内存上限时,会触发Out Of Memory Killer机制以保持系统空间正常运行,java默认最大1/4物理内存占用,还不太容易导致系统的OOM。
总的来说,漏洞危害有限,但是利用过程还是挺看细节的,有一些值得学习的点~
参考文章: