CVE-2017-16244: Cross-Site Request Forgery (CSRF) (CWE-352)
value: Cross-Site Request Forgery exists in OctoberCMS 1.0.426 (aka Build 426) due to improper validation of CSRF tokens for postback handling, allowing an attacker to successfully take over the victim's account. The attack bypasses a protection mechanism involving X-CSRF headers and CSRF tokens via a certain _handler postback variable.
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-11-01T01:29Z
Repository: https://github.com/octobercms/october
Language: PHP
Files changed: config/cms.php, modules/cms/classes/Controller.php
Commit date: 2017-10-30 09:00:17 +1100
Commit message: Implement CSRF token by default
Implement CSRF protection on CMS for postback handling

Diff: diff --git a/config/cms.php b/config/cms.php
index e6f7c32b9..5c35ca4f3 100644
--- a/config/cms.php
+++ b/config/cms.php
@@ -330,11 +330,11 @@ return [
     | If the CSRF protection is enabled, all "postback" requests are checked
     | for a valid security token.
     |
     */
 
-    'enableCsrfProtection' => false,
+    'enableCsrfProtection' => true,
 
     /*
     |--------------------------------------------------------------------------
     | Force bytecode invalidation
     |--------------------------------------------------------------------------
diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php
index b2b7852c6..971da3f41 100644
--- a/modules/cms/classes/Controller.php
+++ b/modules/cms/classes/Controller.php
@@ -335,10 +335,11 @@ class Controller
          * Execute postback handler
          */
         if (
             $useAjax &&
             ($handler = post('_handler')) &&
+            ($this->verifyCsrfToken()) &&
             ($handlerResponse = $this->runAjaxHandler($handler)) &&
             $handlerResponse !== true
         ) {
             return $handlerResponse;
         }
@@ -1353,6 +1354,34 @@ class Controller
                 $component->setProperty($propertyName, $newPropertyValue);
                 $component->setExternalPropertyName($propertyName, $paramName);
             }
         }
     }
+
+    //
+    // Security
+    //
+
+    /**
+     * Checks the request data / headers for a valid CSRF token.
+     * Returns false if a valid token is not found. Override this
+     * method to disable the check.
+     * @return bool
+     */
+    protected function verifyCsrfToken()
+    {
+        if (!Config::get('cms.enableCsrfProtection')) {
+            return true;
+        }
+
+        if (in_array(Request::method(), ['HEAD', 'GET', 'OPTIONS'])) {
+            return true;
+        }
+
+        $token = Request::input('_token') ?: Request::header('X-CSRF-TOKEN');
+
+        return hash_equals(
+            Session::token(),
+            $token
+        );
+    }
 }

Vulnerable code:     'enableCsrfProtection' => false,
Fixed code:     'enableCsrfProtection' => true,
            ($this->verifyCsrfToken()) &&

    //
    // Security
    //

    /**
     * Checks the request data / headers for a valid CSRF token.
     * Returns false if a valid token is not found. Override this
     * method to disable the check.
     * @return bool
     */
    protected function verifyCsrfToken()
    {
        if (!Config::get('cms.enableCsrfProtection')) {
            return true;
        }

        if (in_array(Request::method(), ['HEAD', 'GET', 'OPTIONS'])) {
            return true;
        }

        $token = Request::input('_token') ?: Request::header('X-CSRF-TOKEN');

        return hash_equals(
            Session::token(),
            $token
        );
    }