CVE-2015-8379: Cross-Site Request Forgery (CSRF) (CWE-352)
value: CakePHP 2.x and 3.x before 3.1.5 might allow remote attackers to bypass the CSRF protection mechanism via the _method parameter.
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: 2016-01-26T19:59Z
Repository: https://github.com/cakephp/cakephp
Language: PHP
Files changed: src/Controller/Component/CsrfComponent.php, tests/TestCase/Controller/Component/CsrfComponentTest.php
Commit date: 2015-11-25 22:11:16 -0500
Commit message: Only allow GET, HEAD, OPTIONS to not have CSRF tokens.

This covers cases where bad guys make up fake HTTP methods to trick CSRF
validation.

Update test cases to not muck about in $_SERVER too.

Diff: diff --git a/src/Controller/Component/CsrfComponent.php b/src/Controller/Component/CsrfComponent.php
index 3e4b3f21ac..1824c281de 100644
--- a/src/Controller/Component/CsrfComponent.php
+++ b/src/Controller/Component/CsrfComponent.php
@@ -92,11 +92,11 @@ class CsrfComponent extends Component
         }
 
         if ($request->is('get') && $cookieData === null) {
             $this->_setCookie($request, $response);
         }
-        if ($request->is(['patch', 'put', 'post', 'delete'])) {
+        if (!$request->is(['head', 'get', 'options'])) {
             $this->_validateToken($request);
             unset($request->data[$this->_config['field']]);
         }
     }
 
diff --git a/tests/TestCase/Controller/Component/CsrfComponentTest.php b/tests/TestCase/Controller/Component/CsrfComponentTest.php
index aa611a6eb8..f42aeb5e6b 100644
--- a/tests/TestCase/Controller/Component/CsrfComponentTest.php
+++ b/tests/TestCase/Controller/Component/CsrfComponentTest.php
@@ -59,14 +59,15 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testSettingCookie()
     {
-        $_SERVER['REQUEST_METHOD'] = 'GET';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
-        $controller->request = new Request(['webroot' => '/dir/']);
+        $controller->request = new Request([
+            'environment' => ['REQUEST_METHOD' => 'GET'],
+            'webroot' => '/dir/',
+        ]);
         $controller->response = new Response();
 
         $event = new Event('Controller.startup', $controller);
         $this->component->startup($event);
 
@@ -85,11 +86,11 @@ class CsrfComponentTest extends TestCase
      * @return void
      */
     public static function httpMethodProvider()
     {
         return [
-            ['PATCH'], ['PUT'], ['POST'], ['DELETE']
+            ['PATCH'], ['PUT'], ['POST'], ['DELETE'], ['PURGE'], ['INVALIDMETHOD']
         ];
     }
 
     /**
      * Test that the X-CSRF-Token works with the various http methods.
@@ -98,15 +99,18 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testValidTokenInHeader($method)
     {
-        $_SERVER['REQUEST_METHOD'] = $method;
-        $_SERVER['HTTP_X_CSRF_TOKEN'] = 'testing123';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
-        $controller->request = new Request(['cookies' => ['csrfToken' => 'testing123']]);
+        $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => $method,
+                'HTTP_X_CSRF_TOKEN' => 'testing123',
+            ],
+            'cookies' => ['csrfToken' => 'testing123']
+        ]);
         $controller->response = new Response();
 
         $event = new Event('Controller.startup', $controller);
         $result = $this->component->startup($event);
         $this->assertNull($result, 'No exception means valid.');
@@ -120,15 +124,16 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testInvalidTokenInHeader($method)
     {
-        $_SERVER['REQUEST_METHOD'] = $method;
-        $_SERVER['HTTP_X_CSRF_TOKEN'] = 'nope';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => $method,
+                'HTTP_X_CSRF_TOKEN' => 'nope',
+            ],
             'cookies' => ['csrfToken' => 'testing123']
         ]);
         $controller->response = new Response();
 
         $event = new Event('Controller.startup', $controller);
@@ -142,14 +147,15 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testValidTokenRequestData($method)
     {
-        $_SERVER['REQUEST_METHOD'] = $method;
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => $method,
+            ],
             'post' => ['_csrfToken' => 'testing123'],
             'cookies' => ['csrfToken' => 'testing123']
         ]);
         $controller->response = new Response();
 
@@ -166,14 +172,15 @@ class CsrfComponentTest extends TestCase
      * @expectedException \Cake\Network\Exception\InvalidCsrfTokenException
      * @return void
      */
     public function testInvalidTokenRequestData($method)
     {
-        $_SERVER['REQUEST_METHOD'] = $method;
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => $method,
+            ],
             'post' => ['_csrfToken' => 'nope'],
             'cookies' => ['csrfToken' => 'testing123']
         ]);
         $controller->response = new Response();
 
