flAWS.cloud Walkthrough — Level 5 — Instance ProfileMetadata Attack
The third instalment in a multi-part series I’m doing on the AWS CTF flaws.cloud by Scott Piper.
Part 1 is here.
Level 5
We can see the configuration of this proxy in etc/nginx/sites-available/default:
location ~* ^/proxy/((?U).+)/(.*)$ {
limit_except GET {
deny all;
}
limit_req zone=one burst=1;
set $proxyhost '$1';
set $proxyuri '$2';
proxy_limit_rate 4096;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $proxyhost;
resolver 8.8.8.8;
proxy_pass http://$proxyhost/$proxyuri;
}
Possibly one of the most high-profile hacks against infra running in AWS was the Capital One hack. It exploited a misconfigured web proxy running on an EC2 instance, using it to retrieve creds for the EC2 Instance Profile.
Each EC2 instance has metadata available on a magic private (only itself can access) IP address “169.254.169.254”. The juiciest part of this metadata, are the credentials for the Instance Profile (if one is set), which can be retrieved through a web request to http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance/
Perhaps we can trick this proxy into requesting its own Instance Profile security creds, and relaying the results to us. Let’s try by visiting http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance:
That’s what we want.
Let’s put those creds in our ~/.aws/credentials and try them out:
However at this point I must admit I ran into a roadblock — seems either I’m missing something, or that user has lost perms on the relevant bucket, because when I try “aws — profile flaws5 s3 ls level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud”:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
:( I’ve reached out to Scott on Twitter, hopefully he can help.
Luckily, the final hint has the URL to Level 6: http://level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud/ddcc78ff/
Preventing this Attack with AWS Instance Metadata Service v2
EC2 instance metadata v2 (aka “IMDSv2”) prevents such attacks by requiring a token in the request header “X-aws-ec2-metadata-token”.
When I launched my ec2 instance to mount that flaws snapshot, in the Instance Metadata settings I selected “Only v2”. This is a great idea, and something you should always do if possible. Because I did so, calls to my instance metadata will fail if I don’t supply a valid token header:
To do so, IMDSv2 requires that we first make an HTTP PUT request (something most HTTP proxies won’t forward) to retrieve a token, then use that token to query the metadata, like so:
This is documented on AWS’ page on retrieving instance metadata.
Other notable security improvements of IMDSv2:
- The token PUT request must specify an expiry
- The response to the PUT has IP TTL=1 (default- adjustable), preventing anyone except the local machine retrieving a token
- Requests with an X-Forwarded-For header (having passed through a proxy) are dropped
Bonus Digression: Run a vulnerable web proxy on an EC2 instance with only IMDSv2, and prove your Instance Profile creds are safe
Out of interest, I tried running that same nginx proxy on my instance (this was Amazon Linux). Install nginx with the command amazon-linux-extras install nginx1.12
Then copy and paste the above nginx config into /etc/nginx.conf, under http->server block.
Initially, systemctl restart nginx gave me the error
nginx: [emerg] zero size shared memory zone “one”
This was due to the line “limit_req zone=one burst=1;”
We don’t need rate limiting atm, so let’s remove that, restart, all good.
I then opened up the security group port 80 from My IP and made the request, which fails as desired:
How about if we try getting a token from IMDSv2 via the proxy:
However, notice that’s nginx saying forbidden — the /only/ reason this didn’t work is because Scott took a basic precaution in that proxy config — namely rejecting all except GET requests:
limit_except GET {
deny all;
}
If we remove those lines and restart nginx, we can make the PUT request and still retrieve metadata from IMDSv2 just fine:
Knowing this, if we had to run a web proxy, what are some changes we might make to harden it and prevent such attacks?
- Add the X-Forwarded-For header, requests with which IMDSv2 will drop:
proxy_set_header X-Forwarded-For $remote_addr;
= IMDSv2 responds 403 (requested resource forbidden) - Remove the metadata token header, if supplied:
proxy_set_header X-aws-ec2-metadata-token “”;
= IMDSv2 responds 401 (invalid creds) - Strip the TTL header from potentially-token requests:
proxy_set_header X-aws-ec2-metadata-token-ttl-seconds “”;
= IMDSv2 response 400 (invalid request) - Limit EC2 Security Group inbound (and ideally outbound) to least-privilege
- Ideally — whitelist upstream hosts, and use the user input as little as possible in proxying
- If whitelist is impossible, then we can at least blacklist the Metadata service by putting the proxy_pass in e.g.
if ( $proxyhost != ‘169.254.169.254’) { proxy_pass …. }
= Nginx responds 405 (not allowed)
Right, on to the next, and final, level 6.
This is part of my 4-part Walkthrough of flaws.cloud:
- Levels 1–3 — S3 Buckets
- Level 4 — EBS Snapshot Snarfing
- Level 5 — Instance Profile Metadata Attack
- Level 6 — Read-Only Recon of API Gateway and Lambda — Finale