An Australian-based company recently migrated from on-premise to AWS Cloud infrastructure. During the migration, several misconfigurations were introduced that a threat actor group leveraged to gain full access to their environment. The company has no IR team and manages their cloud exclusively via browser — no CLI was configured. We’re provided with AWS CloudTrail and VPC Flow Logs ingested into Splunk to reconstruct the full attack chain.
Note: The
_timefield in Splunk reflects when logs were ingested into Splunk, not when events occurred. All timestamps referenced here useeventTime(CloudTrail) andstart/end(VPC Flow Logs).
The investigation begins with CloudTrail, querying for S3 GetObject events to identify what was accessed and by whom:
index=* eventSource="s3.amazonaws.com" eventName="GetObject"
| table eventTime, sourceIPAddress, requestParameters.bucketName, requestParameters.key, userAgent
Two source IPs appear in the results — 103[.]108[.]229[.]19 and 18[.]216[.]138[.]52. Filtering on 18[.]216[.]138[.]52 reveals the attacker accessed the developers-configuration bucket and downloaded VPN-Profiles/DevelopersProfile_wg0.conf. A WireGuard VPN configuration file contains private keys and peer endpoints — effectively handing the attacker direct network-level access into the VPC. This is the root cause of the entire compromise: a sensitive credential file sitting in a misconfigured, publicly accessible S3 bucket.
With a valid WireGuard config in hand, the attacker connected directly into the VPC. WireGuard operates on UDP port 51820, so the VPC Flow Logs capture the handshake:
index=* dstport=51820
| table start, srcaddr, dstaddr, dstport, protocol
| sort start
The connection originates from 122[.]161[.]49[.]105 — notably different from the S3 attacker IP. One of the threat actors made an OPSEC error, connecting via their personal machine rather than routing through their usual anonymising infrastructure. This leaks their real IP address. The WireGuard endpoint they connected to maps to EC2 instance with private IP 10[.]0[.]1[.]125.
From inside the EC2 instance, the attacker enumerated the AWS environment using the instance’s attached IAM role via the Instance Metadata Service (IMDS). CloudTrail logs AssumeRole calls, but filtering these is noisy due to AWS internal services constantly assuming roles. Excluding the service noise:
index=* eventName="AssumeRole" sourceIPAddress!="resource-explorer-2.amazonaws.com"
| table eventTime, sourceIPAddress, requestParameters.roleArn
| sort eventTime

The results show ec2.amazonaws.com as the source — the EC2 instance itself assuming its attached role, which is exactly how IMDS credential abuse appears in CloudTrail. Expanding the raw event confirms the full role ARN:

The role arn:aws:iam::764581110688:role/ec2-role is assumed, with the session name set to the instance ID i-00eda415438b3d90c and temporary credentials issued with access key ASIA3EBEV4OQLCERBWSP. The attacker now has IAM permissions to operate across the AWS environment.
Armed with the ec2-role credentials, the attacker pivots to IAM to establish persistence. Searching CloudTrail for IAM write events tied to the compromised instance:
index=* eventSource="iam.amazonaws.com" "i-00eda415438b3d90c"
| table eventTime, eventName, requestParameters
| sort eventTime
The first page of results is pure enumeration — ListUsers, GetUser, ListAttachedUserPolicies, ListRoles, and so on. The attacker is thoroughly mapping out the IAM landscape before acting. Scrolling to page two, the write actions appear:

The persistence chain executes in three steps — CreateUser at 11:10:59Z, CreateAccessKey at 11:11:10Z, and AttachUserPolicy at 11:11:22Z. Expanding the CreateUser event reveals the new identity:

The rogue user web_engg_2 is created, blending in with what appears to be an existing naming convention for engineering accounts. The AttachUserPolicy event confirms what was granted:

The policy arn:aws:iam::aws:policy/AdministratorAccess is attached — full, unrestricted access to the entire AWS account. The attacker now has a persistent backdoor IAM user with programmatic access keys that survives even if the EC2 instance is terminated.
With AdministratorAccess and knowledge of the internal network layout from the enumeration phase, the attacker moves laterally. Checking VPC Flow Logs for SSH connections:
index=* dstport=22
| table start, srcaddr, dstaddr, dstport
| sort start

The source IP 3[.]26[.]170[.]141 — the public IP of EC2-1 (10[.]0[.]1[.]125) — is used as a jump host to SSH into a second instance at private IP 10[.]0[.]2[.]32. The attacker is pivoting deeper into the network using the initially compromised instance as a launchpad.
From the second EC2 instance, the attacker establishes a reverse shell for interactive C2 access. Filtering VPC Flow Logs for outbound TCP connections from 10[.]0[.]2[.]32:
index=* srcaddr="10.0.2.32" protocol=6
| stats count by dstaddr, dstport
| sort -count
The vast majority of traffic is port 443 HTTPS — legitimate AWS service communication. Port 123 UDP (NTP) is also noise. The standout entry is two connections to 3[.]15[.]209[.]50 on port 13337 — a deliberate leet-speak port choice that immediately flags as attacker infrastructure. The low connection count is consistent with a reverse shell initiation rather than sustained polling. Notably, 3[.]15[.]209[.]50 also appeared in the SSH lateral movement traffic, confirming this is the attacker’s C2 server.

S3 Misconfiguration (developers-configuration bucket public)
→ WireGuard config downloaded (DevelopersProfile_wg0.conf)
→ Attacker VPN into VPC (OPSEC fail: real IP 122[.]161[.]49[.]105 leaked)
→ EC2-1 (10[.]0[.]1[.]125) accessed via WireGuard
→ IMDS abuse → ec2-role assumed (arn:aws:iam::764581110688:role/ec2-role)
→ IAM enumeration → rogue user web_engg_2 created with AdministratorAccess
→ SSH pivot from EC2-1 to EC2-2 (10[.]0[.]2[.]32)
→ Reverse shell → 3[.]15[.]209[.]50:13337
| Type | Value |
|---|---|
| IP — Initial S3 Attacker | 18[.]216[.]138[.]52 |
| IP — Attacker Real IP (OPSEC fail) | 122[.]161[.]49[.]105 |
| IP — C2 / Reverse Shell | 3[.]15[.]209[.]50 |
| S3 Bucket | developers-configuration |
| File | VPN-Profiles/DevelopersProfile_wg0.conf |
| IAM Role ARN | arn:aws:iam::764581110688:role/ec2-role |
| IAM User | web_engg_2 |
| Policy ARN | arn:aws:iam::aws:policy/AdministratorAccess |
| EC2 Private IP — Initial | 10[.]0[.]1[.]125 |
| EC2 Private IP — Lateral | 10[.]0[.]2[.]32 |
| Port — Reverse Shell | 13337 |