RCE in Symfony
Description
In Symfony before versions 4.4.13 and 5.1.5, the CachingHttpClient class from the HttpClient Symfony component relies on the HttpCache class to handle requests. HttpCache uses internal headers like X-Body-Eval and X-Body-File to control the restoration of cached responses. The class was initially written with surrogate caching and ESI support in mind (all HTTP calls come from a trusted backend in that scenario). But when used by CachingHttpClient and if an attacker can control the response for a request being made by the CachingHttpClient, remote code execution is possible. This has been fixed in versions 4.4.13 and 5.1.5.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
symfony/http-kernelPackagist | >= 4.3.0, < 4.4.13 | 4.4.13 |
symfony/http-kernelPackagist | >= 5.0.0, < 5.1.5 | 5.1.5 |
symfony/symfonyPackagist | >= 4.3.0, < 4.4.13 | 4.4.13 |
symfony/symfonyPackagist | >= 5.0.0, < 5.1.5 | 5.1.5 |
Affected products
1- Range: >= 4.4.0, < 4.4.13
Patches
1d9910e0b33a2security #cve-2020-15094 Remove headers with internal meaning from HttpClient responses (mpdude)
3 files changed · +75 −0
src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php+68 −0 modified@@ -16,6 +16,7 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Contracts\HttpClient\ResponseInterface; class CachingHttpClientTest extends TestCase { @@ -39,4 +40,71 @@ public function testRequestHeaders() self::assertSame($response->getRequestOptions()['normalized_headers']['application-name'][0], 'Application-Name: test1234'); self::assertSame($response->getRequestOptions()['normalized_headers']['test-name-header'][0], 'Test-Name-Header: test12345'); } + + public function testDoesNotEvaluateResponseBody() + { + $body = file_get_contents(__DIR__.'/Fixtures/assertion_failure.php'); + $response = $this->runRequest(new MockResponse($body, ['response_headers' => ['X-Body-Eval' => true]])); + $headers = $response->getHeaders(); + + $this->assertSame($body, $response->getContent()); + $this->assertArrayNotHasKey('x-body-eval', $headers); + } + + public function testDoesNotIncludeFile() + { + $file = __DIR__.'/Fixtures/assertion_failure.php'; + + $response = $this->runRequest(new MockResponse( + 'test', ['response_headers' => [ + 'X-Body-Eval' => true, + 'X-Body-File' => $file, + ]] + )); + $headers = $response->getHeaders(); + + $this->assertSame('test', $response->getContent()); + $this->assertArrayNotHasKey('x-body-eval', $headers); + $this->assertArrayNotHasKey('x-body-file', $headers); + } + + public function testDoesNotReadFile() + { + $file = __DIR__.'/Fixtures/assertion_failure.php'; + + $response = $this->runRequest(new MockResponse( + 'test', ['response_headers' => [ + 'X-Body-File' => $file, + ]] + )); + $headers = $response->getHeaders(); + + $this->assertSame('test', $response->getContent()); + $this->assertArrayNotHasKey('x-body-file', $headers); + } + + public function testRemovesXContentDigest() + { + $response = $this->runRequest(new MockResponse( + 'test', [ + 'response_headers' => [ + 'X-Content-Digest' => 'some-hash', + ] + ])); + $headers = $response->getHeaders(); + + $this->assertArrayNotHasKey('x-content-digest', $headers); + } + + private function runRequest(MockResponse $mockResponse): ResponseInterface + { + $mockClient = new MockHttpClient($mockResponse); + + $store = new Store(sys_get_temp_dir() . '/sf_http_cache'); + $client = new CachingHttpClient($mockClient, $store); + + $response = $client->request('GET', 'http://test'); + + return $response; + } }
src/Symfony/Component/HttpClient/Tests/Fixtures/assertion_failure.php+3 −0 added@@ -0,0 +1,3 @@ +<?php + +throw new \PHPUnit\Framework\AssertionFailedError('Response body should not be evaluated.');
src/Symfony/Component/HttpKernel/HttpClientKernel.php+4 −0 modified@@ -58,6 +58,10 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); + $response->headers->remove('X-Body-File'); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Content-Digest'); + $response->headers = new class($response->headers->all()) extends ResponseHeaderBag { protected function computeCacheControlValue(): string {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
13- github.com/advisories/GHSA-754h-5r27-7x3rghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/HNGUWOEETOFVH4PN3I3YO4QZHQ4AUKF3/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/VAQJXAKWPMWB7OL6QPG2ZSEQZYYPU5RC/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2020-15094ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/http-kernel/CVE-2020-15094.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2020-15094.yamlghsaWEB
- github.com/symfony/symfony/commit/d9910e0b33a2e0f993abff41c6fbc86951b66d78ghsax_refsource_MISCWEB
- github.com/symfony/symfony/security/advisories/GHSA-754h-5r27-7x3rghsax_refsource_CONFIRMWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HNGUWOEETOFVH4PN3I3YO4QZHQ4AUKF3ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VAQJXAKWPMWB7OL6QPG2ZSEQZYYPU5RCghsaWEB
- packagist.org/packages/symfony/http-kernelghsax_refsource_MISCWEB
- packagist.org/packages/symfony/symfonyghsax_refsource_MISCWEB
- symfony.com/cve-2020-15094ghsaWEB
News mentions
0No linked articles in our index yet.