一 、本文核心
如果以迷宫类比反序列化漏洞,__destruct()
与__toString()
就是入口;__call
、同名类、call_user_func_array
、call_user_func
为路径;call_user_func_array
、call_user_func
、eval
、$a($b,$c)
等命令执行与文件写入为出口。
本文完……
本文的目的是给出自己分析反序列化漏洞时的思路,明确目标,知道自己接下来应该去找什么样的方法与函数。
二、 反序列化漏洞
好文章很多,这里就快速过一下
1 成因
-
反序列化时将
String
还原成类,当类被销毁时默认会触发__destruct()
析构方法 -
当类被当作
String
使用时会执行__toString
方法
2 利用条件
-
有一个内容完全可控的反序列化点,例如:
unserialize(可控变量)
-
存在文件上传、文件名完全可控、使用了文件操作函数,例如:
file_exists('phar://恶意文件')
-
PHP8Phar中的元信息不再自动进行反序列化了
三、 以迷宫类比反序列化链
1 相关知识
-
PHP魔术方法
-
PHP继承
-
绕过
__wakeup
-
执行unserialize()时,先会调用这个函数
-
phar://反序列化
-
session反序列化
-
注意private和protected修饰时的
%00
2 迷宫入口
以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()时,先会调用这个函数
3 路径
(1) 前提:
类之间有包含关系
-
完全自建的代码中很难,但遵循composer规则的框架则比较容易做到
-
框架中会有自动加载类的代码,可以了解composer加载机制
(2) 看起来对的路
__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()
也是个好的跳板,一般在二次或更多次跳转中使用
(3) 控制方向与闯关
__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();";
}
4 出口
-
call_user_func()
,call_user_func_array()
-
这两个函数参数可控时可执行命令
-
形如$a($b,$c),$a、$b可控,可执行
system('id',$c)
-
eval($a)
-
其他可执行命令或写入文件的函数
四 、极简单例子分析
1 例子
<?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']);
2 分析
-
入口
-
__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);
}
}
3 构造
<?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 "nr";
下篇可能结合thinkphp6.09的反序列化漏洞来分析具体怎么操作,如果想写的话。
其实也没什么,也就是代码多点、链长点、有个命名空间。
该内容转载自网络,更多内容请点击“阅读原文”
原文始发于微信公众号(web安全工具库):以迷宫类比PHP反序列化链
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论