Clickstorm SEO Allows Cross-Site Scripting (XSS)
Description
A cross-site scripting (XSS) vulnerability has been discovered in the Clickstorm SEO extension. This vulnerabily is exploitable by a logged in backend user utilizing the TYPO3 backend user interface. This user can create output in the HTML context by exploiting improperly encoded user input. Updates 6.7.0, 7.4.0, 8.3.0 and 9.2.0 are available for download.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Clickstorm SEO extension for TYPO3 contains a stored XSS vulnerability exploitable by backend users to inject arbitrary HTML or JavaScript.
Vulnerability
Overview
The Clickstorm SEO extension (cs_seo) for TYPO3 fails to properly encode user input when rendering output in an HTML context within the backend user interface. Specifically, the showEmptyImageAltAction() method in ModuleFileController did not sanitize data passed to the template, allowing a logged-in backend user to inject malicious code [1][2]. This is a classic cross-site scripting (XSS) vulnerability rooted in insufficient output encoding [4].
Attack
Vector and Prerequisites
An attacker must be an authenticated backend user with at least some access to the TYPO3 backend. The vulnerable functionality is triggered when the user interacts with the SEO file module, specifically the feature that displays images with missing alt text. The attacker can craft input that, when stored and later rendered in the backend interface, executes arbitrary JavaScript or HTML in the context of the victim's browser session [2][4]. The patch (commit 46e15a2) introduced permission checks and encoded output to mitigate the issue [1].
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript in the TYPO3 backend, potentially leading to session hijacking, data theft, or unauthorized actions performed on behalf of other backend users. The severity is rated medium (CVSS 3.1 score not provided, but advisory suggests AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L/E:F/RL:O/RC:C) [4].
Mitigation and
Remediation
The vulnerability affects versions 6.0.0–6.6.0, 7.0.0–7.3.3, 8.0.0–8.2.1, and 9.0.0–9.1.0. Fixed versions are 6.7.0, 7.4.0, 8.3.0, and 9.2.0 [2][4]. Users should update immediately. As a general hardening measure, enabling Content Security Policy (CSP) for the TYPO3 backend is also recommended [4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
clickstorm/cs-seoPackagist | >= 9.0.0, < 9.2.0 | 9.2.0 |
clickstorm/cs-seoPackagist | >= 8.0.0, < 8.3.0 | 8.3.0 |
clickstorm/cs-seoPackagist | >= 7.0.0, < 7.4.0 | 7.4.0 |
clickstorm/cs-seoPackagist | >= 6.0.0, < 6.7.0 | 6.7.0 |
Affected products
3- Range: >= 6.0.0, < 6.7.0
- Range: <=6.6.9999 <=7.3.9999 <=8.2.9999 <=9.1.9999
Patches
146e15a22d52d[SECURITY] check user permissions and prevent xss in file module #2025030410000029
3 files changed · +42 −23
Classes/Controller/AbstractModuleController.php+8 −2 modified@@ -154,7 +154,7 @@ protected function wrapModuleTemplate(): string $this->jsInlineCode .= $this->renderFlashMessages(); if ($this->jsInlineCode !== '' && $this->jsInlineCode !== '0') { - $this->pageRenderer->addJsInlineCode('csseo-inline', $this->jsInlineCode); + $this->pageRenderer->addJsInlineCode('csseo-inline', $this->jsInlineCode, true, false, true); } // Shortcut in doc header @@ -226,7 +226,13 @@ protected function renderFlashMessages(): string foreach ($messageQueue->getAllMessages() as $flashMessage) { $method = $flashMessage->getSeverity()->getCssClass(); $messages[] = - 'top.TYPO3.Notification.' . $method . '("' . $flashMessage->getTitle() . '", "' . $flashMessage->getMessage() . '", ' . static::$flashMessageDurationInSeconds . ');'; + 'top.TYPO3.Notification.' . + $method . + '("' . htmlspecialchars($flashMessage->getTitle()) . + '", "' . + htmlspecialchars($flashMessage->getMessage()) . + '", ' . + static::$flashMessageDurationInSeconds . ');'; } return '
Classes/Controller/ModuleFileController.php+33 −20 modified@@ -12,6 +12,7 @@ use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\Components\ButtonBar; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Http\AllowedMethodsTrait; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Imaging\IconSize; use TYPO3\CMS\Core\Messaging\FlashMessage; @@ -27,6 +28,8 @@ class ModuleFileController extends AbstractModuleController { + use AllowedMethodsTrait; + public static string $session_prefix = 'tx_csseo_file_'; public static string $mod_name = 'file_CsSeoModFile'; public static string $uriPrefix = 'tx_csseo_file_csseomodfile'; @@ -144,26 +147,32 @@ public function showEmptyImageAltAction(): ResponseInterface $dataMapper = GeneralUtility::makeInstance(DataMapper::class); $files = $dataMapper->map(File::class, $imageRow); $this->image = $files[0]; - $formService = GeneralUtility::makeInstance(FormService::class); - $metadataUid = (int)$this->image->getOriginalResource()->getProperties()['metadata_uid']; - // if no metadata record is there, create one - if ($metadataUid === 0) { - $this->image->getOriginalResource()->getMetaData()->save(); + if ($this->image->getOriginalResource()->checkActionPermission('read') + && $this->image->getOriginalResource()->checkActionPermission('editMeta')) { + $formService = GeneralUtility::makeInstance(FormService::class); $metadataUid = (int)$this->image->getOriginalResource()->getProperties()['metadata_uid']; - } - if ($this->modParams['onlyReferenced']) { - $this->moduleTemplate->assign('numberOfReferences', DatabaseUtility::getFileReferenceCount($this->image->getUid())); + // if no metadata record is there, create one + if ($metadataUid === 0) { + $this->image->getOriginalResource()->getMetaData()->save(); + $metadataUid = (int)$this->image->getOriginalResource()->getProperties()['metadata_uid']; + } + + if ($this->modParams['onlyReferenced']) { + $this->moduleTemplate->assign('numberOfReferences', DatabaseUtility::getFileReferenceCount($this->image->getUid())); + } + + $editForm = $formService->makeEditForm('sys_file_metadata', $metadataUid, implode(',', $configuredColumns)); + + $this->moduleTemplate->assignMultiple([ + 'offset' => $this->offset, + 'editForm' => $editForm, + 'image' => $files[0] + ]); + } else { + $this->moduleTemplate->assign('error', 'no_access'); } - - $editForm = $formService->makeEditForm('sys_file_metadata', $metadataUid, implode(',', $configuredColumns)); - - $this->moduleTemplate->assignMultiple([ - 'offset' => $this->offset, - 'editForm' => $editForm, - 'image' => $files[0] - ]); } } @@ -176,15 +185,19 @@ public function showEmptyImageAltAction(): ResponseInterface */ public function updateAction(): ResponseInterface { - $uid = $this->request->hasArgument('uid') ? (int)$this->request->getArgument('uid') : 0; - $data = $this->request->getParsedBody()['data']['sys_file_metadata'] ?? $this->request->getQueryParams()['data']['sys_file_metadata'] ?? ''; + $uid = (int)($this->request->getParsedBody()['uid'] ?? 0); + $data = $this->request->getParsedBody()['data']['sys_file_metadata'] ?? false; - if ($uid && $data) { + $this->assertAllowedHttpMethod($this->request, 'POST'); + + if ($uid > 0 && $data) { /** @var ResourceFactory $resourceFactory */ $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $file = $resourceFactory->getFileObject($uid); - $file->getMetaData()->add(array_values($data)[0])->save(); + if ($file->checkActionPermission('editMeta')) { + $file->getMetaData()->add(array_values($data)[0])->save(); + } if ($file->getProperty('alternative')) { $message = GeneralUtility::makeInstance(
Resources/Private/Templates/ModuleFile/ShowEmptyImageAlt.html+1 −1 modified@@ -9,7 +9,7 @@ <h1> <f:translate key="layouts.module.action.showEmptyImageAlt" extensionName="cs_seo"/> </h1> - <f:if condition="{numberOfAllImages}"> + <f:if condition="{numberOfAllImages} && !{error}"> <f:then> <div class="csseo-progress form-group"> <f:translate key="module.file.count" extensionName="cs_seo"/>
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.