Breach in the Cloud is a PwnedLabs beginner lab that features an AWS incident response lab that begins with analysing compromised access keys belonging to temp-user. Inspecting the inline IAM policy test-temp-user reveals permission to execute sts:AssumeRole targeting AdminRole. Although assuming the role provides temporary elevated session credentials, global bucket enumeration is restricted by policy boundaries. Parsing CloudTrail log entries using jq isolates extensive automated IAM discovery and tracks an attacker session. Querying ListObjects and GetObject log entries identifies direct API targeting of the emergency-data-recovery S3 bucket. Re-evaluating CLI access against the specific bucket path bypasses global listing limits, permitting retrieval of emergency.txt and message.txt to confirm data exfiltration and complete the forensic investigation.
1. Incident Overview & Initial Identity Assessment
1.1 Scenario
We've been alerted to a potential security incident. The Huge Logistics security team have provided you with AWS keys of an account that saw unusual activity, as well as AWS CloudTrail logs around the time of the activity. We need your expertise to confirm the breach by analyzing our CloudTrail logs, identifying the compromised AWS service and any data that was exfiltrated.
1.2 Credential Configuration & Identity Verification
I was able to produce 7 JSON CloudTrail log files after extracting the ZIP file INCIDENT-3252.zip. Since I was not sure what to look for in the log files, I decided to configure my AWS CLI with the provided credentials.
Tip: You can deliver temporary credentials to the AWS CLI using your AWS Console session by running the command 'aws login'. AWS Access Key ID [None]: AK****************P4 AWS Secret Access Key [None]: Wv**********************************K7QJ Default region name [None]: Default output format [None]:
After configuring, I was able to verify the active identity context by executing aws sts get-caller-identity.
{
"UserId": "AIDARSCCN4A3X2YWZ37ZI",
"Account": "107513503799",
"Arn": "arn:aws:iam::107513503799:user/temp-user"
}Based on the result, I have access as temp-user.
1.3 IAM Policy & Privilege Assessment
To enumerate the permissions attached to the temp-user account, I used aws iam list-user-policies --user-name temp-user. This revealed that a policy named test-temp-user is attached to the user.
{
"PolicyNames": [
"test-temp-user"
]
}I used aws iam get-user-policy --user-name temp-user --policy-name test-temp-user to enumerate the permissions and authorizations that the test-temp-user policy granted to the user.
{
"UserName": "temp-user",
"PolicyName": "test-temp-user",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::107513503799:role/AdminRole"
}
]
}
}Based on the result, temp-user is allowed to assume role/AdminRole.
I used aws iam get-role --role-name AdminRole to enumerate the permissions that role/AdminRole has, but the request was denied because temp-user did not have the necessary permission.
2. Role Assumption & Permission Boundary Testing
2.1 STS Role Assumption
Since the user temp-user has permission to assume the AdminRole role, I was able to make an AWS Security Token Service (STS) call to request temporary credentials for AdminRole.
{
"Credentials": {
"AccessKeyId": "ASI**************4R",
"SecretAccessKey": "IM**********************************1GIL",
"SessionToken": "FwoGZXIvYXdzEL3//////////wEaDGQYDab6oHpZW1eExyK7ARHPw0mxLCcuDABfLjaPXAMqVbi/YsotF6FHQundUb4WtmBsBjYqyEbcf3Pgb/pf+oVyjRpNp/EBHI0z6Bp17c0OMBJomhGnLPcqj64o3CO5/4ONFJumvz8cnpTcyZf2eGHnjUbKmXwSxuUJMTOtnyQwoW+kKfqtVKqRBoE2aUIw+nAag5NNDGjZeZNtZ/moRPxDqNEiUqlfiRPkpdXlO8o3azKYtSH3XtG4LMlPnxv9bq6QJH5sTswdqXko2uvn1gYyLWVSTCHxtKPkvw9QyRtAj/TVU90GDI9U+63dQGGmRnLjYa2tKhYzk/ybeRc4uw==",
"Expiration": "2026-10-22T12:39:06+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROARSCCN4A34V23XHK6I:PwnLabsBreachInTheCloud",
"Arn": "arn:aws:sts::107513503799:assumed-role/AdminRole/PwnLabsBreachInTheCloud"
},
"PackedPolicySize": 9
}The request succeeded, and I received temporary session credentials for the assumed role.
2.2 AWS CLI Profile Configuration
To make it easy to use the AWS CLI commands with the assumed role, I created a dedicated profile and configured it in the ~/.aws/config file.
[default] [profile pwnedlabs-admin] role_arn = arn:aws:iam::107513503799:role/AdminRole source_profile = default role_session_name = PwnLabsBreachInTheCloud region = us-east-1
By running aws sts get-caller-identity --profile pwnedlabs-admin, I was able to confirm the new profile's identity context.
{
"UserId": "AROARSCCN4A34V23XHK6I:PwnLabsBreachInTheCloud",
"Account": "107513503799",
"Arn": "arn:aws:sts::107513503799:assumed-role/AdminRole/PwnLabsBreachInTheCloud"
}3. CloudTrail Log Forensics & Reconnaissance Analysis
To analyse the 7 CloudTrail log files, I used jq. Usually, CloudTrail logs are placed inside a top-level array element Records.
Analysing all the JSON log files at once, I was able to list the most used event names using jq -r '.Records[].eventName' *.json|sort|uniq -c|sort -nr|head -20. The result indicated that there is a large number of List*, Describe*, and Get* calls, which might indicate enumeration attempts.
41 DescribeEventAggregates
9 ListNotificationHubs
7 DescribeOrganization
6 ListDelegatedAdministrators
5 ListSigningCertificates
5 ListMFADevices
5 ListAccessKeys
4 ListPolicies
4 ListPipelines
4 ListJobs
4 ListGroupsForUser
4 ListClusters
4 GetLoginProfile
4 GetAccessKeyLastUsed
4 DescribeClusters
3 ListStreams
3 ListRoles
3 ListGroups
3 ListChannels
3 ListAccessPointsAnalysing API calls with the identity principal revealed that temp-user has performed a large number of IAM enumeration requests from different IP addresses, and the assume role action was performed on role AdminRole.
jq -r '
.Records[]
| [
.userIdentity.type,
.userIdentity.arn,
.sourceIPAddress
]
| @tsv
' *.json | sort | uniq -c | sort -nr
14 IAMUser arn:aws:iam::107513503799:user/temp-user 84.32.71.51
14 IAMUser arn:aws:iam::107513503799:user/temp-user 84.32.71.28
13 IAMUser arn:aws:iam::107513503799:user/temp-user 84.32.71.43
13 IAMUser arn:aws:iam::107513503799:user/temp-user 84.32.71.34
13 IAMUser arn:aws:iam::107513503799:user/temp-user 84.32.71.18
....
1 AssumedRole arn:aws:sts::107513503799:assumed-role/AdminRole/MySession 84.32.71.36
1 AssumedRole arn:aws:sts::107513503799:assumed-role/AdminRole/MySession 84.32.71.3
1 AssumedRole arn:aws:sts::107513503799:assumed-role/AdminRole/MySession 84.32.71.1254. Threat Actor Activity Reconstruction & Target Discovery
Based on the result above, the assumed role session is MySession. Filtering the CloudTrail logs by the session name revealed the event name and the source of the events performed.
q -r '
.Records[]
| select(.userIdentity.arn? | strings | contains("assumed-role/AdminRole/MySession"))
| [
.eventTime,
.eventSource,
.eventName,
.sourceIPAddress,
(.errorCode // "SUCCESS"),
.userAgent
]
| @tsv
' *.json | sort
2023-08-26T20:59:54Z sts.amazonaws.com GetCallerIdentity 84.32.71.36 SUCCESS aws-cli/1.27.74 Python/3.10.6 Linux/5.15.90.1-microsoft-standard-WSL2 botocore/1.29.74
2023-08-26T21:17:10Z s3.amazonaws.com ListObjects 84.32.71.125 SUCCESS [aws-cli/1.27.74 Python/3.10.6 Linux/5.15.90.1-microsoft-standard-WSL2 botocore/1.29.74]
2023-08-26T21:17:16Z s3.amazonaws.com GetObject 84.32.71.3 SUCCESS [aws-cli/1.27.74 Python/3.10.6 Linux/5.15.90.1-microsoft-standard-WSL2 botocore/1.29.74]Earlier, I tried aws s3 ls --profile pwnedlabs-admin using the assumed role to list the available S3 buckets, but access was denied. However, based on the logs, the assumed session MySession by the threat actor was able to perform the ListObjects action.
This means the threat actor must have specified the bucket name and listed the objects in the bucket. I was able to retrieve the bucket name.
jq '
.Records[]
| select(
.userIdentity.arn? ==
"arn:aws:sts::107513503799:assumed-role/AdminRole/MySession"
)
| select(.eventName == "ListObjects" or .eventName == "GetObject")
| {
eventTime,
eventName,
requestParameters,
resources,
errorCode,
responseElements
}
' *.json
{
"eventTime": "2023-08-26T21:17:10Z",
"eventName": "ListObjects",
"requestParameters": {
"list-type": "2",
"bucketName": "emergency-data-recovery",
"encoding-type": "url",
"prefix": "",
"delimiter": "/",
"Host": "emergency-data-recovery.s3.amazonaws.com"
},
"resources": [
{
"type": "AWS::S3::Object",
"ARNPrefix": "arn:aws:s3:::emergency-data-recovery/"
},
{
"accountId": "107513503799",
"type": "AWS::S3::Bucket",
"ARN": "arn:aws:s3:::emergency-data-recovery"
}
],
"errorCode": null,
"responseElements": null
}
{
"eventTime": "2023-08-26T21:17:16Z",
"eventName": "GetObject",
"requestParameters": {
"bucketName": "emergency-data-recovery",
"Host": "emergency-data-recovery.s3.amazonaws.com",
"key": "emergency.txt"
},
"resources": [
{
"type": "AWS::S3::Object",
"ARN": "arn:aws:s3:::emergency-data-recovery/emergency.txt"
},
{
"accountId": "107513503799",
"type": "AWS::S3::Bucket",
"ARN": "arn:aws:s3:::emergency-data-recovery"
}
],
"errorCode": null,
"responseElements": null
}5. Exfiltrated Data Acquisition & Flag Extraction
With the S3 bucket name, I was able to list the objects successfully using the assumed identity.
2023-08-26 17:07:50 2232 emergency.txt 2023-08-26 18:19:02 236 message.txt
I was able to download the files from the S3 bucket successfully and extract the flag.
download: s3://emergency-data-recovery/emergency.txt to ./emergency.txt