A feature that fetches a URL supplied by a user can seem harmless: preview a link, import an image, or retrieve a webhook response. But the request comes from your server, which may be able to reach systems and services that the user cannot. If an attacker can influence where that request goes, the feature may have a server-side request forgery (SSRF) vulnerability.
This guide explains how SSRF works, why URL checks can fail, and how to design a safer URL-fetching service. It uses a fictional lab scenario and focuses on defensive controls—not testing real systems.
Key takeaways
- SSRF occurs when an attacker can influence a server-side request to an unintended destination.
- A URL parser or hostname check is not, by itself, an SSRF defense.
- Prefer a strict allowlist of destinations. Also validate resolved IP addresses and enforce network-level egress restrictions.
- Prevent automatic redirects, or validate every redirect destination before following it.
- Protect cloud metadata services and credentials with infrastructure controls as well as application checks.
What is an SSRF vulnerability?
A server-side request forgery vulnerability exists when an application makes a network request based on user-controlled input without adequately restricting the destination. The attacker does not necessarily receive a direct connection to the target. Depending on the feature, they might instead infer information from the response, cause a server-side action, or access data through the application.
The risk comes from the server’s network position and permissions. A backend may be able to contact internal services, administrative interfaces, or cloud infrastructure endpoints that are not reachable from the public internet. A URL that looks ordinary to the application can therefore direct the server somewhere sensitive.
Consider a fictional image-preview service in an isolated training lab. It accepts a URL and downloads the image to create a preview. If it accepts arbitrary destinations, a user may be able to make the service request a location outside the intended set of image providers. This example describes a design risk; it is not a request to probe any real system.
How URL fetching creates risk
The vulnerable pattern is not simply “the app accepts a URL.” The problem is that user input controls a network request, while the application fails to constrain where that request can go and what it can access.
Potential consequences include:
- Access to internal services: The server may be able to reach private network addresses or services that have no public-facing access.
- Exposure of cloud credentials: If a workload can reach a cloud metadata service, a poorly isolated fetcher might retrieve information used to obtain credentials or configuration.
- Unexpected actions: Some internal endpoints perform actions when called. A server-side request can sometimes reach them even when an external user cannot.
- Information disclosure: The application may reveal response content, status codes, timing, or other signals that help an attacker learn about the server’s environment.
Impact depends on the application, network, runtime permissions, and how responses are handled. SSRF does not automatically mean that credentials or internal data will be exposed, but it can create a path to those assets.
Why common URL checks fail
A URL parser can separate a URL into components such as scheme, hostname, and path. That is useful for inspection, but parsing does not determine whether a destination is safe. You can inspect URL components to understand how a URL is structured; the security decision still belongs in the server-side fetch design.
Blocking private IP addresses alone is not enough
Rejecting private or loopback IP addresses is an important control, but hostname validation must account for DNS resolution. A hostname can resolve to an address that should not be reachable, and the answer can change over time. A check performed before the request is ineffective if the HTTP client later resolves the name again and connects to a different address.
OWASP’s SSRF guidance recommends layered controls, including allowlisting trusted destinations where feasible and validating resolved addresses. A robust design also constrains the actual network connection, rather than relying only on a preliminary check.
DNS rebinding can create a time-of-check/time-of-use gap
With DNS rebinding, a hostname can return one address when the application validates it and a different address when the HTTP client connects. If the application checks a public address but the connection resolves to a restricted address later, the check has not protected the connection.
Mitigations include resolving the hostname through a controlled resolver, checking every returned address against policy, and ensuring the connection uses an address that was actually validated. The transport must preserve the correct hostname for TLS certificate verification and SNI while connecting to the validated address. This is a subtle networking detail: a custom resolver or HTTP transport should be reviewed and tested by someone familiar with the client library.
Redirects need their own validation
A URL that initially passes validation may return a redirect to a different host or address. If the HTTP client follows redirects automatically, the request can leave the approved destination set.
The simplest safe policy is to disable redirects. If redirects are necessary, inspect each Location target, apply the complete destination policy again, and limit the number of redirects. Do not assume that validating only the first URL makes the redirect chain safe.
Cloud metadata risks
Cloud platforms may provide workload metadata through special network endpoints. Depending on the environment and configuration, metadata can contain information about the instance or workload and may enable access to credentials. A server-side fetch feature that can reach those endpoints can turn an SSRF flaw into a broader cloud security issue.
Do not rely on the application’s URL checks as the only barrier. Apply the cloud provider’s recommended metadata protections, restrict workload permissions to the minimum required, and use network controls to prevent application components from reaching metadata services unless they need to. The exact protection depends on the cloud platform and workload configuration, so verify it against the relevant provider documentation.
A safer URL-fetching design
Start by asking whether the feature needs to fetch arbitrary URLs at all. If it only retrieves images from a small set of partners, allowlist those partner hostnames rather than accepting the entire internet. If arbitrary destinations are essential, treat the fetcher as a high-risk network service and isolate it accordingly.
A defensive design should include these layers:
- Restrict schemes and ports. Usually permit HTTPS only and the expected port. Reject URL user information, unsupported schemes, and malformed input.
- Use an exact hostname allowlist where possible. Compare canonicalized hostnames against known entries. Avoid loose suffix checks that may accept a different hostname than intended.
- Resolve and validate every address. Reject destinations that fall outside your permitted address policy, including private, loopback, link-local, and other restricted ranges relevant to your environment. Apply policy to all returned addresses, not just one.
- Connect to the validated destination. Prevent a second, uncontrolled DNS lookup between validation and connection. Preserve correct TLS hostname verification.
- Control redirects. Disable them or revalidate every redirect before following it.
- Constrain the fetcher at the network layer. Use egress rules, a controlled outbound proxy, or an isolated worker to limit reachable destinations. Application checks and network restrictions complement each other.
- Limit resource use. Set connection and read timeouts, response-size limits, and redirect limits. Restrict response types if the feature only needs specific formats. These controls reduce abuse and resource exhaustion but do not replace destination validation.
- Minimize what the response can reveal. Avoid returning raw internal error details or arbitrary fetched content to users. Log security-relevant outcomes without recording sensitive data unnecessarily.
Illustrative validation gate
The following Python example shows a narrow policy check, not a complete safe fetcher. It uses a fictional, reserved .example hostname. The caller must provide DNS answers from a controlled resolution step. A production implementation must ensure the HTTP transport connects to a validated address without resolving the hostname again, verify TLS for the hostname, and apply network-level egress controls.
import ipaddress
from urllib.parse import urlsplit
ALLOWED_HOSTS = {"images.partner.example"}
def validate_candidate_url(url: str, resolved_ips: list[str]) -> tuple[str, list[str]]:
try:
parts = urlsplit(url)
host = parts.hostname
port = parts.port
except ValueError as exc:
raise ValueError("Malformed URL") from exc
if parts.scheme.lower() != "https":
raise ValueError("Only HTTPS URLs are allowed")
if not host or parts.username is not None or parts.password is not None:
raise ValueError("A valid hostname without user information is required")
if port not in (None, 443):
raise ValueError("The requested port is not allowed")
try:
canonical_host = host.rstrip(".").encode("idna").decode("ascii").lower()
except UnicodeError as exc:
raise ValueError("Invalid hostname") from exc
if canonical_host not in ALLOWED_HOSTS:
raise ValueError("Hostname is not approved")
if not resolved_ips:
raise ValueError("Hostname did not resolve")
checked_ips = []
for value in resolved_ips:
try:
address = ipaddress.ip_address(value)
except ValueError as exc:
raise ValueError("Invalid DNS address") from exc
if not address.is_global:
raise ValueError("Destination address is not globally routable")
checked_ips.append(str(address))
return canonical_host, checked_ips
ipaddress.is_global is only one part of an address policy; organizations may need additional explicit deny ranges and network-specific rules. More importantly, this function does not make a subsequent request safe by itself. If the HTTP client performs a fresh DNS lookup, follows a redirect, or can reach restricted networks, the SSRF risk remains. Use a transport and network architecture designed to enforce the validated destination.
Fictional lab workflow: review a preview service
Use this workflow in a local, isolated lab or during an authorized review of your own application:
- Trace the input. Find where the submitted URL enters the application and follow it to the HTTP client call.
- Identify the intended destinations. Write down the hosts and schemes the product genuinely needs. If there is no legitimate need for arbitrary hosts, replace that behavior with an allowlist.
- Review every request path. Check DNS resolution, redirects, retries, proxy behavior, and any URL transformations. Confirm that each new destination is subject to policy.
- Inspect the network boundary. Verify that the fetcher cannot reach internal services or metadata endpoints merely because application validation fails.
- Test safely. Use mock DNS, a local test server, and controlled test addresses in an isolated environment. Confirm that disallowed destinations are rejected without contacting external or production systems.
- Check observability. Ensure rejected requests produce useful security logs, while responses and logs do not expose sensitive fetched content.
A review should verify the behavior of the actual HTTP client and deployment environment. Passing unit tests for a URL-validation function alone is not evidence that the network connection is constrained.
Common mistakes
- Treating parsing as protection: A parser identifies URL components; it does not decide whether the server should connect to them.
- Allowing any hostname that ends with a trusted string: Loose suffix checks can match unintended hosts. Prefer canonicalized, exact allowlist entries.
- Checking DNS only once: A later lookup can differ. Ensure the connection uses a validated address.
- Validating only the original URL: Redirects can introduce a new destination and must be disabled or checked individually.
- Assuming a public address is always safe: Address policy must reflect your network and must be enforced at connection time as well as through egress controls.
- Relying only on metadata defenses: Metadata protections reduce impact, but they do not fix an unrestricted server-side fetch capability.
- Returning fetched responses without limits: Large or unexpected responses can create availability and data-exposure risks even when the destination is approved.
Conclusion
SSRF is a trust-boundary problem: the user influences a request made from a server with its own network access and permissions. Safer URL fetching requires more than checking a string. Use strict destination policies, validate DNS results, bind validation to the actual connection, control redirects, restrict egress, and reduce the fetcher’s privileges. When arbitrary URL fetching is not essential, removing that capability is often the clearest way to reduce risk.
