CVE-2017-15729: Cross-Site Request Forgery (CSRF) (CWE-352)
value: In phpMyFAQ before 2.9.9, there is Cross-Site Request Forgery (CSRF) for adding a glossary.
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 8.8
Published: 2017-10-22T18:29Z
Repository: https://github.com/thorsten/phpMyFAQ
Language: PHP
Files changed: phpmyfaq/admin/glossary.add.php, phpmyfaq/admin/glossary.edit.php, phpmyfaq/admin/glossary.main.php
Commit date: 2017-10-08 11:27:18 +0200
Commit message: Added missing CSRF protection

Diff: diff --git a/phpmyfaq/admin/glossary.add.php b/phpmyfaq/admin/glossary.add.php
index e7fbe5b7a..0e3be840a 100644
--- a/phpmyfaq/admin/glossary.add.php
+++ b/phpmyfaq/admin/glossary.add.php
@@ -39,10 +39,12 @@ if (!defined('IS_VALID_PHPMYFAQ')) {
 
 <?php
 if ($user->perm->checkRight($user->getUserId(), 'addglossary')) {
     ?>
                 <form class="form-horizontal" action="?action=saveglossary" method="post" accept-charset="utf-8">
+                    <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">
+
                     <div class="form-group">
                         <label class="col-lg-2 control-label" for="item"><?php echo $PMF_LANG['ad_glossary_item'];
     ?>:</label>
                         <div class="col-lg-4">
                             <input class="form-control" type="text" name="item" id="item" required />
diff --git a/phpmyfaq/admin/glossary.edit.php b/phpmyfaq/admin/glossary.edit.php
index 304896a66..5ce20fc4c 100644
--- a/phpmyfaq/admin/glossary.edit.php
+++ b/phpmyfaq/admin/glossary.edit.php
@@ -41,10 +41,11 @@ if ($user->perm->checkRight($user->getUserId(), 'editglossary')) {
     $id = PMF_Filter::filterInput(INPUT_GET, 'id', FILTER_VALIDATE_INT);
     $glossary = new PMF_Glossary($faqConfig);
     $glossaryItem = $glossary->getGlossaryItem($id);
     ?>
                 <form class="form-horizontal" action="?action=updateglossary" method="post" accept-charset="utf-8">
+                    <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">
                     <input type="hidden" name="id" value="<?php echo $glossaryItem['id'];
     ?>" />
                     <div class="form-group">
                         <label class="col-lg-2 control-label" for="item"><?php echo $PMF_LANG['ad_glossary_item'];
     ?>:</label>
diff --git a/phpmyfaq/admin/glossary.main.php b/phpmyfaq/admin/glossary.main.php
index 081f10b05..2941bb886 100644
--- a/phpmyfaq/admin/glossary.main.php
+++ b/phpmyfaq/admin/glossary.main.php
@@ -7,15 +7,13 @@
  * This Source Code Form is subject to the terms of the Mozilla Public License,
  * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  * obtain one at http://mozilla.org/MPL/2.0/.
  *
  * @category  phpMyFAQ
- *
  * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
  * @copyright 2005-2017 phpMyFAQ Team
  * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
- *
  * @link      http://www.phpmyfaq.de
  * @since     2005-09-15
  */
 if (!defined('IS_VALID_PHPMYFAQ')) {
     $protocol = 'http';
@@ -40,16 +38,30 @@ if (!defined('IS_VALID_PHPMYFAQ')) {
         </header>
 
         <div class="row">
             <div class="col-lg-12">
 <?php
+
+$csrfTokenFromPost = PMF_Filter::filterInput(INPUT_POST, 'csrf', FILTER_SANITIZE_STRING);
+$csrfTokenFromGet = PMF_Filter::filterInput(INPUT_GET, 'csrf', FILTER_SANITIZE_STRING);
+if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfTokenFromPost) {
+    $csrfCheck = false;
+} else {
+    $csrfCheck = true;
+}
+if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfTokenFromGet) {
+    $csrfCheckDelete = false;
+} else {
+    $csrfCheckDelete = true;
+}
+
 if ($user->perm->checkRight($user->getUserId(), 'addglossary') ||
     $user->perm->checkRight($user->getUserId(), 'editglossary') ||
     $user->perm->checkRight($user->getUserId(), 'delglossary')) {
     $glossary = new PMF_Glossary($faqConfig);
 
-    if ('saveglossary' == $action && $user->perm->checkRight($user->getUserId(), 'addglossary')) {
+    if ('saveglossary' == $action && $user->perm->checkRight($user->getUserId(), 'addglossary') && $csrfCheck) {
         $item = PMF_Filter::filterInput(INPUT_POST, 'item', FILTER_SANITIZE_SPECIAL_CHARS);
         $definition = PMF_Filter::filterInput(INPUT_POST, 'definition', FILTER_SANITIZE_SPECIAL_CHARS);
         if ($glossary->addGlossaryItem($item, $definition)) {
             echo '<p class="alert alert-success"><a href="#" class="close" data-dismiss="alert">×</a>';
             echo $PMF_LANG['ad_glossary_save_success'].'</p>';
@@ -59,11 +71,11 @@ if ($user->perm->checkRight($user->getUserId(), 'addglossary') ||
             echo '<br />'.$PMF_LANG['ad_adus_dberr'].'<br />';
             echo $faqConfig->getDb()->error().'</p>';
         }
     }
 
-    if ('updateglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary')) {
+    if ('updateglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary') && $csrfCheck) {
         $id = PMF_Filter::filterInput(INPUT_POST, 'id', FILTER_VALIDATE_INT);
         $item = PMF_Filter::filterInput(INPUT_POST, 'item', FILTER_SANITIZE_SPECIAL_CHARS);
         $definition = PMF_Filter::filterInput(INPUT_POST, 'definition', FILTER_SANITIZE_SPECIAL_CHARS);
         if ($glossary->updateGlossaryItem($id, $item, $definition)) {
             echo '<p class="alert alert-success"><a href="#" class="close" data-dismiss="alert">×</a>';
@@ -74,11 +86,11 @@ if ($user->perm->checkRight($user->getUserId(), 'addglossary') ||
             echo '<br />'.$PMF_LANG['ad_adus_dberr'].'<br />';
             echo $faqConfig->getDb()->error().'</p>';
         }
     }
 
-    if ('deleteglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary')) {
+    if ('deleteglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary') && $csrfCheckDelete) {
         $id = PMF_Filter::filterInput(INPUT_GET, 'id', FILTER_VALIDATE_INT);
         if ($glossary->deleteGlossaryItem($id)) {
             echo '<p class="alert alert-success"><a href="#" class="close" data-dismiss="alert">×</a>';
             echo $PMF_LANG['ad_glossary_delete_success'].'</p>';
         } else {
@@ -109,14 +121,16 @@ if ($user->perm->checkRight($user->getUserId(), 'addglossary') ||
         printf(
             '<td>%s</td>',
             $items['definition']
         );
         printf(
-            '<td><a class="btn btn-danger" onclick="return confirm(\'%s\'); return false;" href="%s%d">',
+            '<td><a class="btn btn-danger" onclick="return confirm(\'%s\'); return false;" href="%s%d%s%s">',
             $PMF_LANG['ad_user_del_3'],
             '?action=deleteglossary&amp;id=',
-            $items['id']
+            $items['id'],
+            '&csrf=',
+            $user->getCsrfTokenFromSession()
         );
         printf(
             '<span title="%s"><i aria-hidden="true" class="fa fa-trash-o"></i></span></a></td>',
             $PMF_LANG['ad_entry_delete']
         );

Vulnerable code:  *
 *
    if ('saveglossary' == $action && $user->perm->checkRight($user->getUserId(), 'addglossary')) {
    if ('updateglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary')) {
    if ('deleteglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary')) {
            '<td><a class="btn btn-danger" onclick="return confirm(\'%s\'); return false;" href="%s%d">',
            $items['id']
Fixed code:                     <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">

                    <input type="hidden" name="csrf" value="<?php echo $user->getCsrfTokenFromSession() ?>">

$csrfTokenFromPost = PMF_Filter::filterInput(INPUT_POST, 'csrf', FILTER_SANITIZE_STRING);
$csrfTokenFromGet = PMF_Filter::filterInput(INPUT_GET, 'csrf', FILTER_SANITIZE_STRING);
if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfTokenFromPost) {
    $csrfCheck = false;
} else {
    $csrfCheck = true;
}
if (!isset($_SESSION['phpmyfaq_csrf_token']) || $_SESSION['phpmyfaq_csrf_token'] !== $csrfTokenFromGet) {
    $csrfCheckDelete = false;
} else {
    $csrfCheckDelete = true;
}

    if ('saveglossary' == $action && $user->perm->checkRight($user->getUserId(), 'addglossary') && $csrfCheck) {
    if ('updateglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary') && $csrfCheck) {
    if ('deleteglossary' == $action && $user->perm->checkRight($user->getUserId(), 'editglossary') && $csrfCheckDelete) {
            '<td><a class="btn btn-danger" onclick="return confirm(\'%s\'); return false;" href="%s%d%s%s">',
            $items['id'],
            '&csrf=',
            $user->getCsrfTokenFromSession()