VYPR
Low severityNVD Advisory· Published Apr 3, 2024· Updated Aug 30, 2024

Concrete CMS version 9 prior to 9.2.8 and previous versions prior to 8.5.16 are vulnerable to Stored XSS in the Search Field.

CVE-2024-3181

Description

Concrete CMS version 9 prior to 9.2.8 and previous versions prior to 8.5.16 are vulnerable to Stored XSS in the Search Field. Prior to the fix, stored XSS could be executed by an administrator changing a filter to which a rogue administrator had previously added malicious code. The Concrete CMS security team gave this vulnerability a CVSS v3.1 score of 3.1 with a vector of AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:L https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator . Thanks Alexey Solovyev for reporting

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Concrete CMS 9 prior to 9.2.8 and 8.5.16 are vulnerable to stored XSS in the Search Field, allowing a rogue administrator to inject malicious code that executes when another admin modifies the filter.

Vulnerability

Overview A stored cross-site scripting (XSS) vulnerability exists in the Search Field of Concrete CMS versions 9 prior to 9.2.8 and versions prior to 8.5.16. The root cause is insufficient sanitization of user-supplied input when saving search filter preset names and file download link text [1][2][3]. An authenticated administrator with the ability to create or modify search filters can inject arbitrary JavaScript code into the preset name or link text fields [1].

Exploitation

To exploit this vulnerability, an attacker must have administrative privileges on a Concrete CMS instance. The attacker creates or modifies a search filter, embedding malicious script in the preset name or link text. Later, when another administrator edits or changes that filter, the injected script executes in the context of the victim's browser session [1]. The CVSS vector (AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:L) indicates the attack requires high complexity and privileges, and relies on user interaction from another admin [1].

Impact

A successful stored XSS attack allows the rogue administrator to perform actions on behalf of the victim administrator within the CMS, potentially altering configurations or exfiltrating session tokens. The impact is limited to low integrity and availability impacts, as the vulnerable field is only accessible to administrators and the attack requires a high level of privileges [1].

Mitigation

Concrete CMS addressed this vulnerability in version 9.2.8 (commit 822e689) and version 8.5.16 (commit e85ef24) by applying output encoding via the h() function to user-supplied data in the affected templates [2][3][4]. All users should upgrade to the latest patched version to prevent exploitation.

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.

PackageAffected versionsPatched versions
concrete5/concrete5Packagist
>= 9.0.0RC1, < 9.2.89.2.8
concrete5/concrete5Packagist
< 8.5.168.5.16

Affected products

2

Patches

4
e85ef2408a5e

Merge pull request #12007 from KorvinSzanto/8.5.x-output-cleanup

