web
- 1:基本概念
- 1.1:序列化&反序列化
- 1.2:反序列化漏洞
- 1.3:POP 链
- 2:PHP 反序列化
- 2.1:序列化&反序列化
- 2.2:魔术方法
- 3:JAVA 反序列化
- 3.1:序列化&反序列化
- 3.2:反射机制
- 3.3:相关资源
本系列侧重方法论,各工具只是实现目标的载体。
命令与工具只做简单介绍,其使用另见《安全工具录》。
靶场参考:pikachu,WebGoat。
1:基本概念
1.1:序列化&反序列化
序列化(Serialization),指将内存数据(数据结构或对象)转化为一种便于存储或传输的格式(字节序列)的过程。
反序列化(Deserialization),是序列化的逆过程,把字节序列恢复为原先的内存数据,以便在程序中进行进一步处理和使用。
作用:
- 将内存中的数据转化为字节序列,以便于数据传递和恢复。
用途:
- 把内存数据以字节序列形式永久保存到硬盘上,通常存放在一个文件中(序列化对象)。
- 在网络上传送内存数据的字节序列(网络传输对象)。
不同语言,不同操作序列化生成的字节流格式一般不同。
1.2:反序列化漏洞
反序列化漏洞(Deserialization Vulnerability):用户可以控制或修改用来反序列化的数据,这可能使攻击者能够操纵序列化对象,将有害数据传递到应用程序中,从而产生安全问题。
漏洞成因:
在身份验证,文件读写,数据传输等功能处,未对反序列化接口做访问控制,未对序列化数据做加密和签名,加密密钥使用硬编码(如Shiro 1.2.4),使用不安全的反序列化框架库(如Fastjson 1.2.24)或函数的情况下,由于序列化数据可被用户控制,攻击者可以精心构造恶意的序列化数据(执行特定代码或命令的数据)传递给应用程序,在应用程序反序列化对象时执行攻击者构造的恶意代码,达到攻击者的目的。
反序列化漏洞一般都可执行任意命令或代码。
某些漏洞无法回显,所以一般情况下需要反弹 shell。
黑盒测试可以通过 http 头发现反序列化利用处。
参考文章:
常见的Web漏洞——反序列化漏洞
1.3:POP 链
POP(Property-Oriented Programing,面向属性编程),从现有运行环境中寻找一系列能够调用的代码或指令,然后根据需求将这些指令整合成有逻辑的、能实现需求的代码。称为 POP 链。
一般的 CTF 反序列化,存在漏洞的地方在魔术方法(或反射)中,可以通过自动调用魔术方法(或反射)来达到攻击效果。
但是当注入点存在普通的类方法中,通过自动调用的方法就失效了,此时需要找到普通类与魔术方法(或反射)之间的联系,理出一种逻辑思路,通过这种逻辑思路来构造一条 pop 链,从而达到攻击的目的。
参考文章:
php反序列化-POP链的构造利用
php反序列化之pop链构造
【Java安全】反序列化-CC3反序列化漏洞POP链分析
2:PHP 反序列化
2.1:序列化&反序列化
PHP 中的序列化与反序列化:
- serialize():序列化。
- unserialize():反序列化。
示例:
<?php
class Stu {
public $name = 'Bob';
public $age = 18;
public function demo() {
echo "Hello";
}
}
$stu = new Stu();
print_r($stu);
echo "\n\n----------\n\n";
// 进行序列化
$stus = serialize($stu);
print_r($stus);
echo "\n\n----------\n\n";
// 进行反序列化
$stus = unserialize($stus);
print_r($stus);
echo "\n\n----------\n\n";
?>
序列化结果说明:
2.2:魔术方法
魔术方法是 PHP 面向对象中特有的特性,在特定的情况下被触发,然后在魔术方法中的命令代码就会被执行。
示例:
<?php
class Stu {
public $name = 'Bob';
public $age = 18;
function __construct() {
echo "\n对象被创建了__construct()";
}
function __wakeup() {
echo "\n执行了反序列化__wakeup()\n";
}
function __toString() {
echo "\n对象被当做字符串输出__toString\n";
return 'asdsadsad';
}
function __sleep() {
echo "\n执行了序列化__sleep\n";
return array('name', 'age');
}
function __destruct() {
echo "对象被销毁了__destruct()\n";
}
}
$stu = new Stu();
echo "\n";
// 序列化
$stu_ser = serialize($stu);
echo "\n";
print_r($stu_ser);
echo "\n";
// 当成字符串输出
echo "$stu";
echo "\n";
// 反序列化
$stu_unser = unserialize($stu_ser);
print_r($stu_unser);
echo "\n";
?>
参考文章:
PHP反序列化漏洞详解
https://blog.csdn.net/LJH1999ZN/article/details/123338591
PHP反序列化与魔术方法:
https://www.cnblogs.com/20175211lyz/p/11403397.html
php反序列化完整总结:
https://xz.aliyun.com/t/12507#toc-15
3:JAVA 反序列化
3.1:序列化&反序列化
Java 中的序列化与反序列化:
- 序列化:ObjectOutputStream -> writeObject()
- 反序列化:ObjectInputStream -> readIObject()
序列化方法对参数指定的 obj 对象进行序列化,把字节序列写到目标输出流中,按 Java 的标准约定文件扩展名为 .ser。
反序列化方法从一个源输入流中读取字节序列,再把字节序列反序列化为一个对象,并将其返回。
示例:以下代码写在 Test.java 文件。
1、javac Test.java
2、java Test
import java.io.*;
public class Test {
public static void main(String[] args) {
try {
// 序列化
serialize();
// 反序列化
deserialize();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void serialize() throws IOException {
Student student = new Student();
student.setName("Bob");
student.setAge(18);
student.setScore(1000);
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream(new File("student.txt")));
objectOutputStream.writeObject(student);
objectOutputStream.close();
System.out.println("序列化成功!已经生成 student.txt 文件");
System.out.println("==============================================");
}
public static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream(new File("student.txt")));
Student student = (Student) objectInputStream.readObject();
objectInputStream.close();
System.out.println("\n反序列化结果为:");
System.out.println(student);
}
}
class Student implements Serializable {
private String name;
private Integer age;
private Integer score;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "Student:" + '\n' +
"name = " + this.name + '\n' +
"age = " + this.age + '\n' +
"score = " + this.score + '\n';
}
}
Java 序列化的结果不可读。
Java 序列化的标志特征参考:
- 数据以
aced
开头,那么这是一段 16 进制的 Java 序列化数据。 - 数据以
rO0
开头,基本可以确定这是一段 base64 编码的 Java 序列化数据。
3.2:反射机制
Java 中没有魔术方法,但是有反射(Reflection)机制。
Java 反射机制是一种动态获取信息以及动态调用对象方法的功能。即在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个类的成员变量和方法,可以了解任意一个对象所属的类,可以调用任意一个对象的属性和方法。
Java 反射机制允许在运行时获取和操作类、对象、方法、字段等信息,而不需要在编译时知道这些信息的确切类型。反射机制为开发者提供了一种动态获取和操作类的能力,使得编写更灵活、通用和可扩展的代码成为可能。
一般利用反射机制来构造一个执行命令的对象或直接调用一个具有命令/代码执行功能的方法,以此实现任意代码执行。
示例:
1、javac Person.java ReflectionExample.java
2、java ReflectionExample
Person.java 文件:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void introduce() {
System.out.println("My name is " + name + " and I am " + age + " years old.");
}
}
ReflectionExample.java 文件:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 使用反射创建 Person 类的实例
Class<?> personClass = Class.forName("Person");
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Object personObject = constructor.newInstance("John Doe", 30);
// 使用反射调用 Person 类的 introduce 方法
Method introduceMethod = personClass.getMethod("introduce");
introduceMethod.invoke(personObject);
}
}
3.3:相关资源
参考文章:
JAVA反序列化-反射机制
Java 反序列化工具 ysoserial:
https://github.com/frohoff/ysoserial
Java 反序列化工具 SerializationDumper:
https://github.com/NickstaDB/SerializationDumper/tree/1.12
非鬼亦非仙,一曲桃花水。
——《生查子 · 独游雨岩》(宋)辛弃疾