Detection Engineering

SSRF Detection Without Exploit Code

SSRF detection without running exploits — metadata-access signatures, Sigma/Suricata/CloudTrail rules, IMDSv2 defense, a CVE-2025-53767 case, and tuning tips.

A network switch with one cyan ethernet cable isolated from the bundle and a red warning LED

SSRF detection does not require you to launch a single exploit. The attack leaves two clean fingerprints: outbound requests from your app to internal IPs it should never reach (especially the cloud metadata endpoint 169.254.169.254), and, in the cloud, instance-role credentials suddenly used from an external IP. Catch either and you catch server-side request forgery — without ever running a payload against your own systems.

SSRF sits at A10:2021 in the OWASP Top 10, and it is still escalating. In 2025, researchers disclosed CVE-2025-53767, an Azure OpenAI SSRF reported at CVSS 10.0 that allowed retrieval of managed-identity tokens. SSRF is how a web bug becomes a cloud-account compromise.

What is SSRF, in a defender’s terms?

Server-side request forgery turns your trusted server into the attacker’s proxy: it makes the internal requests an outsider cannot. From outside, an attacker cannot reach your internal network or your cloud metadata service. With SSRF, your own application makes those requests for them. The prize is usually credentials.

The canonical case is still the 2019 Capital One breach: an SSRF flaw reached the EC2 Instance Metadata Service and walked away with IAM role credentials. It maps to T1190 — Exploit Public-Facing Application for entry and T1552.005 — Unsecured Credentials: Cloud Instance Metadata API for the payoff.

What are the types of SSRF?

Three variants matter for detection, and each names a different destination — which is why the signal is so clean.

VariantWhat the attacker reachesTelemetry fingerprintBest detection layer
Metadata theft169.254.169.254 / metadata.google.internalApp host → link-local metadata IPNetwork + cloud control plane
Internal scanningRFC 1918 ranges, 127.0.0.1App host → internal hosts/ports it never usesNetwork egress
Blind SSRFAttacker-controlled out-of-band hostOutbound DNS/HTTP to a freshly seen domainDNS + egress logs

The common thread: SSRF detection lives in egress, not ingress. That makes it detectable without exploit code — you watch your own outbound traffic.

How to detect SSRF across web and cloud telemetry

Layer it from the triggering request to the credential abuse that follows.

Web tier: requests that name internal targets

The loudest, cheapest signal is a user-supplied URL parameter containing an internal or metadata address — the same web-log approach behind SQL injection detection.

Sigma Web Request Referencing Cloud Metadata or Internal IP
title: Web Request Referencing Cloud Metadata or Internal IP
id: 4f2b9c81-darkpwn-illustrative
status: experimental
logsource:
  category: webserver
detection:
  selection:
    cs-uri-query|contains:
      - '169.254.169.254'
      - 'metadata.google.internal'
      - '169.254.170.2'
      - '127.0.0.1'
      - '://10.'
      - '://192.168.'
  condition: selection
falsepositives:
  - Webhook/health-check features that accept internal URLs by design
level: high

Network tier: egress to the metadata address

Attackers bypass string filters with encodings (octal, decimal-dotted, hex). The reliable catch is on the wire: any connection from your app subnet to 169.254.169.254 that is not the instance agent itself. Reserve SID 1000003+.

Suricata Outbound Request to Cloud Metadata Service
alert http $HOME_NET any -> 169.254.169.254 any (
    msg:"DARKPWN Outbound request to cloud metadata service (possible SSRF)";
    flow:established,to_server;
    classtype:web-application-attack; sid:1000003; rev:1;)

Cloud control plane: credentials used from the wrong place

The highest-fidelity SSRF signal often appears after the request: the instance role’s credentials used from an IP that is not the instance. AWS GuardDuty raises UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration for exactly this; in CloudTrail it is an instance-profile session calling APIs from an external sourceIPAddress.

SPL Instance-Role Credentials Used From Outside the VPC
index=cloudtrail
| where match('userIdentity.arn', "assumed-role/.*i-")
| where NOT cidrmatch("10.0.0.0/8", sourceIPAddress)
    AND NOT cidrmatch("172.16.0.0/12", sourceIPAddress)
| stats count, values(eventName) by 'userIdentity.arn', sourceIPAddress

The same cloud detection in KQL