@@ -187,14 +194,15 @@ class CsrfComponentTest extends TestCase
      * @expectedException \Cake\Network\Exception\InvalidCsrfTokenException
      * @return void
      */
     public function testInvalidTokenRequestDataMissing()
     {
-        $_SERVER['REQUEST_METHOD'] = 'POST';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => 'POST',
+            ],
             'post' => [],
             'cookies' => ['csrfToken' => 'testing123']
         ]);
         $controller->response = new Response();
 
@@ -209,14 +217,15 @@ class CsrfComponentTest extends TestCase
      * @expectedException \Cake\Network\Exception\InvalidCsrfTokenException
      * @return void
      */
     public function testInvalidTokenMissingCookie($method)
     {
-        $_SERVER['REQUEST_METHOD'] = $method;
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => [
+                'REQUEST_METHOD' => $method
+            ],
             'post' => ['_csrfToken' => 'could-be-valid'],
             'cookies' => []
         ]);
         $controller->response = new Response();
 
@@ -230,14 +239,13 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testCsrfValidationSkipsRequestAction()
     {
-        $_SERVER['REQUEST_METHOD'] = 'POST';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => ['REQUEST_METHOD' => 'POST'],
             'params' => ['requested' => 1],
             'post' => ['_csrfToken' => 'nope'],
             'cookies' => ['csrfToken' => 'testing123']
         ]);
         $controller->response = new Response();
@@ -254,14 +262,15 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testConfigurationCookieCreate()
     {
-        $_SERVER['REQUEST_METHOD'] = 'GET';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
-        $controller->request = new Request(['webroot' => '/dir/']);
+        $controller->request = new Request([
+            'environment' => ['REQUEST_METHOD' => 'GET'],
+            'webroot' => '/dir/'
+        ]);
         $controller->response = new Response();
 
         $component = new CsrfComponent($this->registry, [
             'cookieName' => 'token',
             'expiry' => '+1 hour',
@@ -288,14 +297,13 @@ class CsrfComponentTest extends TestCase
      * @return void
      * @triggers Controller.startup $controller
      */
     public function testConfigurationValidate()
     {
-        $_SERVER['REQUEST_METHOD'] = 'POST';
-
         $controller = $this->getMock('Cake\Controller\Controller', ['redirect']);
         $controller->request = new Request([
+            'environment' => ['REQUEST_METHOD' => 'POST'],
             'cookies' => ['csrfToken' => 'nope', 'token' => 'yes'],
             'post' => ['_csrfToken' => 'no match', 'token' => 'yes'],
         ]);
         $controller->response = new Response();
 

Vulnerable code:         if ($request->is(['patch', 'put', 'post', 'delete'])) {
        $_SERVER['REQUEST_METHOD'] = 'GET';

        $controller->request = new Request(['webroot' => '/dir/']);
            ['PATCH'], ['PUT'], ['POST'], ['DELETE']
        $_SERVER['REQUEST_METHOD'] = $method;
        $_SERVER['HTTP_X_CSRF_TOKEN'] = 'testing123';

        $controller->request = new Request(['cookies' => ['csrfToken' => 'testing123']]);
        $_SERVER['REQUEST_METHOD'] = $method;
        $_SERVER['HTTP_X_CSRF_TOKEN'] = 'nope';

        $_SERVER['REQUEST_METHOD'] = $method;

        $_SERVER['REQUEST_METHOD'] = $method;

        $_SERVER['REQUEST_METHOD'] = 'POST';

        $_SERVER['REQUEST_METHOD'] = $method;

        $_SERVER['REQUEST_METHOD'] = 'POST';

        $_SERVER['REQUEST_METHOD'] = 'GET';

        $controller->request = new Request(['webroot' => '/dir/']);
        $_SERVER['REQUEST_METHOD'] = 'POST';

Fixed code:         if (!$request->is(['head', 'get', 'options'])) {
        $controller->request = new Request([
            'environment' => ['REQUEST_METHOD' => 'GET'],
            'webroot' => '/dir/',
        ]);
            ['PATCH'], ['PUT'], ['POST'], ['DELETE'], ['PURGE'], ['INVALIDMETHOD']
        $controller->request = new Request([
            'environment' => [
                'REQUEST_METHOD' => $method,
                'HTTP_X_CSRF_TOKEN' => 'testing123',
            ],
            'cookies' => ['csrfToken' => 'testing123']
        ]);
            'environment' => [
                'REQUEST_METHOD' => $method,
                'HTTP_X_CSRF_TOKEN' => 'nope',
            ],
            'environment' => [
                'REQUEST_METHOD' => $method,
            ],
            'environment' => [
                'REQUEST_METHOD' => $method,
            ],
            'environment' => [
                'REQUEST_METHOD' => 'POST',
            ],
            'environment' => [
                'REQUEST_METHOD' => $method
            ],
            'environment' => ['REQUEST_METHOD' => 'POST'],
        $controller->request = new Request([
            'environment' => ['REQUEST_METHOD' => 'GET'],
            'webroot' => '/dir/'
        ]);
            'environment' => ['REQUEST_METHOD' => 'POST'],