New in Symfony 5.1: Server-side request forgery protection
Warning: This post is about an unsupported Symfony version. Some of this information may be out of date. Read the most recent Symfony Docs.
Contributed by
Hallison Boaventura
in #35566.
Security vulnerabilities such as CSRF (Cross-site request forgery) are well known by most web developers and Symfony provides automatic protection against them. A related but lesser known vulnerability is called SSRF (Server-side request forgery).
SSRF allows an attacker to induce the backend application to make HTTP requests to an arbitrary domain. These attacks can also target the internal hosts and IPs of the attacked server. The following simplified example has been extracted from this article, which explains the problem in detail:
Step 1: Your backend admin is freely accessible but only from internal IPs
(e.g. https://192.168.0.68/admin
).
Step 2: Your web application makes API requests like the following to get certain information (e.g. the stock of a product):
1 2 3 4 5
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
stockApi=https://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1
Step 3: The attacker can submit the following request to access to your backend admin:
1 2 3 4 5
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
stockApi=https://192.168.0.68/admin
The solution, as it happens with many security vulnerabilities, requires
filtering the user input (in this case, the IP address requested by the user).
In Symfony 5.1, we improved the HttpClient component to add a new
NoPrivateNetworkHttpClient
that blocks all internal IP addresses by default.
This new client decorates the default HttpClient, so you can use it as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\NoPrivateNetworkHttpClient;
$client = new NoPrivateNetworkHttpClient(HttpClient::create());
// nothing changes when requesting public networks
$client->request('GET', 'https://example.com/');
// however, all requests to private networks are now blocked by default
$client->request('GET', 'http://localhost/');
// the second optional argument defines the networks to block
// in this example, requests from 104.26.14.0 to 104.26.15.255 will result in an exception
// but all the other requests, including other internal networks, will be allowed
$client = new NoPrivateNetworkHttpClient(HttpClient::create(), ['104.26.14.0/23']);
Help the Symfony project!
As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.
Comments
Comments are closed.
To ensure that comments stay relevant, they are closed for old posts.
The application is already broken at this point, isn't it? You should never let the user input the URL which is used to communicate with. And even if you do, you should validate it.
This is equal to mysql_query("SELECT * FROM table WHERE id = {$_POST['id']}");
:-P
Input validation also doesn't work with lying DNS servers: imagine if validation decides it's safe because the DNS replies with a non-internal IP, but when the HTTP client does the DSN resolution the DNS replies with an internal IP? Such DNS servers exist into the wild. NoPrivateNetworkHttpClient also protects against this.
And I fully agree: if your API lets the user specify hostnames for your webserver to make http calls to, you f**d up real bad, and should probably first learn a bit about security, then review the whole app for other obvious, glaring mistakes and fix them.
In fact, I suspect that using the NoPrivateNetworkHttpClient will provide a false sense of security to novice developers, and hence make the situation worse :-(
What am I talking about? Simple: if the end user can decide what the target hostname is, then blacklisting private-network-ips is not enough, as it means that the server is still an open-relay that allows anyone to use it to make requests to the internet at large and obfuscate the source IP address. Helpful in covering up your tracks as well in setting up DOS attacks...
At the very least, as whitelist approach should be preferred to the blacklist approach.
"SELECT * FROM {$_POST['table_id']}"