闻道解惑


  • 首页

  • 标签

  • 站点地图

fastjson 反序列化漏洞POC分析

发表于 2017-12-11

来源:香依香偎@闻道解惑

fastjson 是阿里巴巴开源的,使用 Java 语言编写的 JSON 解析库,项目地址是 https://github.com/alibaba/fastjson,以速度快、性能高著称,使用范围非常广。

在2017年3月15日,Fastjson 官方主动爆出 Fastjson 在 1.2.24 及之前版本存在远程代码执行高危安全漏洞。本文对这个漏洞的POC进行分析。

零、Fastjson 反序列化的特点

先创建一个实体类 User,其中包括:

•    public  元素 name
•    private 元素 age   和它的 setter 函数
•    private 元素 prop  和它的 getter 函数
•    private 元素 grade 和它的 getter 函数

01-User.png

接下来使用 JSON.parseObject(),用指定类型的方式将这个类反序列化出来。

01-App.png

运行结果是:

01-result.png

从结果上看,我们可以得出以下结论:

•    User 对象的无参构造函数被调用
•    public String name 被成功的反序列化
•    private int age 被成功的反序列化,它的 setter 函数被调用
•    private Properties prop 被成功的反序列化,它的 getter 函数被调用
•    private String grade 没有被反序列化,仍然是默认值 null,getter 函数也没有被调用

前三点都自然,奇怪的是后两点。prop 和 grade 同样是 private 类型,同样提供了 getter 函数没有提供 setter 函数,为什么 prop 可以通过 getter 函数来反序列化,而 grade 却没有?

这涉及到 fastjson 的一个特殊处理。对于只有 getter 函数,没有 setter 函数的 private 元素,fastjson 会按如下条件判断反序列化的时候是否调用其 getter 函数。

•    函数名称大于等于 4
•    非静态函数
•    函数名称以get起始,且第四个字符为大写字母
•    函数没有入参
•    函数的返回类型满足如下之一
◦    继承自Collection
◦    继承自Map
◦    是AtomicBoolean
◦    是AtomicInteger
◦    是AtomicLong

01-JavaBeanInfo

回到前面的问题。prop 的 getter 函数 getProp() 满足上面的条件,它的返回类型 Properties 继承自 Map ,因此可以成功的被调用。而 grade 的 getter 函数 getGrade() 的返回类型 String 不满足返回类型的那个条件,因此没有被调用,grade 也无法被赋值。

那么,在反序列化的时候,向 grade 这样无法通过 setter 或 getter 函数进行赋值的 private 元素,有什么方法可以赋值呢?有。就是使用 FEATURE.SupportNonPublicField。

在 JSON.parseObject() 中使用 fastjson 的标签 FEATURE.SupportNonPublicField:

02-App

执行结果是:

02-result

grade 已经成功被反序列化,此时 grade 的 getter 函数 getGrade() 并没有被调用。

FEATURE.SupportNonPublicField 从 fastjson 的 1.2.22 版本开始引入。

还有个疑问:上面的 App.java 中,json string 中使用 @type 标签指定了反序列化的目标类型为 com.xiang.fastjson.poc.User,在调用 JSON.parseObject() 时也指定了目标类型是 User.class。那如果这两个类型不一致,会发生什么?

需要说明的是,只要 JSON.parseObject() 的第二个参数中指定的类,与 json string 中 @type 指定的类之间存在继承或转换关系,那么这个反序列化就会成功执行。比如把 JSON.parseObject() 的第二个参数设为 Object.class(也就是所有类的基类),那么反序列化就不会因为类型不匹配而失败。如果把第二个参数设为 String.class,那么所有支持 toString() 方法的类同样可以序列化成功。

但是如果两个类之间没有关联关系,那么反序列化的时候,是会直接返回错误拒绝反序列化,还是将对象反序列化完成再进行类型转换呢?

这个答案是:不确定。比如,我们修改 App.java,将 JSON.parseObject() 的第二个参数换成 Integer.class,json string 中仍然保持 com.xiang.fastjson.poc.User。

03-App

执行结果是:

03-result

从结果看,fastjson 先按照 json string 中的 @type 将对象反序列化出来,然后再转换为 JSON.parseObject() 中指定的目标类型。即便两个类型不一致,json string 指定的对象对应的 getter 和 setter 函数也一样会被调用。

再换一下,把 JSON.parseObject() 的第二个参数换成 ASMUtils.class。

04-App

执行结果是:

04-result

这一次的结果上,fastjson 却首先检查了两个类型是否匹配,不匹配直接抛出异常,没有调用各个字段的方法进行反序列化操作。

fastjson 内置了一些常用类的反序列化处理类,这些常用类的列表在 ParseConfig.java 中可以看到。

04-deserializemap