https://github.com/concretecms/concretecmsAndrew EmblerApr 2, 2024via ghsa
9 files changed · +20 14
  • concrete/blocks/file/view.php+1 1 modified
    @@ -10,7 +10,7 @@
         ?>
     	<div class="ccm-block-file">
     		<a href="<?php echo $forceDownload ? $f->getForceDownloadURL() : $f->getDownloadURL();
    -    ?>"><?php echo stripslashes($controller->getLinkText()) ?></a>
    +    ?>"><?php echo h(stripslashes($controller->getLinkText())) ?></a>
     	</div>
     
     
    
  • concrete/controllers/dialog/express/preset/delete.php+1 1 modified
    @@ -65,7 +65,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $em = $this->app->make(\Doctrine\ORM\EntityManager::class);
                             $em->remove($searchPreset);
    
  • concrete/controllers/dialog/file/preset/delete.php+1 1 modified
    @@ -46,7 +46,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $node = TreeNodeSearchPreset::getNodeBySavedSearchID($presetID);
                             if (is_object($node)) {
    
  • concrete/controllers/dialog/search/preset/delete.php+1 1 modified
    @@ -43,7 +43,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $em = $this->app->make(EntityManager::class);
                             $em->remove($searchPreset);
    
  • concrete/controllers/dialog/search/preset/edit.php+1 1 modified
    @@ -44,7 +44,7 @@ public function edit_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s edited successfully.', $newPresetName));
    +                        $response->setMessage(t('%s edited successfully.', h($newPresetName)));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $response->setAdditionalDataAttribute('actionURL', (string) $this->getSavedSearchBaseURL($searchPreset));
                             $searchPreset->setPresetName($newPresetName);
    
  • concrete/single_pages/dashboard/system/calendar/colors.php+5 5 modified
    @@ -11,14 +11,14 @@
                 <div class="form-inline">
                 <?=$form->label('defaultBackgroundColor', t('Background'))?>
                 &nbsp;
    -            <?=$color->output('defaultBackgroundColor', $defaultBackgroundColor)?>
    +            <?=$color->output('defaultBackgroundColor', h($defaultBackgroundColor))?>
                 </div>
             </div>
             <div class="form-group col-sm-5">
                 <div class="form-inline">
                 <?=$form->label('defaultTextColor', t('Text'))?>
                     &nbsp;
    -            <?=$color->output('defaultTextColor', $defaultTextColor)?>
    +            <?=$color->output('defaultTextColor', h($defaultTextColor))?>
                 </div>
             </div>
             </div>
    @@ -53,14 +53,14 @@
                     <tr>
                         <td style="text-align: center; width: 10px"><?=$form->checkbox('override[]', $topic->getTreeNodeID(), $checked)?></td>
                         <td style="width: 50%"><?=$topic->getTreeNodeDisplayName()?></td>
    -                    <td><?=$color->output('backgroundColor[' . $topic->getTreeNodeID() . ']', $backgroundColor)?></td>
    -                    <td><?=$color->output('textColor[' . $topic->getTreeNodeID() . ']', $textColor)?></td>
    +                    <td><?=$color->output('backgroundColor[' . $topic->getTreeNodeID() . ']', h($backgroundColor))?></td>
    +                    <td><?=$color->output('textColor[' . $topic->getTreeNodeID() . ']', h($textColor))?></td>
                     </tr>
                 <?php 
     }
         ?>
                 </table>
    -        <?php 
    +        <?php
     } else {
         ?>
                 <p><?=t('You have not defined any categories for your calendars.')?></p>
    
  • concrete/src/StyleCustomizer/Inline/StyleSet.php+8 2 modified
    @@ -247,8 +247,14 @@ public static function populateFromRequest(Request $request)
     
             $v = $post->get('customClass');
             if (is_array($v)) {
    -            $set->setCustomClass(implode(' ', $v));
    -            $return = true;
    +            $v = array_filter($v, function ($class) {
    +                return preg_match('/^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/', $class);
    +            });
    +
    +            if (count($v) > 0) {
    +                $set->setCustomClass(implode(' ', $v));
    +                $return = true;
    +            }
             }
     
             $v = trim($post->get('customID', ''));
    
  • concrete/views/dialogs/search/preset/delete.php+1 1 modified
    @@ -5,7 +5,7 @@
             <?= $token->output('remove_search_preset'); ?>
             <?= $form->hidden('presetID', $searchPreset->getId()); ?>
             <?= $form->hidden('objectID', $controller->getObjectID()); ?>
    -        <p><?= t('Are you sure you want to remove the "%s" search preset?', $searchPreset->getPresetName()); ?></p>
    +        <p><?= t('Are you sure you want to remove the "%s" search preset?', h($searchPreset->getPresetName())); ?></p>
     
             <div class="dialog-buttons">
                 <button class="btn btn-default" data-dialog-action="cancel"><?= t('Cancel'); ?></button>
    
  • concrete/views/dialogs/search/preset/edit.php+1 1 modified
    @@ -7,7 +7,7 @@
             <?= $form->hidden('objectID', $controller->getObjectID()); ?>
             <div class="form-group">
                 <?= $form->label('presetName', t('Name')); ?>
    -            <?= $form->text('presetName', $searchPreset->getPresetName()); ?>
    +            <?= $form->text('presetName', h($searchPreset->getPresetName())); ?>
             </div>
     
             <div class="dialog-buttons">
    
822e689cefe1

Output cleanup (#11988)

https://github.com/concretecms/concretecmsKorvin SzantoMar 27, 2024via ghsa
9 files changed · +20 14
  • concrete/blocks/file/view.php+1 1 modified
    @@ -16,7 +16,7 @@
         ?>
         <div class="ccm-block-file">
             <a href="<?php echo (!empty($forceDownload)) ? $f->getForceDownloadURL() : $f->getDownloadURL(); ?>">
    -            <?php echo stripslashes($controller->getLinkText()) ?>
    +            <?php echo h(stripslashes($controller->getLinkText())) ?>
             </a>
         </div>
         <?php
    
  • concrete/controllers/dialog/express/preset/delete.php+1 1 modified
    @@ -71,7 +71,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $em = $this->app->make(\Doctrine\ORM\EntityManager::class);
                             $em->remove($searchPreset);
    
  • concrete/controllers/dialog/file/preset/delete.php+1 1 modified
    @@ -46,7 +46,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $node = TreeNodeSearchPreset::getNodeBySavedSearchID($presetID);
                             if (is_object($node)) {
    
  • concrete/controllers/dialog/search/preset/delete.php+1 1 modified
    @@ -48,7 +48,7 @@ public function remove_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s deleted successfully.', $searchPreset->getPresetName()));
    +                        $response->setMessage(t('%s deleted successfully.', h($searchPreset->getPresetName())));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $em = $this->app->make(EntityManager::class);
                             $em->remove($searchPreset);
    
  • concrete/controllers/dialog/search/preset/edit.php+1 1 modified
    @@ -49,7 +49,7 @@ public function edit_search_preset()
                         }
                         if (!$this->error->has()) {
                             $response = new EditResponse();
    -                        $response->setMessage(t('%s edited successfully.', $newPresetName));
    +                        $response->setMessage(t('%s edited successfully.', h($newPresetName)));
                             $response->setAdditionalDataAttribute('presetID', $presetID);
                             $response->setAdditionalDataAttribute('actionURL', (string) $this->getSavedSearchBaseURL($searchPreset));
                             $searchPreset->setPresetName($newPresetName);
    
  • concrete/single_pages/dashboard/system/calendar/colors.php+5 5 modified
    @@ -8,11 +8,11 @@
             <legend><?=t('Default Colors')?></legend>
             <div class="form-group">
                 <?=$form->label('defaultBackgroundColor', t('Background'))?>
    -            <?=$color->output('defaultBackgroundColor', $defaultBackgroundColor)?>
    +            <?=$color->output('defaultBackgroundColor', h($defaultBackgroundColor))?>
             </div>
             <div class="form-group">
                 <?=$form->label('defaultTextColor', t('Text'))?>
    -            <?=$color->output('defaultTextColor', $defaultTextColor)?>
    +            <?=$color->output('defaultTextColor', h($defaultTextColor))?>
             </div>
         </fieldset>
     
    @@ -45,10 +45,10 @@
                     <tr>
                         <td style="text-align: center; width: 10px"><?=$form->checkbox('override[]', $topic->getTreeNodeID(), $checked)?></td>
                         <td style="width: 50%"><?=$topic->getTreeNodeDisplayName()?></td>
    -                    <td><?=$color->output('backgroundColor[' . $topic->getTreeNodeID() . ']', $backgroundColor)?></td>
    -                    <td><?=$color->output('textColor[' . $topic->getTreeNodeID() . ']', $textColor)?></td>
    +                    <td><?=$color->output('backgroundColor[' . $topic->getTreeNodeID() . ']', h($backgroundColor))?></td>
    +                    <td><?=$color->output('textColor[' . $topic->getTreeNodeID() . ']', h($textColor))?></td>
                     </tr>
    -            <?php 
    +            <?php
     }
         ?>
                 </table>
    
  • concrete/src/StyleCustomizer/Inline/StyleSet.php+8 2 modified
    @@ -256,8 +256,14 @@ public static function populateFromRequest(Request $request)
     
             $v = $post->get('customClass');
             if (is_array($v)) {
    -            $set->setCustomClass(implode(' ', $v));
    -            $return = true;
    +            $v = array_filter($v, function ($class) {
    +                return preg_match('/^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/', $class);
    +            });
    +
    +            if (count($v) > 0) {
    +                $set->setCustomClass(implode(' ', $v));
    +                $return = true;
    +            }
             }
     
             $v = trim($post->get('customID', ''));
    
  • concrete/views/dialogs/search/preset/delete.php+1 1 modified
    @@ -4,7 +4,7 @@
         <form method="post" data-dialog-form="remove-search-preset" class="form-horizontal" action="<?= $controller->getDeleteSearchPresetAction(); ?>">
             <?= $token->output('remove_search_preset'); ?>
             <?= $form->hidden('presetID', $searchPreset->getId()); ?>
    -        <p><?= t('Are you sure you want to remove the "%s" search preset?', $searchPreset->getPresetName()); ?></p>
    +        <p><?= t('Are you sure you want to remove the "%s" search preset?', h($searchPreset->getPresetName())); ?></p>
     
             <div class="dialog-buttons clearfix">
                 <button class="btn btn-secondary" data-dialog-action="cancel"><?= t('Cancel'); ?></button>
    
  • concrete/views/dialogs/search/preset/edit.php+1 1 modified
    @@ -6,7 +6,7 @@
             <?= $form->hidden('presetID', $searchPreset->getId()); ?>
             <div class="form-group">
                 <?= $form->label('presetName', t('Name')); ?>
    -            <?= $form->text('presetName', $searchPreset->getPresetName()); ?>
    +            <?= $form->text('presetName', h($searchPreset->getPresetName())); ?>
             </div>
     
             <div class="dialog-buttons clearfix">
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.