Skip to content

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.

How to investigate a leaked AWS access key

A leaked AWS access key: deactivate it, then use CloudTrail to find where it was used, what it enumerated, what it created and which data it could reach.

Published on 6 min read

TL;DR. 1) Deactivate the key (aws iam update-access-key --status Inactive), don't delete it yet. 2) Pull every CloudTrail event where userIdentity.accessKeyId is the key, from well before the leak to now. 3) Find the first use from a new IP: that is the attacker's entry. 4) From there, list what the key enumerated, what it created (users, keys, passwords, policies, instances) and what data it could read. 5) Pivot on the attacker's IP to catch other credentials. Remediate everything the key created, not just the key.

Long-term access keys — the ones whose ID starts with AKIA (IAM identifiers reference) — do not expire. When one lands in a public repository, a CI log, a Docker image layer or a pasted support ticket, whoever finds it has the permissions of its user until someone notices. The investigation is about answering "what did they do with it?" precisely enough to undo all of it.

Step 0: deactivate, don't delete

aws iam update-access-key --user-name <user> --access-key-id AKIA... --status Inactive

An inactive key can no longer authenticate. Keeping it (inactive) rather than deleting it means its ID stays visible in IAM and the credential report while you investigate. Before deactivating, find out what legitimately uses the key — a production job failing at 3 a.m. is a price worth knowing about in advance, not a reason to wait.

Check whether AWS already reacted: if the user has the AWSCompromisedKeyQuarantineV3 policy attached, AWS detected the exposure and opened a support case. The policy denies a list of high-risk actions (iam:CreateUser, iam:CreateAccessKey, ec2:RunInstances and more); an attacker who acted before it was attached may still have succeeded.

If the key leaked from a GitHub repository, the repository's own audit trail — who pushed it, when, whether the repo was public — is a separate investigation; githubforensics.com covers that side.

Step 1: get the key's full history

With a trail, filter the exported files on the key. With jq on a folder of decompressed files:

zcat cloudtrail/**/*.json.gz | jq -c '.Records[]
  | select(.userIdentity.accessKeyId == "AKIA...")
  | [.eventTime, .sourceIPAddress, .eventSource, .eventName, (.errorCode // "")]'

Without a trail, event history supports a lookup by key (per region):

aws cloudtrail lookup-events --region us-east-1 \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIA...

The credential report's access_key_1_last_used_date, _region and _service columns give a quick sanity check, but only record the first use in each 15-minute span (IAM documentation). CloudTrail is the source of truth.

Step 2: find the first malicious use

Group the key's events by sourceIPAddress and userAgent. Legitimate use clusters: the office egress, a CI runner's range, the developer's CLI version. The attacker shows up as:

  • a new address (often a VPS or hosting provider),
  • a different user agent — another OS, an older SDK, sometimes a giveaway such as a Kali build string,
  • a GetCallerIdentity call as the very first request. It requires no permissions (STS API reference), so it is the universal "does this key work, and whose is it?" check.

The analyzer's "Long-term access key used from a new IP address" detection automates this: it learns the key's addresses during its first day in the logs and flags later use from a public address outside that set. Its value depends on the baseline, so load a few days before the suspected leak.

Step 3: what did it enumerate?

Attackers map permissions fast. In CloudTrail this is a burst of dozens of different List*, Describe* and Get* calls across IAM, S3, EC2, Lambda, Secrets Manager and others within minutes, frequently mixed with AccessDenied errors where the key lacks rights. GetAccountAuthorizationDetails is a high-value target: it returns every user, role, group and policy in one call.

PatternEvidenceATT&CK
Key validity checksts:GetCallerIdentity from a new IPT1087.004
Service enumeration30+ distinct List/Describe/Get in 10 minT1580, T1526
Permission probingBurst of AccessDenied / UnauthorizedOperationT1069.003
Offensive toolinguserAgent containing Kali, Pacu, CloudFox…T1078.004

Failed calls matter: they show what the attacker wanted, which tells you what to protect next.

Step 4: what did it create?

This is where investigations fail most often. Look for every successful write:

  • CreateUser, CreateLoginProfile, CreateAccessKey (especially for another user), AttachUserPolicy / PutUserPolicy with admin rights;
  • UpdateAssumeRolePolicy, CreateSAMLProvider, CreateOpenIDConnectProvider;
  • RunInstances, CreateKeyPair, ImportKeyPair, AuthorizeSecurityGroupIngress;
  • CreateFunction, AddPermission, CreateFunctionUrlConfig.

The responseElements of CreateAccessKey contains the new key ID — your next pivot. The IAM persistence and privilege escalation post describes each technique.

Step 5: what data could it reach?

Management events show ListBuckets and GetBucketPolicy, not object reads. To know whether objects were downloaded you need S3 data events or server access logs; see S3 data exfiltration evidence. Also check GetSecretValue, GetParametersByPath, ModifySnapshotAttribute (sharing a snapshot with another account) and GetPasswordData.

Step 6: pivot on the attacker's IP

Pivot on every attacker address across all principals. You will often find the backdoor user's new key, a console login of a created user, or an assumed role session — activity that no longer carries the leaked key's ID.

Remediation checklist for a leaked key

  1. Leaked key: inactive now, deleted after scoping; remove it from code, CI variables, images and history.
  2. Every key created during the incident: deactivate then delete.
  3. Users, login profiles, group memberships and policies created or attached by the attacker: save, then remove.
  4. Role trust policies and identity providers changed: revert.
  5. Instances, key pairs, security group rules, Lambda functions in all regions: snapshot if needed, then remove.
  6. Secrets the attacker read: rotate.
  7. Logging and detection services disabled: re-enable (defense evasion post).

Drop the key's CloudTrail history into the analyzer to get this list pre-filled with the actual key IDs, users and regions from your logs. For the account-wide view, go back to the AWS incident response overview.

FAQ

Can I see which IP addresses used a leaked AWS access key?

Yes. Every CloudTrail event signed with the key carries it in userIdentity.accessKeyId and the caller's address in sourceIPAddress. Filter on the key and list the distinct addresses; event history works for the last 90 days of management events if you have no trail.

Is a GetCallerIdentity call proof that a key was stolen?

No. The AWS CLI and SDKs call it routinely and it needs no permissions. It becomes meaningful when it is the first call from an address the key has never used, especially when enumeration follows within minutes.

Does deactivating the key stop the attacker?

It stops that key. It does not stop credentials the attacker created with it: other access keys, console passwords, users, role trust changes. Those are found by scoping the key's activity in CloudTrail.

Further reading

Related articles

AWS account compromised? What to do first, which logs to secure, how to scope the attack in CloudTrail and how to contain it without destroying evidence.
A fictional AWS incident investigated from its logs: leaked key, recon, backdoor admin, GuardDuty deleted, 320 S3 objects taken, GPU mining in Singapore.
How attackers keep access to AWS through IAM: backdoor users, extra keys, admin policies, role trust and identity providers, and how CloudTrail shows it.

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.