Reflected XSS Vulnerability in Sulu Media Bundle
Description
Sulu is a PHP content management system. This vulnerability allows an attacker to inject arbitrary HTML/JavaScript code through the media download URL in Sulu CMS. It affects the SuluMediaBundle component. The vulnerability is a Reflected Cross-Site Scripting (XSS) issue, which could potentially allow attackers to steal sensitive information, manipulate the website's content, or perform actions on behalf of the victim. This vulnerability is fixed in 2.6.5 and 2.5.21.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
sulu/suluPackagist | >= 2.6.0, < 2.6.5 | 2.6.5 |
sulu/suluPackagist | >= 2.0.0, < 2.5.21 | 2.5.21 |
Affected products
1Patches
21 file changed · +1 −1
src/Sulu/Bundle/MediaBundle/Controller/MediaStreamController.php+1 −1 modified@@ -103,7 +103,7 @@ public function downloadAction(Request $request, $id, $slug) } if ($fileVersion->getName() !== $slug) { - return new Response('Invalid file name "' . $slug . '" for media with ID "' . $id . '".', 404); + return new Response('Invalid file name for media with ID "' . $id . '".', 404); } if ($this->securityChecker) {
a5a5ae555d28Add forcing direct matching for media download urls (#7535)
3 files changed · +34 −19
src/Sulu/Bundle/MediaBundle/Controller/MediaStreamController.php+7 −2 modified@@ -81,10 +81,11 @@ public function getImageAction(Request $request) /** * @param int $id + * @param string $slug * * @return Response */ - public function downloadAction(Request $request, $id) + public function downloadAction(Request $request, $id, $slug) { try { if (\ob_get_length()) { @@ -98,7 +99,11 @@ public function downloadAction(Request $request, $id) $fileVersion = $this->getFileVersion($id, $version); if (!$fileVersion) { - return new Response(null, 404); + return new Response('Invalid version "' . $version . '" for media with ID "' . $id . '".', 404); + } + + if ($fileVersion->getName() !== $slug) { + return new Response('Invalid file name "' . $slug . '" for media with ID "' . $id . '".', 404); } if ($this->securityChecker) {
src/Sulu/Bundle/MediaBundle/Tests/Functional/Controller/MediaControllerTest.php+6 −6 modified@@ -381,7 +381,7 @@ protected function setUpCollection(): void */ public function test404ResponseHeader(): void { - $this->client->jsonRequest( + $this->client->request( 'GET', '/uploads/media/50x50/1/0-photo.jpg' ); @@ -399,9 +399,9 @@ public function testDownloadHeaderAttachment(): void $media = $this->createMedia('photo'); \ob_start(); - $this->client->jsonRequest( + $this->client->request( 'GET', - '/media/' . $media->getId() . '/download/photo.jpg' + '/media/' . $media->getId() . '/download/photo.jpeg' ); \ob_end_clean(); $this->assertEquals( @@ -418,9 +418,9 @@ public function testDownloadHeaderInline(): void $media = $this->createMedia('photo'); \ob_start(); - $this->client->jsonRequest( + $this->client->request( 'GET', - '/media/' . $media->getId() . '/download/photo.jpg?inline=1' + '/media/' . $media->getId() . '/download/photo.jpeg?inline=1' ); \ob_end_clean(); $this->assertEquals( @@ -437,7 +437,7 @@ public function testDownloadHeaderUmlauts(): void $media = $this->createMedia('wöchentlich'); \ob_start(); - $this->client->jsonRequest( + $this->client->request( 'GET', '/media/' . $media->getId() . '/download/wöchentlich.jpeg?inline=1' );
src/Sulu/Bundle/MediaBundle/Tests/Functional/Controller/MediaStreamControllerTest.php+21 −11 modified@@ -43,17 +43,27 @@ public function testDownloadAction(): void $filePath = $this->createMediaFile('test.jpg'); $media = $this->createMedia($filePath, 'file-without-extension'); - $this->client->jsonRequest('GET', $media->getUrl()); + $this->client->request('GET', $media->getUrl()); $response = $this->client->getResponse(); $this->assertHttpStatusCode(200, $response); } + public function testDownloadActionNotMatchingFileName(): void + { + $filePath = $this->createMediaFile('test.jpg'); + $media = $this->createMedia($filePath, 'test.jpg'); + + $this->client->request('GET', \str_replace('test.jpg', 'other.jpg', $media->getUrl())); + $response = $this->client->getResponse(); + $this->assertHttpStatusCode(404, $response); + } + public function testNotExistVersionDownloadAction(): void { $filePath = $this->createMediaFile('test.jpg'); $media = $this->createMedia($filePath, 'file-without-extension'); - $this->client->jsonRequest('GET', \str_replace('v=1', 'v=99', $media->getUrl())); + $this->client->request('GET', \str_replace('v=1', 'v=99', $media->getUrl())); $response = $this->client->getResponse(); $this->assertHttpStatusCode(404, $response); } @@ -92,7 +102,7 @@ public function testDownloadWithoutExtensionAction(): void $filePath = $this->createMediaFile('file-without-extension'); $media = $this->createMedia($filePath, 'File without Extension'); - $this->client->jsonRequest('GET', $media->getUrl()); + $this->client->request('GET', $media->getUrl()); $response = $this->client->getResponse(); $this->assertHttpStatusCode(200, $response); } @@ -102,7 +112,7 @@ public function testDownloadWithDotInName(): void $filePath = $this->createMediaFile('fitness-seasons.agency--C-&-C--Rodach,-Johannes'); $media = $this->createMedia($filePath, 'fitness-seasons.agency--C-&-C--Rodach,-Johannes'); - $this->client->jsonRequest('GET', $media->getUrl()); + $this->client->request('GET', $media->getUrl()); $response = $this->client->getResponse(); $this->assertHttpStatusCode(200, $response); @@ -118,7 +128,7 @@ public function testDownloadWithDotInName(): void public function testGetImageActionForNonExistingMedia(): void { - $this->client->jsonRequest('GET', '/uploads/media/sulu-400x400/01/test.jpg?v=1'); + $this->client->request('GET', '/uploads/media/sulu-400x400/01/test.jpg?v=1'); $this->assertHttpStatusCode(404, $this->client->getResponse()); } @@ -128,11 +138,11 @@ public function testGetImageAction(): void $filePath = $this->createMediaFile('test.jpg'); $media = $this->createMedia($filePath, 'Test jpg'); - $this->client->jsonRequest('GET', $media->getFormats()['small-inset']); + $this->client->request('GET', $media->getFormats()['small-inset']); $this->assertHttpStatusCode(200, $this->client->getResponse()); $this->assertSame('image/jpeg', $this->client->getResponse()->headers->get('Content-Type')); - $this->client->jsonRequest('GET', $media->getFormats()['small-inset.gif']); + $this->client->request('GET', $media->getFormats()['small-inset.gif']); $this->assertHttpStatusCode(200, $this->client->getResponse()); $this->assertSame('image/gif', $this->client->getResponse()->headers->get('Content-Type')); } @@ -142,11 +152,11 @@ public function testGetImageActionSvg(): void $filePath = $this->createMediaFile('test.svg', 'sulu.svg'); $media = $this->createMedia($filePath, 'Test svg'); - $this->client->jsonRequest('GET', $media->getFormats()['small-inset']); + $this->client->request('GET', $media->getFormats()['small-inset']); $this->assertHttpStatusCode(200, $this->client->getResponse()); $this->assertSame('image/svg+xml', $this->client->getResponse()->headers->get('Content-Type')); - $this->client->jsonRequest('GET', $media->getFormats()['small-inset.svg']); + $this->client->request('GET', $media->getFormats()['small-inset.svg']); $this->assertHttpStatusCode(200, $this->client->getResponse()); $this->assertSame('image/svg+xml', $this->client->getResponse()->headers->get('Content-Type')); } @@ -162,14 +172,14 @@ public function testGetImageActionSvgAsJpg(): void $filePath = $this->createMediaFile('test.svg', 'sulu.svg'); $media = $this->createMedia($filePath, 'Test svg'); - $this->client->jsonRequest('GET', $media->getFormats()['small-inset.jpg']); + $this->client->request('GET', $media->getFormats()['small-inset.jpg']); $this->assertHttpStatusCode(200, $this->client->getResponse()); $this->assertSame('image/jpeg', $this->client->getResponse()->headers->get('Content-Type')); } public function testDownloadActionForNonExistingMedia(): void { - $this->client->jsonRequest('GET', '/media/999/download/test.jpg?v=1'); + $this->client->request('GET', '/media/999/download/test.jpg?v=1'); $this->assertHttpStatusCode(404, $this->client->getResponse()); }
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
6- github.com/advisories/GHSA-6784-9c82-vr85ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-47617ghsaADVISORY
- github.com/sulu/sulu/blob/2.6/src/Sulu/Bundle/MediaBundle/Controller/MediaStreamController.phpghsaWEB
- github.com/sulu/sulu/commit/a5a5ae555d282e88ff8559d38cfb46dea7939bdaghsax_refsource_MISCWEB
- github.com/sulu/sulu/commit/eeacd14b6cf55f710084788140d40ebb00314b29ghsax_refsource_MISCWEB
- github.com/sulu/sulu/security/advisories/GHSA-6784-9c82-vr85ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.