SSRF in a server-side document import feature
An authorised security lab examining how a user-controlled URL can cause a web server to make unintended requests to internal or restricted destinations, followed by layered remediation and retesting.
Lab objective
This case study documents authorised SSRF training and remediation work involving a web application feature that accepts a URL and retrieves the referenced resource from the server.
The objective was to understand:
- why server-side URL fetching creates a security boundary
- how an attacker can influence the destination
- why normal URL validation is insufficient
- how internal network resources can become reachable
- common weaknesses in hostname allowlists
- how redirects and DNS resolution affect validation
- how SSRF should be remediated
- how the remediation should be retested
The work was performed in authorised training environments and personal lab scenarios rather than against an unauthorised production system.
Vulnerable functionality
Consider a document-import feature:
POST /documents/import
source_url=https://documents.example.com/invoice.pdf
The application receives the URL and retrieves the document server-side.
A simplified vulnerable PHP implementation might look like:
$url = $_POST['source_url'];
$content = file_get_contents($url);
The business functionality is legitimate.
The security problem is that the user controls where the server makes the request.
Trust boundary
This creates an important trust-boundary change.
Normally:
USER
|
v
WEB APPLICATION
With server-side URL fetching:
USER
|
| controls destination
v
WEB APPLICATION
|
| server makes request
v
DESTINATION
The outbound request originates from the application server rather than from the attacker's computer.
That matters because the server may have access to systems and network locations that an external user cannot reach directly.
What is SSRF?
Server-Side Request Forgery occurs when an attacker can influence a server-side request and cause the application to communicate with an unintended destination.
The vulnerable application effectively becomes a request proxy.
Depending on the environment, this could expose access to:
- localhost services
- internal APIs
- private network hosts
- administrative interfaces
- cloud-related internal services
- services protected from direct internet access
The exact impact depends on what the application server can reach.
Initial test
The first question during testing is:
Does the application actually make the request from the server?
A normal external URL establishes the expected behaviour.
For example:
source_url=https://example.com/document.pdf
If the server retrieves the resource, the next question is whether the destination is restricted.
Internal destination testing
In an authorised lab, the destination can then be changed to a loopback or controlled internal address.
Conceptually:
source_url=http://127.0.0.1/
If the application server attempts that connection, user-controlled input has crossed into a network location that should not normally be available to the user.
The purpose of the test is not simply to obtain a successful HTTP response.
Different:
- status codes
- response lengths
- error messages
- connection behaviour
- timing differences
may also reveal whether the server attempted to contact the destination.
Root cause
The root cause is trusting a user-controlled destination in a server-side network request.
A common mistake is validating only that the input looks like a URL:
if (!filter_var($url, FILTER_VALIDATE_URL)) {
exit('Invalid URL');
}
This validates syntax.
It does not answer the security question:
Is the server permitted to communicate with this destination?
A syntactically valid URL can still point to an unsafe destination.
Why hostname checks can fail
A weak defence might look like:
if (str_contains($url, 'documents.example.com')) {
fetch($url);
}
This is unsafe because URLs have structure.
They can contain:
- schemes
- usernames
- passwords
- hostnames
- ports
- paths
- query strings
- fragments
Security decisions should therefore be made using a proper URL parser rather than substring matching.
For example:
$parts = parse_url($url);
$scheme = $parts['scheme'] ?? '';
$host = $parts['host'] ?? '';
The application can then validate the actual parsed scheme and hostname.
Allowlisting
If the application only needs to retrieve documents from known suppliers or services, an exact allowlist is safer than accepting arbitrary internet destinations.
For example:
$allowedHosts = [
'documents.example.com',
'files.example.com',
];
if (!in_array($host, $allowedHosts, true)) {
throw new RuntimeException('Destination not permitted.');
}
The comparison should be made against the parsed hostname.
An allowlist should not rely on the approved hostname merely appearing somewhere inside the URL.
Scheme validation
The application should also explicitly restrict protocols.
For a normal web document-import feature:
if (!in_array($scheme, ['https'], true)) {
throw new RuntimeException('Scheme not permitted.');
}
The application should not automatically support additional schemes simply because the underlying networking library does.
Only protocols required by the business functionality should be enabled.
DNS resolution
Hostname validation alone is not necessarily sufficient.
A hostname eventually resolves to an IP address.
Therefore, the application should consider where the approved hostname resolves before making the outbound connection.
Conceptually:
USER URL
|
v
PARSE URL
|
v
CHECK SCHEME
|
v
CHECK HOSTNAME
|
v
RESOLVE DNS
|
v
CHECK RESOLVED IP
|
v
MAKE REQUEST
This is important because an apparently external hostname may resolve to an internal or otherwise prohibited address.
Private and special-use destinations
The application should prevent outbound requests to network ranges that are not valid destinations for the feature.
Examples commonly considered in SSRF defence include:
127.0.0.0/8 loopback
10.0.0.0/8 private
172.16.0.0/12 private
192.168.0.0/16 private
169.254.0.0/16 link-local
IPv6 must also be considered.
The defensive principle is more important than memorising individual ranges:
Resolve the destination and determine whether the resulting address belongs to a network the application is permitted to contact.
PHP validation example
A simplified defensive check might include:
$ip = gethostbyname($host);
if (!filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
)) {
throw new RuntimeException('Destination not permitted.');
}
This illustrates the principle, but a production implementation requires more care.
For example:
- IPv6 must be handled correctly.
- A hostname may resolve to multiple addresses.
- DNS results may change.
- Redirects may introduce a new destination.
A single gethostbyname() check should therefore not be treated as a complete SSRF defence.
DNS rebinding
Another consideration is the time between validation and connection.
Conceptually:
VALIDATION
documents.example -> public IP
time passes
CONNECTION
documents.example -> internal IP
If the application validates one DNS result but the HTTP client later resolves the hostname independently, the destination may not necessarily remain the same.
This is one reason SSRF defence requires careful handling of DNS resolution rather than only checking the hostname string.
Redirects
Redirects create another important problem.
Suppose the initial destination is approved:
https://documents.example.com/file
but it responds:
302 Location: http://127.0.0.1/admin
If the HTTP client automatically follows the redirect without revalidating the new destination, the original allowlist can be bypassed.
A secure design should either:
- disable redirects where they are unnecessary, or
- validate every redirect destination using the same security rules before following it.
Ports
The destination port should also match the business requirement.
If the application only needs HTTPS document retrieval, there is little reason to permit arbitrary ports.
Restricting ports reduces the number of internal services that could potentially be reached if another control fails.
Network-layer defence
Application validation should not be the only protection.
Where infrastructure permits it, outbound network access from the application should also be restricted.
This creates defence in depth:
USER INPUT
|
v
APPLICATION VALIDATION
|
v
DNS / IP VALIDATION
|
v
REDIRECT VALIDATION
|
v
NETWORK EGRESS CONTROL
|
v
APPROVED DESTINATION
If an application-layer validation bug is introduced later, network controls can still reduce the destinations reachable by the application server.
Error handling
Detailed networking errors can provide useful information to an attacker.
For example, different messages for:
Connection refused
Connection timed out
Host not found
HTTP 401
HTTP 200
can help map internal services.
The public application should therefore avoid unnecessarily exposing detailed upstream network errors.
Operational detail can be retained in appropriate server-side logs.
Resource controls
A server-side fetch feature can also create availability risks.
The application should consider limits such as:
- connection timeout
- overall request timeout
- maximum response size
- maximum redirects
- permitted content types where appropriate
Without limits, an attacker may be able to make the server download extremely large responses or maintain expensive outbound connections.
Remediation strategy
A layered SSRF defence for a document-import feature can therefore include:
- Avoid server-side URL fetching entirely if it is not required.
- Use an exact allowlist when destinations are known.
- Parse URLs with a proper URL parser.
- Restrict permitted schemes.
- Restrict permitted ports.
- Resolve the hostname.
- Reject prohibited IP ranges.
- Handle IPv4 and IPv6.
- Consider multiple DNS results.
- Prevent validation/connect DNS inconsistencies.
- Disable redirects or validate every redirect destination.
- Apply outbound network restrictions where possible.
- Limit connection time and response size.
- Return generic public errors.
- Log blocked attempts without exposing sensitive data.
Retesting
Remediation is not complete until the original vulnerability and likely bypasses are tested again.
Legitimate request
Test an approved external document source.
Expected result:
200 / successful import
The business feature should continue working.
Loopback destination
Attempt a loopback destination.
Expected result:
Rejected
No outbound request to loopback service
Private network destination
Attempt a private network address.
Expected result:
Rejected
No internal connection
Link-local destination
Attempt a link-local destination.
Expected result:
Rejected
Unapproved hostname
Use an external hostname not present on the allowlist.
Expected result:
Rejected
URL parsing tricks
Test malformed or misleading URL structures to confirm the application is validating the parsed hostname rather than performing substring matching.
Expected result:
Rejected
Redirect
Use an approved destination that attempts to redirect to a prohibited destination in the authorised lab.
Expected result:
Redirect blocked or destination revalidated
No prohibited connection
DNS resolution
Verify that a hostname resolving to a prohibited address is rejected.
Expected result:
Rejected before sensitive request is made
Verification criterion
A blocked SSRF attempt should not merely display an error in the browser.
The important verification is:
Did the application server actually make the prohibited outbound request?
Where possible in a controlled lab, server or destination logs should confirm that blocked requests produced no connection to the prohibited service.
Security lesson
The central SSRF lesson is:
URL validation is not destination validation.
A secure server-side fetch feature needs to understand the destination after parsing and DNS resolution and must continue enforcing that decision through redirects and the actual outbound connection.
Development lesson
SSRF is also a useful example of why secure development requires thinking beyond individual lines of code.
A function that downloads a URL may be only a few lines long, but its security depends on:
- application input validation
- URL parsing
- DNS
- IP addressing
- HTTP redirects
- network architecture
- error handling
- resource limits
That is why server-side network functionality should be treated as a security-sensitive feature rather than as a simple convenience function.
Authorisation
This work was carried out under written authorisation or inside an authorised training environment. All evidence has been sanitised.
Continue reading
Related case studies
IDOR: missing server-side authorisation in an invoice workflow
An authenticated user could modify an invoice identifier and attempt to access another customer's resource. The remediation enforced ownership at the database query layer and was verified with positive and negative authorisation tests.
5 min read
Secure payment webhooks: validating PayPal, Stripe and Flutterwave callbacks
A secure payment integration case study covering server-side verification, webhook authenticity, payment-state validation, duplicate processing, secrets management and safe failure handling.
8 min read
XXE in a legacy XML import parser
A supplier feed parser resolved external entities, exposing server-side file contents. Remediated by disabling entity loading, switching to a safe parser configuration and validating against a schema.
2 min read