In a previous post, I mentioned that the attack vector for the Capital One breach specifically targeted an EC2 feature.
In this post, I’ll give my educated guesses about how the attack actually worked.
[Note 1: if anyone happens to have any of the contents of the original gist then I’d love to get a look at it to confirm these guesses – until then I’m going to draw my conclusions from the text of the complaint]
[Note 2: after writing this I saw a Krebs post in which the author claims to have some insider information that backs up my guesses and confirms that the WAF was itself the attack vector – always nice when an educated guess turns out to be correct]
A bit of background: as I stated before, the first step in the attack was to obtain the credentials for an IAM role. I won’t go into a deep dive into IAM roles – the short version is that EC2 has the ability to associate a server (“instance” in AWS parlance) with a set of permissions. In order to actually use those permissions, a user or application needs to request a special URL via HTTP that can only be accessed from the instance itself – the HTTP response will include temporary AWS credentials that grant those permissions (see here for AWS’s documentation on the process).
Overall, this is a pretty good setup and far superior to embedding static credentials directly in your code base – I have personally seen two very high-impact incidents in which someone created an IAM user and accidentally committed the credentials to a public GH repo, which is exactly what this is meant to protect against. However, it does create an interesting threat vector that otherwise wouldn’t exist, which is an EC2-specific variant of a Server-Side Request Forgery (SSRF) attack.
Let me give a simplified example of the SSRF attack; let’s imagine you hobbled together a widget for your site that’s meant to slurp in external content and make it fit in with the theme of your site. An attacker, inspecting your site, spots a call to “/widgets/format-external-content.php?external-site=[some_https_string]”. Just for grins, the attacker tries manually pasting that content into a browser, but replaces [some_https_string] with the IAM metadata string from the AWS documentation. Your plugin, not knowing what to do with the JSON response, just spits it out more or less unchanged and just like that, your credentials have been captured.
In page 6 of the complaint, the agent writes that the gist contained the IP address of a server and three commands, the first of which returned the credentials for “an account known as *****-WAF-Role” – a pretty strong indicator that the attack retrieved EC2 IAM credentials. Since we don’t have the original exploit, we don’t know exactly *how* it worked, but my money’s on some variation of the SSRF method described above.
Once the attacker had the credentials, it was pretty much game over; the credentials could be used to get a list of buckets and objects and then pick and choose which of those the attacker wanted.
This particular flavor of attack has been on my mind for years now and I’m surprised that it’s taken so long to surface. I’d also like to point out the irony here:
- This attack was made possible by engineers following an established best practice (use an associated IAM role for AWS credentials). Had they been using a non-recommended method like a config file, the application could not have been compromised this way.
- The name of the role ends in “WAF-Role”; combined with the fact that the attacker referenced an IP address, this indicates that the compromised server was a Web Application Firewall (WAF), or possibly that the creators of the application just made a single role called “WAF-Role” and applied it to everything. Given that a WAF’s job is to filter out malicious requests, it’s especially ironic that it was the attack vector.
In part 3 of this article, I’ll describe what Capital One could have done to prevent this, and how to protect against it happening to you. Stay tuned.