CVE-2023-6889: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository thorsten/phpmyfaq prior to 3.1.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-12-16T09:15Z
Repository: https://github.com/thorsten/phpmyfaq
Language: PHP
Files changed: phpmyfaq/admin/ajax.tags.php, phpmyfaq/admin/assets/js/tags.js
Commit date: 2023-08-23 12:22:08 +0200
Commit message: fix: added missing conversion to HTML entities

Diff: diff --git a/phpmyfaq/admin/ajax.tags.php b/phpmyfaq/admin/ajax.tags.php
index b4172ca663b..e2023e7ad86 100644
--- a/phpmyfaq/admin/ajax.tags.php
+++ b/phpmyfaq/admin/ajax.tags.php
@@ -15,10 +15,11 @@
  * @since 2005-12-15
  */
 
 use phpMyFAQ\Filter;
 use phpMyFAQ\Helper\HttpHelper;
+use phpMyFAQ\Strings;
 use phpMyFAQ\Tags;
 use phpMyFAQ\Entity\TagEntity as TagEntity;
 
 if (!defined('IS_VALID_PHPMYFAQ')) {
     http_response_code(400);
@@ -53,11 +54,11 @@ switch ($ajaxAction) {
             $tagNames = [];
             foreach ($tags as $tagName) {
                 ++$i;
                 if ($i <= PMF_TAGS_AUTOCOMPLETE_RESULT_SET_SIZE) {
                     $currentTag = new stdClass();
-                    $currentTag->tagName = $tagName;
+                    $currentTag->tagName = Strings::htmlentities($tagName);
                     $tagNames[] = $currentTag;
                 }
             }
 
             $http->sendJsonWithHeaders($tagNames);
diff --git a/phpmyfaq/admin/assets/js/tags.js b/phpmyfaq/admin/assets/js/tags.js
index 87e4f753376..94c84885fcc 100644
--- a/phpmyfaq/admin/assets/js/tags.js
+++ b/phpmyfaq/admin/assets/js/tags.js
@@ -22,11 +22,11 @@ document.addEventListener('DOMContentLoaded', () => {
 
     if (span.length > 0) {
       span.replaceWith('<input name="tag" class="form-control" data-tag-id="' + id + '" value="' + span.html() + '">');
     } else {
       const input = $('input[data-tag-id="' + id + '"]');
-      input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
+      input.replaceWith('<span data-tag-id="' + id + '">' + escape(input.val().replace(/\//g, '&#x2F;')) + '</span>');
     }
   });
 
   $('.tag-form').bind('submit', function (event) {
     event.preventDefault();
@@ -34,14 +34,10 @@ document.addEventListener('DOMContentLoaded', () => {
     const input = $('input[data-tag-id]:focus');
     const id = input.data('tag-id');
     const tag = input.val();
     const csrf = $('input[name=csrf]').val();
 
-    const escape = (unsafe) => {
-      return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
-    }
-
     $.ajax({
       url: 'index.php?action=ajax&ajax=tags&ajaxaction=update',
       type: 'POST',
       data: 'id=' + id + '&tag=' + tag + '&csrf=' + csrf,
       dataType: 'json',
@@ -57,6 +53,10 @@ document.addEventListener('DOMContentLoaded', () => {
       },
     });
 
     return false;
   });
+
+  const escape = (unsafe) => {
+    return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
+  }
 });

Vulnerable code:                     $currentTag->tagName = $tagName;
      input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
    const escape = (unsafe) => {
      return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
    }

Fixed code: use phpMyFAQ\Strings;
                    $currentTag->tagName = Strings::htmlentities($tagName);
      input.replaceWith('<span data-tag-id="' + id + '">' + escape(input.val().replace(/\//g, '&#x2F;')) + '</span>');

  const escape = (unsafe) => {
    return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
  }