When one AWS principal creates an access key for a different IAM user, it has just minted a permanent credential that it controls but that is attributed to someone else. That is the persistence move MITRE ATT&CK records as T1098.001 Additional Cloud Credentials, and in most accounts it looks almost identical to a user rotating their own key. This runbook shows how to tell the two apart in CloudTrail, how to hunt the last 90 days and older history, how to build a near real-time alert with EventBridge, and how to triage and contain a hit without breaking a production integration. It is for cloud security engineers and detection engineers in single accounts or AWS Organizations. For the wider picture of key risk, see the AWS Access Keys Security Guide.
This is an independent guide from NHI Mgmt Group, based on AWS's public documentation as of November 2026. It is not sponsored or reviewed by AWS. Product features change, so confirm settings against AWS's current documentation.
Key takeaways
- The signal is caller not equal to target.
CreateAccessKeytakes an optionalUserName; if it is omitted, IAM creates the key for the identity that signed the request. A presentrequestParameters.userNamethat differs from the caller is the event to examine. - Role callers are always "another user". Roles cannot own access keys, so any
CreateAccessKeyfrom anAssumedRolesession creates a key for someone else. Allow-list the provisioning roles that legitimately do this and alert on the rest. - Look in us-east-1. IAM events are recorded in US East (N. Virginia), and your multi-Region trail must have
IncludeGlobalServiceEventsset to true. An EventBridge rule in any other Region never sees them. - Pattern languages cannot compare two fields. EventBridge patterns and CloudWatch metric filters compare a field with a literal. Narrow with the pattern, then do the caller versus target comparison in a Lambda function or in SQL.
- Contain by deactivating, not deleting.
update-access-key --status Inactivestops the key at once and is reversible if the key turns out to be a legitimate integration; deletion destroys evidence and cannot be undone.
When you need this
- Any account that still has IAM users: as long as long-term keys exist, the ability to create one for another user is a privilege escalation and persistence path. A low-privileged principal with
iam:CreateAccessKeyon an administrator user becomes that administrator. - After a suspected key compromise: attackers who hold one key often create a second on a different user so that rotating the first key does not remove them. The How to Investigate CloudTrail for a Compromised Access Key runbook treats every key created this way as a new anchor.
- Building a cloud identity detection baseline: this is one of the highest-value, lowest-volume IAM detections, because legitimate cross-user key creation is rare and should come from a small set of known automation.
- Audit evidence: NIST SP 800-53 AC-2(4) Automated Audit Actions asks for automatic auditing of account creation and modification; credential additions to an existing account fall squarely within it.
Before you start
| Requirement | Why it matters | How to check |
|---|---|---|
| A multi-Region trail with global service events | IAM events have been recorded in us-east-1 since 22 November 2021; a multi-Region trail must have IncludeGlobalServiceEvents true | aws cloudtrail describe-trails and get-trail-status; confirm IsMultiRegionTrail, IncludeGlobalServiceEvents and that logging is on |
| Management events logged | CreateAccessKey is a write management event; trails log management events by default | Check the trail's event selectors have not excluded write events |
| Athena table over the trail bucket | Event history covers only 90 days; older hunting needs the S3 logs | Create the CloudTrail table with partition projection, pointing the location at the us-east-1 prefix |
| A list of approved key creators | The only way to separate a provisioning pipeline from an attacker | Ask platform teams which roles or users create keys for others, and why |
| Read permissions | Hunting must not change anything | cloudtrail:LookupEvents, Athena and S3 read, iam:ListAccessKeys, iam:GetAccessKeyLastUsed |
CloudTrail Lake is an option for existing customers, but AWS states it is no longer open to new customers from 31 May 2026, so this runbook uses Athena and EventBridge. See the AWS CloudTrail Guide for trail design.
Step-by-step
Steps 1 to 5 are read-only or create detection resources only. Step 6 changes a credential and is reversible.
Step 1: Understand the fields that decide the case
Caller userIdentity.type | Where the caller's name is | Key created for | Default verdict |
|---|---|---|---|
IAMUser, no requestParameters.userName | userIdentity.userName | The caller itself | Self-service; normal, but watch key count and age |
IAMUser, userName equal to caller | userIdentity.userName | The caller itself | Self-service |
IAMUser, userName different from caller | userIdentity.userName | Another user | Alert: rare for humans, common in attacks |
AssumedRole | sessionContext.sessionIssuer.arn (top-level userName is absent) | Always another user | Alert unless the issuer role is on the approved list |
Root, no userName | Account alias at most | The root user | Critical: a root access key |
Root, with userName | Account alias at most | An IAM user | Alert: root use for IAM administration |
FederatedUser | sessionContext.sessionIssuer | Another user if userName is present | Alert: GetFederationToken sessions are unusual |
Also record sourceIPAddress, userAgent, sessionContext.attributes.mfaAuthenticated, errorCode and the new key ID from responseElements. AWS logs the full responseElements for mutating events, but the secret access key is only ever returned to the caller at creation. Console users signing in through IAM Identity Center appear as AssumedRole sessions, so the session name in userIdentity.arn identifies the person behind the role.
Step 2: Hunt the last 90 days in Event history
Event history needs no trail and covers 90 days of management events per Region. Query us-east-1 explicitly:
aws cloudtrail lookup-events --region us-east-1 \
--lookup-attributes AttributeKey=EventName,AttributeValue=CreateAccessKey \
--start-time 2026-08-13T00:00:00Z
Each result holds the raw event in CloudTrailEvent. Parse it and apply the Step 1 table. In an organisation, repeat per member account, or use the organisation trail in Step 3 instead.
Step 3: Hunt older history with Athena
In the Athena CloudTrail table, requestparameters and responseelements are strings, so extract fields with JSON functions. Always filter on the timestamp partition to limit the data scanned.
SELECT eventtime,
useridentity.type AS caller_type,
useridentity.arn AS caller_arn,
useridentity.sessioncontext.sessionissuer.arn AS issuer_arn,
json_extract_scalar(requestparameters, '$.userName') AS target_user,
json_extract_scalar(responseelements, '$.accessKey.accessKeyId') AS new_key_id,
sourceipaddress, useragent, errorcode
FROM cloudtrail_logs
WHERE eventsource = 'iam.amazonaws.com'
AND eventname = 'CreateAccessKey'
AND "timestamp" >= '2025/11/01'
AND json_extract_scalar(requestparameters, '$.userName') IS NOT NULL
AND (useridentity.type <> 'IAMUser'
OR useridentity.username <> json_extract_scalar(requestparameters, '$.userName'))
ORDER BY eventtime;
Keep rows with an errorcode such as AccessDenied: a denied attempt to create a key for an administrator is a strong sign that someone is probing for escalation paths. Add the same query for UpdateAccessKey (an inactive key reactivated by another principal) and CreateLoginProfile (console password added to an API-only user), which serve the same purpose for an attacker.
Step 4: Build the real-time rule in us-east-1
EventBridge receives CloudTrail events on the default event bus with the detail type AWS API Call via CloudTrail, provided a trail is logging. Write management events are matched by rules in the normal ENABLED state. Create the rule in us-east-1 and narrow it to calls that name a target user:
{
"source": ["aws.iam"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["iam.amazonaws.com"],
"eventName": ["CreateAccessKey"],
"requestParameters": { "userName": [{ "exists": true }] }
}
}
The pattern cannot test whether the target equals the caller, because EventBridge operators compare a field only with literal values. Send matches to a small Lambda function that:
- Drops events where
userIdentity.typeisIAMUseranduserIdentity.userNameequalsrequestParameters.userName. - Drops events whose
sessionContext.sessionIssuer.arnis on the approved creator list, but only when the target user also matches that creator's expected naming or path (a provisioning role creating keys forsvc-users should not be creating one foradmin-users). - Raises everything else to your SIEM or on-call channel with the Step 1 fields, the new key ID and the target user's attached policies.
Add a second rule with no userName condition and "userIdentity": { "type": ["Root"] } so that any root access key creation pages immediately. For an organisation, forward the matched events from each member account's us-east-1 default bus to a central security account bus. See the Cloud Detection Engineering Guide for pipeline design.
Step 5: Add the layers that need no code
- CloudWatch Logs metric filter: if the trail delivers to CloudWatch Logs, a filter such as
{ ($.eventName = "CreateAccessKey") && ($.userIdentity.type = "AssumedRole") }counts role-initiated key creation for a simple alarm. Like EventBridge, metric filter syntax compares fields with literals only. - GuardDuty:
Persistence:IAMUser/AnomalousBehavior(default severity Medium) is raised when a persistence API such asCreateAccessKeyis called in a way the machine learning model finds anomalous for that identity. It complements, not replaces, the deterministic rule, because a planned attacker action from a usual location may not look anomalous. See the Amazon GuardDuty Guide.
Step 6: Triage and contain a hit
- Find the approval: look for a change ticket or pipeline run that matches the caller, time and target. Ask the caller's owner, not the target user's owner.
- Check use of the new key:
aws iam get-access-key-last-used --access-key-id <new key>shows the last service and Region; search CloudTrail foruserIdentity.accessKeyIdequal to the new key. - Contain: if there is no approval, run
aws iam update-access-key --user-name <target> --access-key-id <new key> --status Inactive. Then examine the caller: a role session needs its source credentials found, an IAM user caller needs its own keys reviewed. - Scope: treat both the caller and the new key as compromised anchors and follow the Cloud Access Key Leak Playbook.
- Close: delete the key only after evidence is preserved and the owner of any affected integration has confirmed it is not in use.
Verify it worked
- Test in a sandbox account: from a test role, create a key for a test user. The event should appear in us-east-1 Event history within minutes, the EventBridge rule's invocation metric should increment, and the alert should arrive with the issuer role ARN and target user name filled in.
- Test the suppression: have the test user create its own key without
--user-name. No alert should fire. - Test the denial path: attempt creation from a principal without permission and confirm the Athena hunt returns the
AccessDeniedrow. - Clean up: deactivate and delete the test keys.
Rollback and common errors
- Rollback of containment:
update-access-key --status Activerestores a key that turned out to be legitimate. The secret was never changed, so the integration resumes without redeployment. - Rollback of the detection: disable the EventBridge rule rather than deleting it while you tune the allow-list.
- Rule in the wrong Region: the most common reason the alert never fires. IAM events are recorded in us-east-1.
- Trail missing global events: a single-Region trail outside us-east-1, or a trail with
IncludeGlobalServiceEventsfalse, misses IAM entirely. - Allow-listing whole roles: approving a provisioning role without constraining its targets lets an attacker who reaches that role create keys for anyone. Constrain by target path or tag in both the detection and the role's IAM policy.
- Ignoring management account and SCP blind spots: AWS states SCPs do not affect users or roles in the management account, so preventive SCPs cannot stand in for detection there.
- Two-key ceiling surprises: IAM allows at most two access keys per user. A cross-user creation that fails with
LimitExceededis still an attempted persistence action and belongs in the hunt.
Hardening afterwards
| Control | Setting | Effect |
|---|---|---|
| Self-service only for humans | Grant iam:CreateAccessKey on arn:aws:iam::*:user/${aws:username}, as in AWS's own self-management example policy | Users can rotate their own keys but not mint keys for others |
| Organisation guardrail | SCP denying iam:CreateAccessKey unless aws:PrincipalARN matches the approved provisioning role; test in a non-production OU first | Member accounts cannot create keys outside the pipeline; SCPs grant nothing and do not cover the management account |
| Fewer IAM users | Move people to IAM Identity Center and workloads to roles or OIDC federation | No IAM users means no target for this technique |
| Escalation path review | Find principals that hold iam:CreateAccessKey on * | Removes the escalation before it is used; see How to Find IAM Privilege Escalation Paths in AWS |
Small account or large organisation
A single account with a handful of IAM users can run the Event history hunt weekly and rely on the EventBridge rule with an email target. An organisation with hundreds of accounts should deploy the rule and Lambda through StackSets to every member account's us-east-1, forward to a central bus, keep the approved creator list in version control, and pair the detection with an SCP once the inventory of legitimate creators is complete. Applying the SCP first breaks unknown automation. See the AWS Organizations and SCPs Guide.
Metrics
- Cross-user key creations per month, split into approved and unapproved (unapproved should be zero after triage).
- Mean time from event to alert (target: minutes) and from alert to deactivation.
- Accounts with the rule deployed in us-east-1, as a percentage of all accounts (target 100).
- Principals holding
iam:CreateAccessKeyon resources other than themselves (trend down). - IAM users remaining (trend down, since each is a possible target).
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| MITRE ATT&CK | T1098.001 Additional Cloud Credentials; T1098 Account Manipulation; T1078.004 Cloud Accounts | The technique detected, its parent, and the use of the new key |
| NIST SP 800-53 Rev 5 | AC-2(4); AU-6; AU-12; SI-4; AC-6; IA-5 | Automated audit of account changes, log review, monitoring, least privilege over who can issue authenticators |
| NIST CSF 2.0 | DE.CM-03; DE.AE-02; PR.AA-05 | Monitoring identity activity, analysing adverse events, least privilege and separation of duties |
Where identity fits: the target of this technique is almost always a non-human identity, an IAM user that exists only to hold keys for an integration, because nobody notices an extra key on it. Owners, expected creators and key counts for these users belong in your NHI inventory. See MITRE ATT&CK T1098.001 Additional Cloud Credentials for the same technique across Entra ID and Google Cloud.
Practitioner checklist
- Confirm a multi-Region trail with global service events is logging.
- Hunt 90 days of
CreateAccessKeyin us-east-1 Event history and classify caller against target. - Hunt older history in Athena, including denied attempts,
UpdateAccessKeyandCreateLoginProfile. - Write down the approved key creators and the targets each may create keys for.
- Deploy the EventBridge rule in us-east-1 with a Lambda comparison and a separate root key rule.
- Test both the alert and the self-service suppression in a sandbox account.
- Contain unapproved keys by deactivation, preserve evidence, then delete.
- Restrict human key creation to
${aws:username}and add an SCP once creators are known.
Standards and references
- AWS: IAM API Reference, CreateAccessKey
- AWS: CloudTrail userIdentity element
- AWS: CloudTrail concepts, global service events
- AWS: EventBridge events delivered via CloudTrail
- AWS: EventBridge comparison operators
- AWS: Athena CloudTrail table with partition projection
- AWS: GuardDuty IAM finding types
- AWS: Service control policies
Related NHI Mgmt Group resources: AWS Access Keys Security Guide · How to Investigate CloudTrail for a Compromised Access Key · How to rotate an AWS IAM access key without downtime · How to find unused AWS IAM roles and access keys