Skip to content

Prerequisites

  • projectx-prod-vpc has been created with subnets configured.
  • My-Desktop-Key-Pair key pair exists.
  • AWS CLI configured with appropriate IAM credentials.

Network Topology

Base Layout
(Click to zoom)

Overview

This guide demonstrates how to identify and exploit hardcoded secrets in AWS resources. We provide example scenarios focusing on S3 buckets and EC2 instances to explore from the CloudFormation template.

Hardcoded secrets are one of the most common cloud security issues, often resulting from developers embedding credentials, API keys, passwords, or tokens directly into configuration files, source code, environment variables, or user data scripts.

These secrets can be discovered through various methods including scanning through public code repositories (GitLab/GitHub), metadata services, exposed configuration files on the Internet, or misconfigured storage buckets.

In this scenario, we'll deploy a deliberately vulnerable environment with hardcoded secrets in a public S3 bucket (simulating a public repository) and EC2 instance user data.

You'll need to explore and discover these secrets like a CTF challenge, following hints and clues to uncover hidden credentials.

What are Hardcoded Secrets?

A hardcoded secret is a string or sequence of characters meant to be kept secret. This is used for authentication (who) and authorization (what or permissions).

Think of credentials, API keys, passwords, tokens, or other sensitive information as examples. These are embedded directly into source code, configuration files, environment variables, or infrastructure-as-code templates rather than being stored securely in a secrets management service.

Common Places Where Secrets Get Hardcoded

Secrets often get hardcoded in various locations:

  • Configuration Files: Secrets stored in config files committed to version control or stored in public storage
  • Source Code: Credentials embedded directly in application code
  • Environment Variables: Sensitive values set in environment variables
  • User Data Scripts: Credentials in EC2 user data scripts (accessible via metadata service)
  • CloudFormation/Terraform Templates: Secrets hardcoded in infrastructure-as-code
  • Public Repositories: Code with secrets committed to public GitHub/GitLab repositories
  • Public Storage Buckets: Configuration files with credentials stored in publicly accessible S3 buckets
  • Log Files: Secrets accidentally logged and exposed

👉 In production environments, secrets should be stored in AWS Secrets Manager, AWS Systems Manager Parameter Store, or similar secure secrets management services, and accessed programmatically at runtime. Never hardcode secrets in files or commit them to version control.

Deploy Vulnerable Environment with Hardcoded Secrets

Run CloudFormation Template

Navigate to the CloudFormation service in the AWS Console.

Select Create stackChoose an existing template.

Choose Upload a template file and select the hardcoded-secrets.yaml template from https://github.com/projectsecio/exercise-files/tree/main/cloud-attacks-101/attacks_cf_templates

Stack name: hardcoded-secrets

Configure Parameters

  • StackName: delete-me-hardcoded-secrets (or leave default)
  • KeyPairName: Select My-Desktop-Key-Pair
  • VpcId: Select projectx-prod-vpc
  • SubnetId: Select a public subnet (e.g., Public Subnet)

Leave everything else default.

Submit

Wait for the stack creation to complete. This should take 2-3 minutes. You can monitor the progress in the CloudFormation console.

