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.
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.
| Variant | What the attacker reaches | Telemetry fingerprint | Best detection layer |
|---|---|---|---|
| Metadata theft | 169.254.169.254 / metadata.google.internal | App host → link-local metadata IP | Network + cloud control plane |
| Internal scanning | RFC 1918 ranges, 127.0.0.1 | App host → internal hosts/ports it never uses | Network egress |
| Blind SSRF | Attacker-controlled out-of-band host | Outbound DNS/HTTP to a freshly seen domain | DNS + 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.
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+.
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.
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
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 class | How it works | Why the denylist misses it |
|---|---|---|
| Alternate encodings | Decimal, octal, hex, or IPv6-mapped forms of the same address | The string does not match, the packet still arrives |
| DNS rebinding | Hostname resolves to a safe IP at validation, a link-local IP at fetch | Validation and fetch resolve separately (a TOCTOU gap) |
| Redirect following | Supplied URL is external and returns a 302 to an internal address | Only the first URL was ever checked |
| Parser differential | The validator and the HTTP client disagree about which part is the host | Both 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:
- 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. - Block link-local egress with a NetworkPolicy. Deny
169.254.0.0/16from application namespaces outright. Most workloads have no legitimate reason to reach it. - 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?
| Layer | Source | What it gives you | Common gap |
|---|---|---|---|
| Application | Outbound fetch logs from the app or egress proxy | The requested URL, pre-resolution | Almost never logged |
| DNS | Resolver query logs | Blind SSRF callbacks; rebinding attempts | Pods often bypass the central resolver |
| Network | VPC/CNI flow logs, IDS | Actual destinations reached, encoding-proof | Link-local traffic sometimes not captured |
| Cloud control plane | CloudTrail / Azure Activity / GCP audit | Credential use after theft — the highest-fidelity signal | Requires 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 positive | Which rule it hits | Why it happens | Resolution |
|---|---|---|---|
| Webhook and callback features | Web-tier URL rule | Users legitimately supply URLs | Detect on the resolved destination, not the parameter |
| Health checks against internal hosts | Internal-IP rule | Monitoring is supposed to do this | Allowlist the monitoring source and its targets |
| The cloud instance agent | Metadata egress rule | The agent legitimately reads metadata | Allowlist the agent process or its node-level source |
| Kubernetes CNI and cloud controllers | Metadata egress rule | Infrastructure components need metadata | Allowlist by namespace, never cluster-wide |
| Link preview and thumbnail services | Web-tier rule | Fetching arbitrary URLs is the feature | Route through the egress proxy; alert on proxy denials |
| Vulnerability scanners | All layers | They probe SSRF deliberately | Allowlist scanner sources; keep the rules enabled |
| CI jobs pulling dependencies | Internal-IP rule | Builds reach internal registries | Separate 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?
- 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.
- 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.
- 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.
- 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.
- 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.
- Find the vulnerable endpoint. Correlate the egress timestamp against application access logs to identify the request and parameter that triggered the fetch.
- Check for persistence created with the stolen identity — new access keys, new roles, modified trust policies.
- 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:
- In a throwaway lab account, stand up an app with a URL-fetch feature you control.
- Point it at
169.254.169.254from the app host and confirm the network rule fires. - Point it at an internal RFC 1918 host and confirm the scanning signal triggers.
- 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.
| Control | Stops credential theft? | Effort | Breaks anything? |
|---|---|---|---|
IMDSv2 required (HttpTokens=required) | Yes, for most SSRF primitives | Low | Only very old SDKs |
| IMDS hop limit = 1 | Yes, from containers specifically | Very low | Pods that legitimately read node metadata |
| Egress default-deny | Yes, and stops internal scanning too | Medium | Anything with undocumented outbound needs |
| Central egress proxy | Yes, plus gives you one detection point | Medium–high | Requires application changes |
| Resolve-then-validate in code | Yes, at the source | Per call site | Nothing, if done correctly |
| Least-privilege IAM | No — caps blast radius | Medium | Over-scoped legacy workloads |
| URL denylist | No | Low | Provides 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
HttpTokenstooptionalreopens 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 endpoint169.254.170.2are 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:
- Enforce IMDSv2 (
HttpTokens=required), disable IMDSv1, and audit for stragglers. - Allowlist outbound URLs and schemes; never rely on a denylist.
- Resolve, validate, then pin the IP before connecting — defeats DNS rebinding.
- Re-validate every redirect hop, or disable redirect-following on server-side fetchers.
- Default-deny egress on application subnets; allowlist required destinations.
- Alert on app hosts reaching
169.254.169.254, RFC 1918 ranges, or new external domains. - Alert on instance-role credentials used from outside the VPC (GuardDuty / CloudTrail).
- Scope IAM roles to least privilege to cap the blast radius of a stolen token.
- Cover GCP (
metadata.google.internal), Azure IMDS, and the ECS endpoint169.254.170.2. - Set the IMDS hop limit to 1 so containers cannot reach the node metadata endpoint.
- Block
169.254.0.0/16egress from application namespaces with a NetworkPolicy. - Use per-pod workload identity rather than node identity, so a theft is scoped to one workload.
- Route user-supplied fetches through a single egress proxy and alert on its denials.
- Log outbound fetch destinations — the layer almost nobody instruments.
- 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 TrainingStart 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.