CVE-2023-0827: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository pimcore/pimcore prior to 1.5.17.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2023-02-14T13:15Z
Repository: https://github.com/pimcore/pimcore
Language: PHP
Files changed: bundles/AdminBundle/Resources/public/js/pimcore/object/tags/urlSlug.js, bundles/CoreBundle/Resources/translations/en.json, models/DataObject/ClassDefinition/Data/UrlSlug.php
Commit date: 2023-02-14 09:14:01 +0100
Commit message: [Task] Improve check validity (#14301)

* set value to sanitized string if string doesn't match requirements

* sanitized string to improve validity check

* sanitized string to improve validity check

* added exception to validation
Diff: diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/object/tags/urlSlug.js b/bundles/AdminBundle/Resources/public/js/pimcore/object/tags/urlSlug.js
index d34216b5ab03..78965ffdc229 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/object/tags/urlSlug.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/object/tags/urlSlug.js
@@ -135,12 +135,10 @@ pimcore.object.tags.urlSlug = Class.create(pimcore.object.tags.abstract, {
             name: "slug",
             labelWidth: 100,
             value: siteData['slug'],
             componentCls: this.getWrapperClassNames(),
             validator: function(value) {
-
-
                 if (value) {
                     if (!value.startsWith('/') || value.length < 2) {
                         return false;
 
                     }
@@ -151,13 +149,14 @@ pimcore.object.tags.urlSlug = Class.create(pimcore.object.tags.abstract, {
                     for (let i = 0; i < parts.length; i++) {
                         let part = parts[i];
                         if  (part.length == 0) {
                             return false;
                         }
+
                         sanitizedPart = part.replace(/[#\?\*\:\\\\<\>\|"%&@=;]/g, '-');
                         if (sanitizedPart != part) {
-                            return false;
+                            return t('url-slug-invalid-chars');
                         }
                     }
                 }
 
                 return true;
diff --git a/bundles/CoreBundle/Resources/translations/en.json b/bundles/CoreBundle/Resources/translations/en.json
index b4b10dde3d76..642b949fa0c0 100644
--- a/bundles/CoreBundle/Resources/translations/en.json
+++ b/bundles/CoreBundle/Resources/translations/en.json
@@ -997,7 +997,8 @@
     "objects_sort_method": "Change data objects sort method",
     "link_recursion_error": "The link is redirecting to itself, causing an infinite loop",
     "no_further_classes_allowed": "No further classes allowed",
     "address_not_found": "The entered address was not found",
     "possible_causes": "Possible causes",
-    "postal_code_format_error": "Postal code format, e.g. use \"5020 Salzburg, Söllheimer Straße 16\" instead of \"A-5020 Salzburg, Söllheimer Straße 16\""
+    "postal_code_format_error": "Postal code format, e.g. use \"5020 Salzburg, Söllheimer Straße 16\" instead of \"A-5020 Salzburg, Söllheimer Straße 16\"",
+    "url-slug-invalid-chars": "Provided invalid character in URL slug"
 }
diff --git a/models/DataObject/ClassDefinition/Data/UrlSlug.php b/models/DataObject/ClassDefinition/Data/UrlSlug.php
index 42a802e4de8e..c650e7204d03 100644
--- a/models/DataObject/ClassDefinition/Data/UrlSlug.php
+++ b/models/DataObject/ClassDefinition/Data/UrlSlug.php
@@ -175,14 +175,19 @@ class UrlSlug extends Data implements CustomResourcePersistingInterface, LazyLoa
         }
         $foundSlug = false;
         if (is_array($data)) {
             /** @var Model\DataObject\Data\UrlSlug $item */
             foreach ($data as $item) {
-                $slug = $item->getSlug();
+                $slug = htmlspecialchars($item->getSlug());
                 $foundSlug = true;
 
                 if (strlen($slug) > 0) {
+                    $slugToCompare = preg_replace('/[#\?\*\:\\\\<\>\|"%&@=;]/', '-', $item->getSlug());
+                    if($item->getSlug() !== $slugToCompare){
+                        throw new Model\Element\ValidationException('Slug contains forbidden characters!');
+                    }
+
                     $document = Model\Document::getByPath($slug);
                     if ($document) {
                         throw new Model\Element\ValidationException('Slug must be unique. Found conflict with document path "' . $slug . '"');
                     }
 

Vulnerable code: 

                            return false;
    "postal_code_format_error": "Postal code format, e.g. use \"5020 Salzburg, Söllheimer Straße 16\" instead of \"A-5020 Salzburg, Söllheimer Straße 16\""
                $slug = $item->getSlug();
Fixed code: 
                            return t('url-slug-invalid-chars');
    "postal_code_format_error": "Postal code format, e.g. use \"5020 Salzburg, Söllheimer Straße 16\" instead of \"A-5020 Salzburg, Söllheimer Straße 16\"",
    "url-slug-invalid-chars": "Provided invalid character in URL slug"
                $slug = htmlspecialchars($item->getSlug());
                    $slugToCompare = preg_replace('/[#\?\*\:\\\\<\>\|"%&@=;]/', '-', $item->getSlug());
                    if($item->getSlug() !== $slugToCompare){
                        throw new Model\Element\ValidationException('Slug contains forbidden characters!');
                    }