👉 The CloudFormation template automatically: - Creates a public S3 bucket with a configuration file (simulating a public repository) - Deploys an EC2 instance (you'll need to manually run the setup script) - Enables IMDSv1 on EC2 instance (allows direct metadata access)

Step 1: SSH into EC2 Instance and Run Setup Script

The EC2 instance is created but the web application is not automatically started. You need to SSH into the instance and run the setup script manually.

Get EC2 Instance Information

# Get the EC2 instance public IP
PUBLIC_IP=$(aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`EC2PublicIP`].OutputValue' \
  --output text)

echo "EC2 Public IP: $PUBLIC_IP"

SSH into the Instance

# SSH into the EC2 instance (adjust path to your key pair)
ssh -i ~/.ssh/My-Desktop-Key-Pair.pem ec2-user@$PUBLIC_IP

Copy and Run the Setup Script

Once SSH'd into the instance, copy the setup script and run it:

# Option 1: Copy setup script from your local machine
# First, exit SSH (or open new terminal), then from your local machine:
scp -i ~/.ssh/My-Desktop-Key-Pair.pem setup-vulnerable-app.sh ec2-user@$PUBLIC_IP:/opt/projectx-app/

# Then SSH back in and run:
ssh -i ~/.ssh/My-Desktop-Key-Pair.pem ec2-user@$PUBLIC_IP
sudo bash /opt/projectx-app/setup-vulnerable-app.sh

# Option 2: Create the script directly on the instance
# SSH into the instance and create the file manually or download it from your repository

The setup script will: - Install required packages (Python, Flask, AWS CLI) - Create a .env file with hardcoded secrets - Download the configuration file from S3 - Create and start the Flask web application - Set up a systemd service to keep it running

Verify the Application is Running

# Check if the Flask app is running
curl http://localhost:8080/health

# Check service status
systemctl status projectx.service

# View logs if needed
journalctl -u projectx.service -f

Once the script completes successfully, the web application will be accessible at http://<EC2_PUBLIC_IP>:8080.

Discovery & Exploration

Step 2: Explore the Web Application

Time to put our attacker hat or lens on to hunt for hardcoded credentials.

Start by exploring the web application via the command line or through the UI.

# Get the EC2 instance public IP and construct app URL
PUBLIC_IP=$(aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`EC2PublicIP`].OutputValue' \
  --output text)

APP_URL="http://$PUBLIC_IP:8080"

# Check the main page
curl $APP_URL

The main page shows a basic welcome message but doesn't reveal available endpoints. This is intentional - you'll need to discover the endpoints yourself.

Step 3: Discover Exposed Endpoints with Directory Enumeration

The web application has exposed endpoints that contain hardcoded secrets, but they're not listed on the main page. You'll need to discover them using directory enumeration tools.

Using Gobuster

Install Gobuster (if not already installed):

# On Linux
wget https://github.com/OJ/gobuster/releases/download/v3.6.0/gobuster_Linux_x86_64.tar.gz
tar -xzf gobuster_Linux_x86_64.tar.gz
sudo mv gobuster /usr/local/bin/

# Or using package manager
sudo apt install gobuster  # For Kali

Run Gobuster:

# Basic directory enumeration
gobuster dir -u $APP_URL -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

# Or use a common wordlist (if SecLists is installed)
gobuster dir -u $APP_URL -w /usr/share/seclists/Discovery/Web-Content/common.txt

# With extensions filter for .env files
gobuster dir -u $APP_URL -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x env,json,txt
Directory Enumeration
Example output from directory enumeration showing discovered endpoints (Click to zoom)

👉 Key Discovery: The /env and /config/env endpoints directly expose configuration files containing hardcoded credentials. These files were accidentally exposed by the developer, making them accessible without authentication.

Step 4: Discover the Public S3 Bucket

Based on the hints from the web application, we need to discover the S3 bucket containing the secrets file. In a real attack scenario, an attacker wouldn't have AWS console access. Let's explore realistic discovery methods.

Method 1: Bucket Enumeration

In a real attack, attackers might enumerate S3 buckets using common naming patterns or tools like s3scanner. The S3 bucket URL may also be embedded within application code.

Method 2: Extract from EC2 Instance (if you have SSH access)

If you have SSH access to the EC2 instance (which you should from setting it up), you can check the environment or look for S3 bucket references:

# SSH into the instance
ssh -i ~/.ssh/My-Desktop-Key-Pair.pem ec2-user@$PUBLIC_IP

# Check environment variables
echo $S3_BUCKET_NAME

# Or find bucket in the downloaded config file
cat /tmp/app-secrets.json | grep -i bucket || true

# List S3 buckets accessible by the instance
aws s3 ls | grep -i secrets

Method 3: Extract from CloudFormation Outputs

If you have AWS console or CLI access, get it from CloudFormation outputs:

aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`SecretsBucketName`].OutputValue' \
  --output text

Step 5: Access the Configuration File from S3

Once you've discovered the S3 bucket, access the secrets file:

List Bucket Contents

# List bucket contents (public access)
aws s3 ls s3://$BUCKET_NAME --no-sign-request

# List recursively to find the config file
aws s3 ls s3://$BUCKET_NAME --recursive --no-sign-request

Download the Configuration File

# Download the config file
aws s3 cp s3://$BUCKET_NAME/config/app-config.json . --no-sign-request

# View the file
cat app-config.json
Config File
(Click to zoom)

The configuration file contains hardcoded secrets in a simple key=value format:

  • Database credentials (host, user, password)
  • AWS credentials (access key ID, secret access key)
  • API keys

👉 Note: This file is in a simple configuration file format (not JSON), which is more realistic for scenarios where developers accidentally upload configuration files.

Access via Public URL

You can also access the file directly via its public URL:

# Get the public URL from CloudFormation
CONFIG_URL=$(aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`ConfigFileURL`].OutputValue' \
  --output text)

# Download via curl
curl $CONFIG_URL

Step 6: Discover Additional Secrets on EC2 Instance

Now that you've discovered secrets exposed via the web application endpoints (/env and /config/env), let's also check for secrets stored locally on the EC2 instance.

Access Secrets Files on EC2 Instance

If you have SSH access to the EC2 instance, you can directly examine the .env files:

# SSH into the instance
ssh -i ~/.ssh/My-Desktop-Key-Pair.pem ec2-user@$PUBLIC_IP

# View the .env file in the app directory
cat /opt/projectx-app/.env

# View the .env file in the config directory
cat /opt/projectx-app/config/.env

# View the downloaded config file from S3
cat /tmp/app-secrets.json

Access EC2 Instance Metadata Service (IMDSv1)

