CVE-2024-22208: Incorrect Authorization (CWE-863)
value: phpMyFAQ is an Open Source FAQ web application for PHP 8.1+ and MySQL, PostgreSQL and other databases. The 'sharing FAQ' functionality allows any unauthenticated actor to misuse the phpMyFAQ application to send arbitrary emails to a large range of targets. The phpMyFAQ application has a functionality where anyone can share a FAQ item to others. The front-end of this functionality allows any phpMyFAQ articles to be shared with 5 email addresses. Any unauthenticated actor can perform this action. There is a CAPTCHA in place, however the amount of people you email with a single request is not limited to 5 by the backend. An attacker can thus solve a single CAPTCHA and send thousands of emails at once. An attacker can utilize the target application's email server to send phishing messages. This can get the server on a blacklist, causing all emails to end up in spam. It can also lead to reputation damages. This issue has been patched in version 3.2.5.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 6.5
Published: 2024-02-05T21:15Z
Repository: https://github.com/thorsten/phpMyFAQ
Language: PHP
Files changed: phpmyfaq/api.service.php, phpmyfaq/assets/themes/default/templates/send2friend.html, phpmyfaq/send2friend.php
Commit date: 2024-01-12 17:41:39 +0100
Commit message: fix: only allow 5 mails per user, don't allow the user to change the URL

