AWS incident response example: leaked key to crypto-mining
A fictional AWS incident investigated from its logs: leaked key, recon, backdoor admin, GuardDuty deleted, 320 S3 objects taken, GPU mining in Singapore.
Fictional scenario. Everything in this post — the company, the people, account
111122223333, the keys, the bucket — is invented. IP addresses come from the documentation ranges of RFC 5737. It is the data behind the Try a sample button on the analyzer, so you can reproduce every step.
TL;DR. On the fictional 14 September 2026, developer dev-marco's access key is used from a VPS running Kali Linux. In 48 minutes the attacker validates the key, enumerates the account, creates an administrator user backup-admin with its own key and console password, deletes GuardDuty in two regions, downloads 320 objects from acme-customer-exports and launches eight GPU instances in ap-southeast-1 that mine to a pool. The logs show all of it. The verdict is Compromised, and the remediation list has to cover two keys, one user, one policy, two detectors, eight instances and a notification assessment.
The evidence
The sample ZIP mirrors real exports:
| Source | Content |
|---|---|
CloudTrail trail (S3 layout, json.gz, plus a digest file) | 449 events from 10 to 14 September, including 325 S3 data events |
| VPC Flow Logs (S3 delivery, header line) | 2,335 records, eu-west-1 and ap-southeast-1 |
| S3 server access logs | 324 lines for acme-customer-exports |
GuardDuty get-findings output | 2 findings, exported before the detectors were deleted |
| IAM credential report | 4 entries, generated after the incident |
Four quiet days (10–13 September) give the baseline: ops-julia signs in to the console with MFA from the office address 198.51.100.24; dev-marco uses the CLI with his long-term key from the same address; an ECS task role is assumed daily.
Timeline (UTC, 14 September 2026)
| Time | Event | Principal | Finding |
|---|---|---|---|
| 09:02:11 | sts:GetCallerIdentity from 203.0.113.77, user agent contains kali | dev-marco (AKIA3EXAMPLEMARCO123) | Key used from a new IP (high), offensive tooling (high), GetCallerIdentity (low) |
| 09:03–09:07 | 48 List/Describe/Get calls across IAM, S3, EC2, Lambda, RDS, KMS, Organizations… 14 of them AccessDenied | dev-marco | Enumeration burst (medium), AccessDenied burst (medium) |
| 09:07 | GuardDuty: Discovery:IAMUser/AnomalousBehavior, PenTest:IAMUser/KaliLinux | — | GuardDuty finding ×2 (medium) |
| 09:08:40 | iam:CreateUser backup-admin | dev-marco | IAM user created (low) |
| 09:08:52 | AttachUserPolicy AdministratorAccess → backup-admin | dev-marco | Administrator policy attached (high) |
| 09:09:05 | CreateAccessKey for backup-admin → AKIA3EXAMPLEBKUP4567 | dev-marco | Key created for another user (high) |
| 09:09:20 | CreateLoginProfile for backup-admin, no reset required | dev-marco | Console password set (medium) |
| 09:12:12 / 09:12:43 | guardduty:DeleteDetector in us-east-1 and eu-west-1 (Boto3) | backup-admin | GuardDuty disabled ×2 (high) |
| 09:15:00–09:36:02 | ListObjects then 320 GetObject on acme-customer-exports | backup-admin | Bulk S3 download — data events (high) and access logs (high) |
| 09:41:10 | CreateKeyPair ops-maint in ap-southeast-1 | backup-admin | Key pair created (low) |
| 09:42:05 | Security group opened to 0.0.0.0/0 on port 22 | backup-admin | Security group opened (medium) |
| 09:43:10 | Quota increase for "Running On-Demand P instances" | backup-admin | Quota increase (medium) |
| 09:44:10 / 09:46:10 | RunInstances 4× p3.8xlarge, 4× g4dn.12xlarge | backup-admin | GPU instances (high), new region (medium) |
| 09:50:31 | ConsoleLogin backup-admin, MFAUsed: No, Firefox on Linux | backup-admin | Console sign-in without MFA (medium) |
| 09:52 → 12:01 | 8 interfaces hold connections to 192.0.2.150 on ports 3333 and 14444 | — | Mining-pool ports (high), 1,040 records |
The credential report adds a posture finding: backup-admin has a console password and no MFA.
How the investigation unfolds
1. The verdict. Twenty-three findings, several of them high-severity with attacker-side signals (access anomaly, defense evasion, exfiltration, impact): the verdict is Compromised. The main reasons listed are the offensive user agent, the key used from a new IP, the admin policy, the key created for another user and the two GuardDuty deletions.
2. The entry point. Pivot on AKIA3EXAMPLEMARCO123 in the Entities tab: four days from 198.51.100.24 with a macOS CLI, then, from 09:02, 203.0.113.77 with a Kali build string. The very first call from the new address is GetCallerIdentity — the textbook leaked access key opening. How the key leaked (the scenario says a public repository) is outside AWS logs.
3. The second key. Pivot on 203.0.113.77: besides dev-marco's key it used AKIA3EXAMPLEBKUP4567, the key minted at 09:09:05 for backup-admin. From 09:12 on, the attacker never uses the leaked key again. Deactivating only dev-marco's key would have changed nothing. This is the IAM persistence pattern in its plainest form.
4. Defense evasion. Both detectors are deleted within a minute, three minutes after the backdoor exists. CloudTrail itself is left running — no StopLogging — which is why the rest of the story is visible. GuardDuty findings survive only because they had been exported; see the defense evasion post.
5. The data. 320 GetObject in 21 minutes from one principal on one bucket, seen twice: in CloudTrail data events and in server access logs. The object keys (exports/2026/customers/customers-0001.csv.gz to -0320) are the disclosure inventory. Note that VPC Flow Logs show nothing here: the download went from S3 to the attacker's host over the internet, never through the VPC — exactly the limit described in S3 data exfiltration evidence.
6. The cost. A region with no activity during the baseline, a quota increase, a key pair, SSH open to the world and eight GPU instances; from 09:52 their interfaces talk to one address on pool ports every minute. The crypto-mining post covers this phase.
7. The console session. At 09:50 the attacker signs in as backup-admin without MFA. Anything done in the console from then on would appear with sessionCredentialFromConsole; in this dataset the session ends the story.
The remediation list it produces
Containment first, with values from the logs:
- Deactivate
AKIA3EXAMPLEMARCO123andAKIA3EXAMPLEBKUP4567; replace dev-marco's key and scrub it from the repository. - Save, then delete
backup-admin(policy, key, login profile). - Detach AdministratorAccess from
backup-adminand review other holders. - Deny API calls from
203.0.113.77while investigating. - Snapshot one instance, terminate the eight in ap-southeast-1, check every other region; delete key pair
ops-maintand the open security group; withdraw quota requestL-417A185B. - Re-enable GuardDuty in us-east-1 and eu-west-1 (ideally everywhere).
- Treat
acme-customer-exportsas exfiltrated: list the 320 objects, assess notification duties. - Open an AWS Support case about the unauthorised usage.
What this example does not show
Real incidents are messier: noisy CI roles, several attacker IPs, role chaining, activity spread over weeks, logs missing exactly where you need them. The sample is deliberately clean so the method is visible. Read the limits of log-only investigation before applying the same confidence to real data, and use the step-by-step analysis guide for the workflow.