其中有一些处理类中(比如Integer、BigInteger等)没有检查类型是否匹配,就直接进行反序列化处理;有一些没有进行检查,但是反序列化过程会因为语法错误而失败。而对于大多数不属于常用类的情况,fastjson 是会进行检查不同类型之间的关联关系的(如上面的ASMUtils)。

好吧,这一部分太晕了。有没有更简单一些的用法,不用考虑这些匹配原则?有,就是 JSON.parse()。

05-App

执行结果是:

05-result

同样也支持 Feature.SupportNonPublicField 设置。

06-App

执行结果是:

06-result

可以看到,JSON.parse(),完全是按照 json string 中指定的类进行反序列化,不用考虑指定目标类的情况,因此可能是更广泛的用法吧。不过少了一次类型检查,也会引入更多的安全风险。

一、TemplatesImpl POC分析

首先是 TemplatesImpl 的 POC,来自于 廖神。

这个 POC 的入口点在 TemplatesImpl 类中 getOutputPerpeties() 函数。由于 TemplatesImpl 的 private 元素 _outputProperties 只有 getter 没有 setter ,同时其 getter 函数的返回类型 Properties 又继承自 Map,因此这个 getter 函数 getOutputProperties() 满足前面说的调用条件,可以被 fastjson 反序列化 TemplatesImpl 时调用到 。POC 的调用链是:

  • getOutputProperties()
    • -> newTransformer()
      • -> getTransletInstance()
        • -> defineTransletClasses()`

09-templatesimpl-01.png

09-templatesimpl-02.png

09-templatesimpl-03.png

09-templatesimpl-04.png

POC 代码如下:

07-exploit.png

07-poc.png

执行之后成功弹出计算器:

07-result.png

上面的POC中,Exploit 类继承自 AbstractTranslet。如果不让 Exploit 类继承也可以,只要在 json str 中,_outputProperties 之前增加 _transletIndex 和 _auxClasses 的设置就好了。

08-exploit.png

08-poc.png

执行之后同样弹出计算器:

08-result.png

TemplatesImpl 的 POC,在利用的时候有几个限制:

  1. 由于 POC 中关键元素 _bytecode 没有对应的 public 的 getter 和 setter 函数,因此需要服务器上解析 json 的时候,不管是使用 JSON.parse() 还是使用 JSON.parseObject(),都需要设置 Feature.SupportNonPublicField 。
  2. Feature.SupportNonPublicField 是在 1.2.22 版本引入,而这个漏洞在 1.2.25 版本就被封堵。这就要求目的服务器上的 fastjson 版本必须在 1.2.22 到 1.2.24 之间。
  3. 如果目的服务器使用的是 JSON.parseObject(),那么第二个参数必须是和 TemplatesImpl 不冲突的类,比如 Object.class,String.class,Integer.class 等。

二、JdbcRowSetImpl POC分析

JdbcRowSetImpl 的 POC 同样来自 廖神。

JdbcRowSetImpl 的调用入口在 setAutoCommit() ,调用链很短:

  • setAutoCommit()
    • -> connect()
      • -> InitialContext.lookup()

10-JdbcRowSetImpl-01.png

10-JdbcRowSetImpl-02.png

需要设置的属性只有 dataSourceName 和 autoCommit 两个。由于它们的 setter 函数 setDataSourceName() 和 setAutoCommit() 都是 public 类型,因此这里 不需要设置 SupportNonPublicField 属性 ,可以直接触发。

POC代码为

11-JdbcRowSetImpl-Poc.png

搭建好 RMI 的环境之后,执行 POC ,成功弹出计算器。

11-JdbcRowSetImpl-result.png

JdbcRowSetImpl 的 POC,使用限制为:
1 fastjson 的版本范围为 1.2.24 及以下的所有版本。
2 如果目的服务器使用的是 JSON.parseObject(),那么第二个参数必须是和 JdbcRowSetImpl 不冲突的类,比如 Object.class,String.class,Integer.class 等。

三、fastjson 的修复

反序列化漏洞的修补,通常都是通过白名单或黑名单的形式,禁止具有恶意功能的类进行反序列化。fastjson 使用的是黑名单的方式,具体而言就是从 1.2.25 版本开始,fastjson 增加了两个处理:

  1. autotype 功能默认关闭,同时提供手动 enable_autotype 的设置。
  2. 默认开启了黑名单,危险类所在的包不允许进行反序列化。

我们将 fastjson 升级到最新的 1.2.41 版本,再执行上面 JdbcRowSetImpl 的 POC,结果是直接抛了异常:

12-1.2.41-autoType-close.ong.png

查看抛异常的地方,

12-1.2.41-checkautotype.png

查看denyList的定义,可以找到目前定义的黑名单列表。

12-denyList.png

•    bsh
•    com.mchange
•    com.sun.
•    java.lang.Thread
•    java.net.Socket
•    java.rmi
•    javax.xml
•    org.apache.bcel
•    org.apache.commons.beanutils
•    org.apache.commons.collections.Transformer
•    org.apache.commons.collections.functors
•    org.apache.commons.collections4.comparators
•    org.apache.commons.fileupload
•    org.apache.myfaces.context.servlet
•    org.apache.tomcat
•    org.apache.wicket.util
•    org.apache.xalan
•    org.codehaus.groovy.runtime
•    org.hibernate
•    org.jboss
•    org.mozilla.javascript
•    org.python.core
•    org.springframework

com.sum.rowset.JdbcRowSetImpl 正好符合其中的 com.sun. 这个黑名单前缀,因此被屏蔽。

四、denyList 黑名单的绕过

fastjson 修补之后默认关闭了 autoType 功能,同时开启了黑名单。

但是这个黑名单功能的实现还是有问题的。在 autoType 功能打开的情况下(总有些偷懒的开发人员会这么干的),我们可以绕过这个黑名单的限制。

首先,我们通过命令行参数 -Dfastjson.parser.autoTypeSupport=true 的方式开启 autoType 功能,执行一下 JdbcRowSetImplPoc。

13-open-autotype.png

在 ParserConfig 的 880 行抛出异常。查看代码,这次是被黑名单 denyList 给拦截了。

13-denyList-block.png

继续往下看,在 926 行调用了 TypeUtils.loadClass() 来反序列化生成类。

14-TypeUtils-loadClass.png

点进去看看,在 TypeUtils 的 1143 行,对于类名由 L 和 ; 包装的情况下,这里会直接去掉类名前后的 L 和 ;,然后再 loadClass() !

14-TypeUtils-loadClass-L.png

这就意味着,我们只需要将 json string 中的类名前后增加 L 和 ;,也就是把 POC 中 @type 的值从 com.sun.rowset.JdbcRowSetImpl 改成 Lcom.sun.rowset.JdbcRowSetImpl; ,就能绕过黑名单的检查,同时也能完成 JdbcRowSetImpl 的反序列化!

15-Lcom.sun.rowset.JdbcRowSetImpl-.png

用命令行参数 -Dfastjson.parser.autoTypeSupport=true 开启 autoType 功能,再执行一下新的 JdbcRowSetImplPoc。

15-result.png

成功!

闭合优先的神奇标签

发表于 2017-07-16

来源:香依香偎@闻道解惑

noframes

先看一段 HTML:

01-noframes-html

不过是展示了一张图片而已。用浏览器打开看看。

02-noframes-alert

咦?怎么弹框了?看看浏览器处理的源码。

03-noframes-src

原来,img 标签 src 属性里的 \</noframes>,跳过了 \<img> 标签和 src 属性,把外层的 \<noframes> 给闭合了,导致 src 后半部分的 script 标签被解析成了 HTML,从而触发了弹框。

这可奇怪了。\</noframes> 明明在双引号里面,怎么就跨越了src 属性跨越了\<img> 标签,漂洋过海地把外层的 noframes 标签给闭合了?难道说,\<noframes> 标签有一种特殊的魔力。它的闭合优先级,高于双引号的完整性优先级,高于嵌套在内层的标签的闭合优先级。

还有其他标签也有这样的魔力么?我们 fuzz 一下看看。

fuzz 找找

从 w3school 找到所有的 HTML 标签:

04-w3school-tags

整理成 tags.txt。

05-tags-txt

按照前述 noframes 的格式,依次把他们输出到 show.html 中。

06-showtag-py

执行,打开 show.html 看看效果。

07-show-all-tag

现在我们知道,这些标签都是闭合优先级高于双引号完整性优先级的特殊标签。

  • \<!–
  • \<iframe>
  • \<noframes>
  • \<noscript>
  • \<script>
  • \<style>
  • \<textarea>
  • \<title>
  • \<xmp>

攻击场景

这个特性出现的原因,可能是源于浏览器对 DOM 树的特殊处理。而在某些 XSS 攻击的场景下,这一特性可能导致意想不到的结果。

特别是,像邮箱或论坛等支持富文本的应用,出于防御 XSS 的考虑,一定会对用户的输入进行语法分析和检查。但是,如果语法分析的结果和浏览器的识别结果不一致的话,就会形成 XSS 的攻击点。

比如文章开头的那段 noframes 语句,如果防 XSS 引擎将 src 里的内容统一理解为 img 的 src 属性,就可能形成 XSS 攻击的入口点。

这个特性还有哪些用处,就需要你的脑洞大开了:)

12
香依香偎

香依香偎

闻道解惑, 香依香偎

12 日志
44 标签
RSS
GitHub E-Mail Weibo
© 2021 香依香偎
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4