# Server-Side Request Forgery
A **server-side request forgery** (SSRF) attack is when an attacker crafts a malicious HTTP request that triggers a further request from your server to a domain of their choosing. SSRF vulnerabilities can be used to probe your network or to disguise denial-of-service attacks against third parties.
## Common Causes of SSRF
There are many reasons your web-server might make outgoing HTTP requests, including:
* Calling a third-party API in response to a user action.
* Communicating with a *Single Sign-On* (SSO) provider.
* Implementing an image upload functions that accept URLs instead of files.
* Checking validation URLs – for example, hosted schema files referenced in XML documents.
* Accessing open-graph meta-data used in generating link previews.
In some of these scenarios, the domain of the URL will be taken from the HTTP request. This allows an attacker to trigger HTTP requests to arbitrary domains. Malicious users will try to use this in denial-of-service attacks against other targets (for which you will get blamed), and to probe internal IP addresses on your network that are not intended to be public.
## Mitigation
The easiest way to mitigate SSRF vulnerabilities is to never make outgoing HTTP requests to domain names drawn from an incoming HTTP request. If you call the Google Maps API from your web-server, for instance, the domain of the API should be defined in server-side code, rather than pulled from the client. An easy way to do this is to use the Google Maps SDK:
“`python import googlemaps from datetime import datetimegmaps = googlemaps.Client(key=GOOGLE_MAPS_API_KEY) directions = gmaps.directions(“Sydney Town Hall”, |
## Validation URLs
XML documents often reference schema files hosted on remote URLs. Generally speaking, however, you should know how to validate an uploaded XML file ahead of time. If you perform validation of XML documents on your server, make sure it is against a schema file stored locally, rather than drawn from an uploaded XML file that could be controlled by an attacker:
“`python from lxml import etreedef is_validate(xml_file, xsd_file): “””Validate an XML file at the supplied path against an XSD schema file, disabling external network access.””” parser = etree.XMLParser(resolve_entities=False, no_network=True) xml = etree.parse(xml_file, parser) xsd = etree.parse(xsd_file, parser) schema = etree.XMLSchema(xsd.getroot()) return schema.validate(xml) |
## Making Outbound HTTP Requests to Arbitrary URLs
Some websites do need to make requests to arbitrary third-party URLs. Social media sites, for example, allow sharing of web links, and will often pull down the open graph meta-data from those URLs to generate link previews. In these cases, you need to protect yourself against SSRF attacks. This means you should:
* Only make outgoing HTTP requests from your server in response to actions by *authenticated* users.
* Limit the number of links a user can share in a given time-frame, to avoid abuse.
* Consider making the user pass a CAPTCHA test with each link they share.
* Keep a configurable “blocklist” of domains you will never contact.
* Talk to your networking team about limiting which internal servers are reachable from your web-servers.
* Validate that the URLs contain web domains rather than IP addresses.
Note that a competent attacker will be able to set up DNS records pointing to private IPs, so simply validating a URL contains a domain name (rather than an IP address) generally isn’t sufficient.
Below is an illustration of some of these techniques:
“`python import re import validatorsfrom flask import Flask from flask_limiter import Limiter from flask_limiter.util import get_remote_address from IPy import IP from opengraph import OpenGraph from urllib.parse import urlparse app = Flask() limiter = Limiter( @app.route(‘/share/<url>’) # Add a protocol if not supplied. # Reject invalid URLs or those containing private IP addresses. components = urlparse(link) # Reject URLs with non-standard protocols. # Reject URLs with non-standard ports. # Reject URLs containing IP addresses rather than domains. # Reject URLs where the domain is in our blocklist. # Everything looks good, go grab the meta-data. |
## CWEs
* [CWE-918](https://cwe.mitre.org/data/definitions/918.html)