CVE-2022-4397: Cross-Site Request Forgery (CSRF) (CWE-352)
value: A vulnerability was found in morontt zend-blog-number-2. It has been classified as problematic. Affected is an unknown function of the file application/forms/Comment.php of the component Comment Handler. The manipulation leads to cross-site request forgery. It is possible to launch the attack remotely. The name of the patch is 36b2d4abe20a6245e4f8df7a4b14e130b24d429d. It is recommended to apply a patch to fix this issue. VDB-215250 is the identifier assigned to this vulnerability.
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: 6.5
Published: 2022-12-10T19:15Z
Repository: https://github.com/morontt/zend-blog-number-2
Language: PHP
Files changed: application/controllers/IndexController.php, application/forms/Comment.php, application/views/scripts/index/formcomment.phtml
Commit date: 2022-06-23 20:26:53 +0300
Commit message: Add CSRF-token for comments

Diff: diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php
index cd3b2e3..dd967fc 100644
--- a/application/controllers/IndexController.php
+++ b/application/controllers/IndexController.php
@@ -152,10 +152,11 @@ class IndexController extends Zend_Controller_Action
 
         $this->view->browsertitle = $post->title;
 
         $form = new Application_Form_Comment();
         $form->topicId->setValue($post->id);
+        $form->getElement('csrfToken')->setValue($this->generateCommentToken());
 
         $this->view->form = $form;
 
         $tracking = new Application_Model_Tracking();
         $tracking->checkTrackingEvent($post->id, $this->_request->getClientIp());
@@ -232,11 +233,14 @@ RAW;
             $form = new Application_Form_Comment();
 
             if ($form->isValid($this->getRequest()->getPost())) {
                 $formData = $form->getValues();
 
-                $this->saveComment($topicId, $url, $formData);
+                if ($this->validCommentToken($formData['csrfToken'])) {
+                    $this->saveComment($topicId, $url, $formData);
+                }
+
                 $result['valid'] = true;
             } else {
                 $formView = new Zend_View;
                 $formView->setScriptPath(APPLICATION_PATH . "/views/scripts/index");
                 $formView->setHelperPath(APPLICATION_PATH . "/views/helpers");
@@ -411,6 +415,31 @@ RAW;
     {
         return !empty($_SERVER['HTTP_VIA'])
             && (stripos($_SERVER['HTTP_VIA'], 'BunnyCDN') !== false
                 || strpos($_SERVER['HTTP_VIA'], 'cdn77') !== false);
     }
+
+    private function generateCommentToken($time = null): string
+    {
+        $time = $time ?? time();
+        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+        return base64_encode($time . ':' . hash('md5', 'MD5_' . $userAgent . $time, true));
+    }
+
+    private function validCommentToken($token): bool
+    {
+        $raw = base64_decode($token, true);
+        if ($raw === false) {
+            return false;
+        }
+
+        $position = strpos($raw, ':');
+        if ($position === false) {
+            return false;
+        }
+
+        $time = substr($raw, 0, $position);
+
+        return hash_equals($this->generateCommentToken($time), $token);
+    }
 }
diff --git a/application/forms/Comment.php b/application/forms/Comment.php
index 6c6095e..f4afeea 100644
--- a/application/forms/Comment.php
+++ b/application/forms/Comment.php
@@ -83,10 +83,14 @@ class Application_Form_Comment extends Zend_Form
 
         if (!Zend_Auth::getInstance()->hasIdentity()) {
             $this->checkCookie();
         }
 
+        $csrfToken = new Zend_Form_Element_Hidden('csrfToken');
+        $csrfToken->addValidator('NotEmpty');
+        $this->addElement($csrfToken);
+
         $this->setElementDecorators($this->elementDecorators);
     }
 
     protected function checkCookie()
     {
diff --git a/application/views/scripts/index/formcomment.phtml b/application/views/scripts/index/formcomment.phtml
index 2802637..e9b3ea3 100644
--- a/application/views/scripts/index/formcomment.phtml
+++ b/application/views/scripts/index/formcomment.phtml
@@ -15,10 +15,11 @@
     </div>
     <div style="display: none;">
         <?php echo $this->form->topicId; ?>
         <?php echo $this->form->parentId; ?>
         <?php echo $this->form->cookie; ?>
+        <?php echo $this->form->csrfToken; ?>
     </div>
     <div class="ym-fbox-button">
         <button type="submit" class="ym-button ym-add" id="submit" name="submit">Добавить комментарий</button>
     </div>
 </form>

Vulnerable code:                 $this->saveComment($topicId, $url, $formData);
Fixed code:         $form->getElement('csrfToken')->setValue($this->generateCommentToken());
                if ($this->validCommentToken($formData['csrfToken'])) {
                    $this->saveComment($topicId, $url, $formData);
                }


    private function generateCommentToken($time = null): string
    {
        $time = $time ?? time();
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        return base64_encode($time . ':' . hash('md5', 'MD5_' . $userAgent . $time, true));
    }

    private function validCommentToken($token): bool
    {
        $raw = base64_decode($token, true);
        if ($raw === false) {
            return false;
        }

        $position = strpos($raw, ':');
        if ($position === false) {
            return false;
        }

        $time = substr($raw, 0, $position);

        return hash_equals($this->generateCommentToken($time), $token);
    }
        $csrfToken = new Zend_Form_Element_Hidden('csrfToken');
        $csrfToken->addValidator('NotEmpty');
        $this->addElement($csrfToken);

        <?php echo $this->form->csrfToken; ?>