flAWS.cloud Walkthrough — Level 5 — Instance ProfileMetadata Attack

Level 5

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;
proxy_pass http://$proxyhost/$proxyuri;
Response of an EC2 metadata request for its Instance Profile credentials
Executing AWS API calls as the stolen Instance Profile

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”.

  • 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

No dice — excellent
limit_except GET {
deny all;
  • 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 != ‘’) { proxy_pass …. }
    = Nginx responds 405 (not allowed)



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nick Doyle

Nick Doyle

Computer Scientist. Agile Enthusiast. Past lives include Perl Hacker, Web Developer, DBA, Tech Lead, Motorcycle Instructor, Forensic Data Analyst, & Cloud Guy