KQL Managed-Identity Token Used From an Unexpected Source
let corpRanges = dynamic(["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]);
AzureActivity
| where OperationNameValue has_any ("MICROSOFT.AUTHORIZATION", "LISTKEYS", "READ")
| where Caller has "managedIdentity" or identity_type_s == "ManagedIdentity"
| extend SrcIp = tostring(CallerIpAddress)
| where not(ipv4_is_in_any_range(SrcIp, corpRanges))
| summarize Ops = make_set(OperationNameValue), Count = count(),
            FirstSeen = min(TimeGenerated) by Caller, SrcIp
| order by Count desc

Why URL validation keeps failing

Most SSRF defences are a validation function, and most validation functions are bypassable for reasons that have nothing to do with the denylist being incomplete. Understanding the four failure classes is what lets you build a control that holds.

Bypass classHow it worksWhy the denylist misses it
Alternate encodingsDecimal, octal, hex, or IPv6-mapped forms of the same addressThe string does not match, the packet still arrives
DNS rebindingHostname resolves to a safe IP at validation, a link-local IP at fetchValidation and fetch resolve separately (a TOCTOU gap)
Redirect followingSupplied URL is external and returns a 302 to an internal addressOnly the first URL was ever checked
Parser differentialThe validator and the HTTP client disagree about which part is the hostBoth are “correct”; they implement different specs

How does SSRF work in containers and Kubernetes?

The metadata service is reachable from inside a pod by default, which means an SSRF in any containerized workload inherits the node’s cloud identity unless you have explicitly stopped it. This is a widespread and quiet exposure.

Three controls, in order of effectiveness:

  1. Set the IMDS hop limit to 1 (AWS HttpPutResponseHopLimit=1). A request originating in a container traverses an extra network hop to reach the node’s metadata endpoint, so a hop limit of 1 makes the metadata service unreachable from pods while leaving it available to the node itself. It is a one-line change with a large blast-radius reduction.
  2. Block link-local egress with a NetworkPolicy. Deny 169.254.0.0/16 from application namespaces outright. Most workloads have no legitimate reason to reach it.
  3. Use workload identity instead of node identity. Per-pod cloud credentials mean an SSRF that does reach metadata retrieves an identity scoped to that workload rather than to the whole node — turning a potential cluster-wide compromise into a contained one.

The related detection is straightforward once you have flow logs: any pod-to-169.254.169.254 connection outside your CNI and cloud agents. This is the same east-west visibility argued in zero trust microservices east-west detection, applied to one specific, very high-value destination.

What telemetry do you need to detect SSRF?

LayerSourceWhat it gives youCommon gap
ApplicationOutbound fetch logs from the app or egress proxyThe requested URL, pre-resolutionAlmost never logged
DNSResolver query logsBlind SSRF callbacks; rebinding attemptsPods often bypass the central resolver
NetworkVPC/CNI flow logs, IDSActual destinations reached, encoding-proofLink-local traffic sometimes not captured
Cloud control planeCloudTrail / Azure Activity / GCP auditCredential use after theft — the highest-fidelity signalRequires cross-account correlation

Prioritize the network and cloud control-plane layers. They are encoding-proof, because they observe what actually happened rather than what a string appeared to say, and the control-plane signal detects the outcome you actually care about even if every earlier layer was evaded.

Which false positives will you actually see?

False positiveWhich rule it hitsWhy it happensResolution
Webhook and callback featuresWeb-tier URL ruleUsers legitimately supply URLsDetect on the resolved destination, not the parameter
Health checks against internal hostsInternal-IP ruleMonitoring is supposed to do thisAllowlist the monitoring source and its targets
The cloud instance agentMetadata egress ruleThe agent legitimately reads metadataAllowlist the agent process or its node-level source
Kubernetes CNI and cloud controllersMetadata egress ruleInfrastructure components need metadataAllowlist by namespace, never cluster-wide
Link preview and thumbnail servicesWeb-tier ruleFetching arbitrary URLs is the featureRoute through the egress proxy; alert on proxy denials
Vulnerability scannersAll layersThey probe SSRF deliberatelyAllowlist scanner sources; keep the rules enabled
CI jobs pulling dependenciesInternal-IP ruleBuilds reach internal registriesSeparate tuning profile for build subnets

The first row is the important one architecturally. Features that legitimately fetch user-supplied URLs — webhooks, link previews, importers — are precisely where SSRF lives, so suppressing alerts on them removes detection from the highest-risk surface you have. Move those features behind the egress proxy instead, and the alert becomes “the proxy denied a fetch,” which is high-signal and safe to leave on.

How do you triage a suspected SSRF?

  1. Establish which layer fired. A web-parameter hit is an attempt; a metadata egress hit is a probable success; a cloud credential used off-instance is a confirmed breach. The response differs completely at each level.
  2. If credentials were used externally, declare an incident. Do not triage further first. Instance-role credentials calling APIs from a public IP means they left the host.
  3. Revoke the role session immediately. Attach a deny-all policy or revoke active sessions — rotating the instance profile alone does not invalidate credentials already issued.
  4. Enumerate what that role could reach. The role’s permissions define the blast radius, and this is usually the moment teams discover the role was far broader than anyone intended.
  5. Pull the control-plane history for the session. Every API call made with those credentials is recorded; read it rather than speculating about what was accessed.
  6. Find the vulnerable endpoint. Correlate the egress timestamp against application access logs to identify the request and parameter that triggered the fetch.
  7. Check for persistence created with the stolen identity — new access keys, new roles, modified trust policies.
  8. Fix the endpoint and enforce IMDSv2 with a hop limit, so the same SSRF cannot produce a credential next time.

Step 3 catches people out. Cloud credentials obtained from the metadata service remain valid for their session lifetime regardless of what you do to the instance profile afterwards, so explicit session revocation is the containment action — not instance termination.

How to test your SSRF detection safely

Validate the egress rules without attacking anyone:

  1. In a throwaway lab account, stand up an app with a URL-fetch feature you control.
  2. Point it at 169.254.169.254 from the app host and confirm the network rule fires.
  3. Point it at an internal RFC 1918 host and confirm the scanning signal triggers.
  4. Confirm IMDSv2 blocks the credential read even when the fetch succeeds — that’s the control validating itself.

How to defend against SSRF

Detection buys time. These controls remove the worst outcomes.

  • Allowlist outbound URLs, never denylist — denylists fall to encoding and redirect tricks (see the OWASP SSRF Prevention Cheat Sheet).
  • Resolve, validate, then pin the IP before connecting, to defeat DNS rebinding.
  • Least-privilege IAM roles cap the blast radius of a stolen token (NIST 800-53 AC-6).
  • Segment egress — app subnets rarely need arbitrary outbound; default-deny turns blind SSRF into a blocked, logged event.

Which control should you deploy first?

These are not alternatives, and the ordering is driven by effort-to-impact rather than by which is strongest.

ControlStops credential theft?EffortBreaks anything?
IMDSv2 required (HttpTokens=required)Yes, for most SSRF primitivesLowOnly very old SDKs
IMDS hop limit = 1Yes, from containers specificallyVery lowPods that legitimately read node metadata
Egress default-denyYes, and stops internal scanning tooMediumAnything with undocumented outbound needs
Central egress proxyYes, plus gives you one detection pointMedium–highRequires application changes
Resolve-then-validate in codeYes, at the sourcePer call siteNothing, if done correctly
Least-privilege IAMNo — caps blast radiusMediumOver-scoped legacy workloads
URL denylistNoLowProvides false confidence

Start with the top two rows: together they are a configuration change measured in minutes that removes the Capital One path from both instances and containers. Everything below them is real engineering work, and the last row is worth deploying only as a noisy signal — never as a control you rely on.

The honest sequencing for most teams: enforce IMDSv2 and the hop limit this week, add default-deny egress on application subnets next quarter, and treat the egress proxy as the target state that makes both the control and the detection live in one auditable place.

Common SSRF detection mistakes

  • IMDSv1 left enabled “temporarily.” One legacy workload flipping HttpTokens to optional reopens the Capital One path for the whole fleet.
  • Following redirects without re-validating. An allowlisted URL returns a 302 to 169.254.169.254; if your client follows it blindly, the allowlist is moot.
  • AWS-only coverage. GCP’s metadata.google.internal, Azure IMDS, and the ECS endpoint 169.254.170.2 are reachable by the same bug class.
  • Ingress-only monitoring. SSRF barely shows up at ingress; the signal is in egress and the cloud control plane.
  • Forgetting containers. Pods reach the node metadata endpoint by default; without a hop limit of 1 or a NetworkPolicy, every containerized SSRF inherits the node’s cloud identity.
  • Validating the hostname instead of the resolved IP. The second DNS resolution is the whole vulnerability in rebinding.
  • Suppressing alerts on webhook and link-preview features. Those are exactly the endpoints where SSRF lives; move them behind an egress proxy instead of muting them.
  • Terminating the instance and calling it contained. Issued credentials stay valid for their session lifetime; revoke the session explicitly.

SSRF detection and defense checklist

Work this list top to bottom:

  1. Enforce IMDSv2 (HttpTokens=required), disable IMDSv1, and audit for stragglers.
  2. Allowlist outbound URLs and schemes; never rely on a denylist.
  3. Resolve, validate, then pin the IP before connecting — defeats DNS rebinding.
  4. Re-validate every redirect hop, or disable redirect-following on server-side fetchers.
  5. Default-deny egress on application subnets; allowlist required destinations.
  6. Alert on app hosts reaching 169.254.169.254, RFC 1918 ranges, or new external domains.
  7. Alert on instance-role credentials used from outside the VPC (GuardDuty / CloudTrail).
  8. Scope IAM roles to least privilege to cap the blast radius of a stolen token.
  9. Cover GCP (metadata.google.internal), Azure IMDS, and the ECS endpoint 169.254.170.2.
  10. Set the IMDS hop limit to 1 so containers cannot reach the node metadata endpoint.
  11. Block 169.254.0.0/16 egress from application namespaces with a NetworkPolicy.
  12. Use per-pod workload identity rather than node identity, so a theft is scoped to one workload.
  13. Route user-supplied fetches through a single egress proxy and alert on its denials.
  14. Log outbound fetch destinations — the layer almost nobody instruments.
  15. Rehearse session revocation: rotating the instance profile does not invalidate credentials already issued.

Item 15 is the one to practise before you need it. Cloud credentials taken from the metadata service stay valid for their session lifetime no matter what you do to the instance afterwards, so explicit revocation — not termination — is the containment step, and discovering that during an incident costs hours you will not have.

Why does egress logging matter more than request logging here?

Most SSRF detection effort goes into inspecting inbound requests for suspicious URLs, which is the harder problem and the weaker signal. The attacker controls the input and has unlimited ways to encode it; you are pattern-matching against an adversary with a thesaurus.

The outbound side is far more tractable, because there you are matching against your own expected behaviour. A service that legitimately calls three known hosts should generate an alert the moment it calls a fourth — regardless of how the URL was encoded on the way in. That flips the problem from enumerating bad inputs to declaring good destinations, which is a finite list you control.

Practically that means default-deny egress with an allowlist per service, and an alert on every denial. The denial log is the detection, and it needs no signature at all.

The takeaway

SSRF is an egress problem with a credential payoff. Instrument what your servers connect to, enforce IMDSv2 so a stolen request cannot read a usable secret, and watch the cloud control plane for the credential that walked out the door — the same telemetry that CloudTrail monitoring patterns that matter turns into durable alerts. Continue across the API attack surface with GraphQL authorization mistakes to detect and broken access control testing, plus JWT misconfiguration detection, or browse the full Detection Engineering pillar.

Training & tools referenced

Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.

  • TryHackMeAuthorized labs to practice SSRF detection and cloud-metadata defenseSecurity Training
    Start training

Frequently asked questions

How do you detect SSRF without running exploits?

Watch your egress, not your ingress. Alert on outbound requests from app hosts to the metadata endpoint 169.254.169.254, to RFC 1918 internal ranges, or to freshly seen external domains from a URL field. In the cloud, alert when instance credentials get used from an IP outside your network.

Does IMDSv2 fully prevent SSRF?

No. IMDSv2 prevents the credential-theft outcome on AWS by requiring a session token and a custom header most SSRF primitives cannot send. The SSRF flaw still exists and can scan internal services, so pair IMDSv2 with allowlist URL validation and egress segmentation.

What is the AWS metadata IP address?

The EC2 Instance Metadata Service lives at the link-local address 169.254.169.254. The ECS task-role endpoint is 169.254.170.2, and GCP uses metadata.google.internal. None of these should appear in a legitimate user-supplied URL.

How do you stop SSRF from reaching metadata in Kubernetes?

Set the IMDS hop limit to 1 so a request originating inside a container cannot reach the node metadata endpoint, block 169.254.0.0/16 egress from application namespaces with a NetworkPolicy, and use per-pod workload identity instead of node identity so a successful theft is scoped to one workload rather than the whole node.

How does DNS rebinding bypass SSRF URL validation?

The hostname resolves to a harmless address when your validator checks it, then resolves to a link-local or internal address moments later when the HTTP client resolves it again to connect. The gap between those two resolutions is the vulnerability. Fix it by resolving once yourself, validating the resulting IP, and connecting to that IP directly — and repeat the check on every redirect hop.

Which OWASP category is SSRF?

Server-side request forgery is its own category, A10:2021 in the OWASP Top 10, added after it ranked first in the community survey.