CVE-2017-15809: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In phpMyFaq before 2.9.9, there is XSS in admin/tags.main.php via a crafted tag.
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: 6.1
Published: 2017-10-23T17:29Z
Repository: https://github.com/thorsten/phpMyFAQ
Language: PHP
Files changed: phpmyfaq/admin/ajax.tags.php, phpmyfaq/admin/assets/js/tags.js, phpmyfaq/admin/tags.main.php
Commit date: 2017-10-10 19:52:30 +0200
Commit message: Added XSS and CSRF protection

Diff: diff --git a/phpmyfaq/admin/ajax.tags.php b/phpmyfaq/admin/ajax.tags.php
index 4787e93aed..68bfbcfd5e 100644
--- a/phpmyfaq/admin/ajax.tags.php
+++ b/phpmyfaq/admin/ajax.tags.php
@@ -68,10 +68,16 @@ switch ($ajaxAction) {
 
     case 'update':
 
         $id = PMF_Filter::filterInput(INPUT_POST, 'id', FILTER_VALIDATE_INT);
         $tag = PMF_Filter::filterInput(INPUT_POST, 'tag', FILTER_SANITIZE_STRING);
+        $csrfToken = PMF_Filter::filterInput(INPUT_POST, 'csrf', FILTER_SANITIZE_STRING);
+
+        if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfToken) {
+            echo json_encode($PMF_LANG['err_NotAuth']);
+            exit(1);
+        }
 
         $entity = new PMF_Entity_Tags();
         $entity->setId($id);
         $entity->setName($tag);
 
diff --git a/phpmyfaq/admin/assets/js/tags.js b/phpmyfaq/admin/assets/js/tags.js
index 723332f0d4..a6ae506905 100644
--- a/phpmyfaq/admin/assets/js/tags.js
+++ b/phpmyfaq/admin/assets/js/tags.js
@@ -27,36 +27,37 @@ $(document).ready(function () {
             span.replaceWith(
                 '<input name="tag" class="form-control" data-tag-id="' + id + '" value="' + span.html() + '">'
             );
         } else {
             var input = $('input[data-tag-id="' + id + '"]');
-            input.replaceWith('<span data-tag-id="' + id + '">' + input.val() + '</span>');
+            input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
         }
     });
 
     $('.tag-form').bind('submit', function (event) {
 
         event.preventDefault();
 
         var input = $('input[data-tag-id]:focus');
         var id = input.data('tag-id');
         var tag = input.val();
+        var csrf = $('input[name=csrf]').val();
 
         $.ajax({
             url: 'index.php?action=ajax&ajax=tags&ajaxaction=update',
             type: 'POST',
-            data: 'id=' + id + '&tag=' + tag,
+            data: 'id=' + id + '&tag=' + tag + '&csrf=' + csrf,
             dataType: 'json',
             beforeSend: function () {
                 $('#saving_data_indicator').html(
                     '<i aria-hidden="true" class="fa fa-spinner fa-spin"></i> Saving ...'
                 );
             },
             success: function (message) {
-                input.replaceWith('<span data-tag-id="' + id + '">' + input.val() + '</span>');
-                $('span[data-tag-id="' + id + '"]').append(' ✓');
-                $('#saving_data_indicator').html('✓ ' + message);
+                input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
+                $('span[data-tag-id="' + id + '"]');
+                $('#saving_data_indicator').html(message);
             }
         });
 
         return false;
     });
diff --git a/phpmyfaq/admin/tags.main.php b/phpmyfaq/admin/tags.main.php
index 07d2400828..a094ddc87a 100644
--- a/phpmyfaq/admin/tags.main.php
+++ b/phpmyfaq/admin/tags.main.php
@@ -36,11 +36,11 @@ if (!defined('IS_VALID_PHPMYFAQ')) {
     </header>
 
     <div class="row">
         <div class="col-lg-12">
             <form action="" method="post" class="tag-form">
-
+                <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">
 <?php
 if ($user->perm->checkRight($user->getUserId(), 'editbt')) {
     $tags = new PMF_Tags($faqConfig);
 
     if ('deletetag' == $action) {
@@ -61,11 +61,11 @@ if ($user->perm->checkRight($user->getUserId(), 'editbt')) {
     echo '<table class="table table-striped">';
     echo '<tbody>';
 
     foreach ($tagData as $key => $tag) {
         echo '<tr>';
-        echo '<td><span data-tag-id="'.$key.'">'.$tag.'</span></td>';
+        echo '<td><span data-tag-id="'.$key.'">'.PMF_String::htmlspecialchars($tag).'</span></td>';
         printf(
             '<td><a class="btn btn-primary btn-edit" data-btn-id="%d" title="%s"><i aria-hidden="true" class="fa fa-edit"></i></a></td>',
             $key,
             $PMF_LANG['ad_user_edit']
         );

Vulnerable code:             input.replaceWith('<span data-tag-id="' + id + '">' + input.val() + '</span>');
            data: 'id=' + id + '&tag=' + tag,
                input.replaceWith('<span data-tag-id="' + id + '">' + input.val() + '</span>');
                $('span[data-tag-id="' + id + '"]').append(' ✓');
                $('#saving_data_indicator').html('✓ ' + message);

        echo '<td><span data-tag-id="'.$key.'">'.$tag.'</span></td>';
Fixed code:         $csrfToken = PMF_Filter::filterInput(INPUT_POST, 'csrf', FILTER_SANITIZE_STRING);

        if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfToken) {
            echo json_encode($PMF_LANG['err_NotAuth']);
            exit(1);
        }
            input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
        var csrf = $('input[name=csrf]').val();
            data: 'id=' + id + '&tag=' + tag + '&csrf=' + csrf,
                input.replaceWith('<span data-tag-id="' + id + '">' + input.val().replace(/\//g, '&#x2F;') + '</span>');
                $('span[data-tag-id="' + id + '"]');
                $('#saving_data_indicator').html(message);
                <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">
        echo '<td><span data-tag-id="'.$key.'">'.PMF_String::htmlspecialchars($tag).'</span></td>';