TA AzeezCode

Case studies

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.

high severity SSRF Web Security PHP Input Validation 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:

  1. Avoid server-side URL fetching entirely if it is not required.
  2. Use an exact allowlist when destinations are known.
  3. Parse URLs with a proper URL parser.
  4. Restrict permitted schemes.
  5. Restrict permitted ports.
  6. Resolve the hostname.
  7. Reject prohibited IP ranges.
  8. Handle IPv4 and IPv6.
  9. Consider multiple DNS results.
  10. Prevent validation/connect DNS inconsistencies.
  11. Disable redirects or validate every redirect destination.
  12. Apply outbound network restrictions where possible.
  13. Limit connection time and response size.
  14. Return generic public errors.
  15. 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

high severity Access control

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.

IDOR Broken Access Control Authorisation PHP

5 min read

high severity XXE

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.

XXE Secure parsing Legacy code Retested

2 min read