The EC2 instance has IMDSv1 enabled, which allows access to metadata. From within the instance or via SSRF:

# From within the EC2 instance (via SSH)
curl http://169.254.169.254/latest/user-data

# Check instance metadata for any secrets
curl http://169.254.169.254/latest/meta-data/

👉 Note: In this scenario, the EC2 instance was created without UserData, so there are no secrets in user data. However, the .env files on the filesystem and the exposed web endpoints (/env, /config/env) contain the hardcoded secrets.

Exploitation Phase

Now we could test and use the valid hardcoded secrets during discovery to get access to additional resources.

👉 All of the hardcoded secrets discovered in this attack are not real, so they won't actually work.

Potential Impact

Based on the extracted secrets, an attacker could:

  • AWS Account Compromise: Use AWS credentials to access AWS services, potentially leading to data exfiltration, resource manipulation, or privilege escalation
  • Database Access: Use database credentials to access sensitive customer data, modify records, or perform data exfiltration
  • Third-Party Service Access: Use API keys to access external services (Stripe, GitHub, Slack), potentially incurring costs or accessing sensitive data
  • Lateral Movement: Use extracted credentials to access other systems or services within the organization
  • Persistence: Create new IAM users or access keys for long-term access
  • Data Exfiltration: Access and download sensitive data from databases, S3 buckets, or other storage systems

👉 In a real attack, the attacker would likely use these credentials to explore the AWS environment, identify high-value targets, and potentially escalate privileges or exfiltrate data.

Detection and Prevention

How to Detect Hardcoded Secrets

  • AWS Secrets Manager: Use AWS Secrets Manager to store and rotate secrets securely
  • AWS Systems Manager Parameter Store: Store secrets in Parameter Store with encryption
  • GitGuardian or Similar Tools: Scan code repositories and configuration files for hardcoded secrets
  • CloudTrail: Monitor API calls for unusual access patterns that might indicate credential compromise
  • GuardDuty: Detects suspicious API calls that might indicate unauthorized access
  • Code Reviews: Regular code reviews to identify hardcoded secrets before deployment
  • Static Application Security Testing (SAST): Use SAST tools to scan code and configuration files for hardcoded secrets
  • Regular Audits: Periodically audit code, configuration files, and infrastructure-as-code for hardcoded secrets
  • S3 Bucket Audits: Regularly audit S3 bucket policies and public access settings
  • EC2 Metadata Service: Use IMDSv2 and restrict access to metadata service

Best Practices for Secrets Management

  • Never Hardcode Secrets: Use AWS Secrets Manager, Parameter Store, or similar services
  • Use IAM Roles: Use IAM roles for EC2 instances instead of hardcoded AWS credentials
  • Rotate Secrets Regularly: Implement secret rotation policies to limit exposure window
  • Never Commit Secrets to Version Control: Use .gitignore to exclude files containing secrets
  • Use Environment-Specific Secrets: Store different secrets for different environments (dev, staging, prod)
  • Encrypt Secrets at Rest: Ensure all secrets are encrypted when stored
  • Use Least Privilege: Grant only necessary permissions to secrets
  • Monitor Secret Access: Log and monitor all access to secrets
  • Use Secrets Scanning Tools: Integrate secret scanning into CI/CD pipelines
  • Educate Developers: Train developers on secure secrets management practices
  • Secure S3 Buckets: Never store secrets in public S3 buckets; use proper bucket policies
  • Use IMDSv2: Enable IMDSv2 on EC2 instances to prevent SSRF-based metadata access

👉 Always use AWS Secrets Manager, AWS Systems Manager Parameter Store, or similar secure secrets management services instead of hardcoding secrets in configuration files, code, or infrastructure templates.

Cleanup

Warning

After completing this exercise, delete all objects inside the S3 bucket. Then choose Delete on CloudFormation stack to remove all resources.

# Stop the Flask application on EC2 (if running)
PUBLIC_IP=$(aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`EC2PublicIP`].OutputValue' \
  --output text)

ssh -i ~/.ssh/My-Desktop-Key-Pair.pem ec2-user@$PUBLIC_IP "sudo systemctl stop projectx.service || true"

# Empty the S3 bucket first
BUCKET_NAME=$(aws cloudformation describe-stacks \
  --stack-name hardcoded-secrets \
  --query 'Stacks[0].Outputs[?OutputKey==`SecretsBucketName`].OutputValue' \
  --output text)

aws s3 rm s3://$BUCKET_NAME --recursive

# Delete the CloudFormation stack
aws cloudformation delete-stack --stack-name hardcoded-secrets

Wait for the stack deletion to complete:

aws cloudformation wait stack-delete-complete --stack-name hardcoded-secrets

👉 Ensure the bucket is empty before deletion, or CloudFormation will fail to delete the stack. Also, ensure you've removed any AWS credentials configured from the exercise before continuing with other work.