What is Server-Side Request Forgery?
Also calledSSRFServer Side Request Forgery
Definition
Server-side request forgery is a vulnerability in which an attacker causes a server to make an HTTP request to a destination the attacker chooses, using the server as the origin of that request instead of their own machine.
What server-side request forgery is
An application takes a URL, or something that becomes a URL, from a user and fetches it. A webhook tester, a document converter that resolves images, a link preview generator, a health check that pings a customer endpoint: all of them are the same shape. The vulnerability is not that the server makes a request. It is that the attacker decides where the request goes, and the request arrives carrying the server identity rather than the attacker identity.
That identity swap is the whole of the impact. The server sits inside a network the attacker cannot reach, and it usually holds credentials the attacker does not have: an instance metadata endpoint, an internal admin panel with no authentication because it is not exposed, a database HTTP interface, a service mesh sidecar. The attacker does not gain code execution. They gain the network position and the ambient authority of the process making the request.
Blind and non-blind variants
If the response body comes back to you, the vulnerability is easy to demonstrate and easy to triage. Most real instances do not behave that way. The server fetches something and returns a status, a thumbnail, or nothing at all. That is a blind SSRF, and the practical difference is that you have to move from reading responses to observing behaviour: an out-of-band collaborator that records DNS lookups and inbound connections, response timing differences, and error messages that differ between a host that resolved and one that did not.
The distinction matters commercially as well as technically. A blind SSRF with no demonstrated internal reach is the finding most often closed as informational, and the difference between a closed report and a paid one is usually whether the tester was able to show that the request landed somewhere that mattered.
Why the usual filters fail
The common defence is a deny-list of internal address ranges, applied to the hostname in the submitted URL. It fails for a reason that is structural rather than a matter of the list being incomplete: the check happens against a string, and the connection happens against an address the resolver produces later. Anything that changes between those two moments defeats the check. A hostname under the attacker control that resolves to an internal address needs no trick at all, and a resolver that answers differently on the second lookup than on the first defeats a check that resolves once and connects again.
Redirect following is the other reliable bypass. An allow-listed host that returns a 302 to an internal address hands the attacker the destination if the fetching client follows redirects, which almost every HTTP client does by default.
What actually fixes it
- Allow-list destinations rather than deny-listing them. A deny-list of internal ranges is a list of the things you thought of.
- Resolve the hostname yourself, validate the resolved address, and then connect to that address. Validating a string and connecting to a name is the gap every bypass walks through.
- Disable redirect following on the fetching client, or re-validate the destination on every hop.
- Send outbound fetches through a dedicated egress proxy with its own network policy, so the application host is not the thing making the decision.
- Remove ambient credentials from the fetch path. On a cloud instance that means requiring a signed token for metadata access rather than relying on the network boundary.
What it looks like in practice
POST /api/v2/documents/render HTTP/1.1
Host: app.example.com
Content-Type: application/json
Authorization: Bearer <your own session token>
{
"template": "invoice",
"assetUrl": "http://probe.your-collaborator.example/marker-01"
}
# What you are looking for, in order:
# 1. a DNS lookup for probe.your-collaborator.example
# 2. an inbound HTTP request carrying a server-side User-Agent
# 3. the source address of that request
#
# A lookup with no connection is still a result. It means something on the
# path resolved the name and then declined, which tells you where the
# control lives. Record it and keep going.What it is not
The distinction matters more than the definition does, because these are the terms people swap by accident in a report.
5 cards
Tap a card to reveal the answer. Answers are in the page source, so this works with JavaScript disabled.
In one sentence, what does SSRF let an attacker do?
Make the server issue a request to a destination the attacker chooses, using the server network position and identity instead of their own.
What makes an SSRF blind, and what changes about testing it?
The response never reaches the attacker. Testing shifts from reading response bodies to observing behaviour: out-of-band DNS and HTTP callbacks, timing differences, and differing error messages.
Why does deny-listing internal IP ranges in the submitted URL fail?
The check runs against a hostname string and the connection runs against an address resolved later. Anything that changes between those two steps, including an attacker-controlled name that resolves internally, defeats it.
What single control most reliably reduces SSRF impact?
Egress network policy on the workload. It survives application refactors and leaves a full SSRF with no reachable internal target.
Why is redirect following a problem for SSRF defences?
An allow-listed host can return a 302 to an internal destination, and most HTTP clients follow redirects by default, so the validated destination is not the destination that is contacted.
Where people get this wrong
The place learners lose this one is the moment the collaborator shows a DNS lookup and no HTTP request. Almost everyone reads that as a negative result and stops. It is the most informative single observation in the whole class: it proves something resolved the name, which means the request path exists and a control interrupted it. Write it down, then go and find out which hop resolved it. Half the blind SSRF findings that get paid start exactly there.
Where you can use this
The G1 gate for this template: the artifacts below exist nowhere else in this combination.
Cyber VK Security Curriculum
Pre-publication content owner
Internal ownership label for the web-security curriculum.
Technically reviewed by Cyber VK Defensive Research
Updated
[ Related ]
- LabBlind SSRF to cloud metadataProve that a server-side request forgery with no visible response can still reach an internal service, and write the finding so a triager cannot close it as informational.
- ArticleWhy your SSRF report gets closed as informationalMost blind SSRF reports are closed without a payout, and usually for the same three reasons. Here is what a triager is actually looking for, and how to supply it without touching anything you should not.
- GlossaryCross-Site Request ForgeryCross-site request forgery is a vulnerability in which a site an attacker controls causes a victim browser to send a state-changing request to a different site where the victim is already authenticated, using credentials the browser attaches automatically.
- GlossaryInsecure Direct Object ReferenceAn insecure direct object reference is an access control failure in which an application uses a user-supplied identifier to select a record and does not check that the requesting user is allowed to see that particular record.
- CTF roomLedger DriftA small internal accounting application with a self-service export feature and an authorisation model that was written before the tenanting was.
- LabIDOR in a multi-tenant invoice APIFind an object-level authorisation failure in a tenanted API and demonstrate cross-tenant access without touching another real customer.