Hallison Boaventura
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']);
Published in #Living on the edge