Diff: diff --git a/phpmyfaq/api.service.php b/phpmyfaq/api.service.php
index 1b7848f5ce..d237171c39 100644
--- a/phpmyfaq/api.service.php
+++ b/phpmyfaq/api.service.php
@@ -769,18 +769,34 @@ switch ($action) {
     case 'sendtofriends':
         $postData = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
 
         $author = trim((string) Filter::filterVar($postData['name'], FILTER_SANITIZE_SPECIAL_CHARS));
         $email = Filter::filterVar($postData['email'], FILTER_VALIDATE_EMAIL);
-        $link = trim((string) Filter::filterVar($postData['link'], FILTER_VALIDATE_URL));
         $attached = trim((string) Filter::filterVar($postData['message'], FILTER_SANITIZE_SPECIAL_CHARS));
         $mailto = Filter::filterArray($postData['mailto[]']);
 
+        $faqLanguage = trim((string) Filter::filterVar($postData['lang'], FILTER_SANITIZE_SPECIAL_CHARS));
+        $faqId = trim((string) Filter::filterVar($postData['faqId'], FILTER_VALIDATE_INT));
+        $categoryId = trim((string) Filter::filterVar($postData['categoryId'], FILTER_VALIDATE_INT));
+
+        if (is_array($mailto) && count($mailto) > 5) {
+            $response->setStatusCode(Response::HTTP_BAD_REQUEST);
+            $response->setData(['error' => Translation::get('err_sendMail')]);
+            break;
+        }
+
         if (
             !is_null($author) && !is_null($email) && is_array($mailto) &&
             $stopWords->checkBannedWord(Strings::htmlspecialchars($attached))
         ) {
+            $send2friendLink = sprintf(
+                '%sindex.php?action=faq&amp;cat=%d&amp;id=%d&amp;artlang=%s',
+                $faqConfig->getDefaultUrl(),
+                $categoryId,
+                $faqId,
+                urlencode($faqLanguage)
+            );
 
             foreach ($mailto as $recipient) {
                 $recipient = trim(strip_tags((string) $recipient));
                 if (!empty($recipient)) {
                     $mailer = new Mail($faqConfig);
@@ -794,12 +810,12 @@ switch ($action) {
                     $mailer->subject = Translation::get('msgS2FMailSubject') . $author;
                     $mailer->message = sprintf(
                         "%s\r\n\r\n%s\r\n%s\r\n\r\n%s",
                         $faqConfig->get('main.send2friendText'),
                         Translation::get('msgS2FText2'),
-                        $link,
-                        $attached
+                        $send2friendLink,
+                        strip_tags($attached)
                     );
 
                     // Send the email
                     $result = $mailer->send();
                     unset($mailer);
diff --git a/phpmyfaq/assets/themes/default/templates/send2friend.html b/phpmyfaq/assets/themes/default/templates/send2friend.html
index 1fc4eab7f5..d5f349245c 100644
--- a/phpmyfaq/assets/themes/default/templates/send2friend.html
+++ b/phpmyfaq/assets/themes/default/templates/send2friend.html
@@ -7,12 +7,14 @@
       <div id="faqs"></div>
     </div>
   </div>
 
   <form id="formValues" action="#" method="post" accept-charset="utf-8" class="needs-validation" novalidate>
-    <input type="hidden" name="{{ msgS2FReferrer }}" value="{{ send2friendLink }}" />
-    <input type="hidden" name="lang" id="lang" value="{{ lang }}" />
+    <input type="hidden" name="{{ msgS2FReferrer }}" value="{{ send2friendLink }}">
+    <input type="hidden" name="lang" id="lang" value="{{ lang }}">
+    <input type="hidden" name="faqId" id="faqId" value="{{ faqId }}">
+    <input type="hidden" name="categoryId" id="categoryId" value="{{ categoryId }}">
 
     <div class="row mb-2">
       <label class="col-sm-3 form-control-label" for="name">{{ msgS2FName }}</label>
       <div class="col-sm-9">
         <input type="text" class="form-control" name="name" id="name" value="{{ defaultContentName }}" required />
diff --git a/phpmyfaq/send2friend.php b/phpmyfaq/send2friend.php
index 8d32ebea07..fdd31cc365 100644
--- a/phpmyfaq/send2friend.php
+++ b/phpmyfaq/send2friend.php
@@ -69,10 +69,12 @@ $send2friendLink = sprintf(
 
 $template->parse(
     'mainPageContent',
     [
         'lang' => Strings::htmlentities($faqLanguage),
+        'faqId' => $faqId,
+        'categoryId' => $faqCategory,
         'msgSend2Friend' => Translation::get('msgSend2Friend'),
         'msgS2FReferrer' => 'link',
         'msgS2FName' => Translation::get('msgS2FName'),
         'msgS2FEMail' => Translation::get('msgS2FEMail'),
         'defaultContentMail' => ($user->getUserId() > 0) ? $user->getUserData('email') : '',

Vulnerable code:         $link = trim((string) Filter::filterVar($postData['link'], FILTER_VALIDATE_URL));
                        $link,
                        $attached
    <input type="hidden" name="{{ msgS2FReferrer }}" value="{{ send2friendLink }}" />
    <input type="hidden" name="lang" id="lang" value="{{ lang }}" />
Fixed code:         $faqLanguage = trim((string) Filter::filterVar($postData['lang'], FILTER_SANITIZE_SPECIAL_CHARS));
        $faqId = trim((string) Filter::filterVar($postData['faqId'], FILTER_VALIDATE_INT));
        $categoryId = trim((string) Filter::filterVar($postData['categoryId'], FILTER_VALIDATE_INT));

        if (is_array($mailto) && count($mailto) > 5) {
            $response->setStatusCode(Response::HTTP_BAD_REQUEST);
            $response->setData(['error' => Translation::get('err_sendMail')]);
            break;
        }

            $send2friendLink = sprintf(
                '%sindex.php?action=faq&amp;cat=%d&amp;id=%d&amp;artlang=%s',
                $faqConfig->getDefaultUrl(),
                $categoryId,
                $faqId,
                urlencode($faqLanguage)
            );
                        $send2friendLink,
                        strip_tags($attached)
    <input type="hidden" name="{{ msgS2FReferrer }}" value="{{ send2friendLink }}">
    <input type="hidden" name="lang" id="lang" value="{{ lang }}">
    <input type="hidden" name="faqId" id="faqId" value="{{ faqId }}">
    <input type="hidden" name="categoryId" id="categoryId" value="{{ categoryId }}">
        'faqId' => $faqId,
        'categoryId' => $faqCategory,