CsrfComponent.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Controller\Component;
  16. use Cake\Controller\Component;
  17. use Cake\Core\Configure;
  18. use Cake\Error\ForbiddenException;
  19. use Cake\Event\Event;
  20. use Cake\Network\Request;
  21. use Cake\Network\Response;
  22. use Cake\Utility\Security;
  23. use Cake\Utility\String;
  24. /**
  25. * Provides CSRF protection & validation.
  26. *
  27. * This component adds a CSRF token to a cookie. The cookie value is compared to
  28. * request data, or the X-CSRF-Token header on each PATCH, POST,
  29. * PUT, or DELETE request.
  30. *
  31. * If the request data is missing or does not match the cookie data,
  32. * a ForbiddenException will be raised.
  33. *
  34. * This component integrates with the FormHelper automatically and when
  35. * used together your forms will have CSRF tokens automatically added
  36. * when `$this->Form->create(...)` is used in a view.
  37. */
  38. class CsrfComponent extends Component {
  39. /**
  40. * Default config for the CSRF handling.
  41. *
  42. * - cookieName = The name of the cookie to send.
  43. * - expiry = How long the CSRF token should last. Defaults to browser session.
  44. * - secure = Whether or not the cookie will be set with the Secure flag. Defaults to false.
  45. * - field = The form field to check. Changing this will also require configuring
  46. * FormHelper.
  47. *
  48. * @var array
  49. */
  50. protected $_defaultConfig = [
  51. 'cookieName' => 'csrfToken',
  52. 'expiry' => 0,
  53. 'secure' => false,
  54. 'field' => '_csrfToken',
  55. ];
  56. /**
  57. * Startup callback.
  58. *
  59. * Validates the CSRF token for POST data. If
  60. * the request is a GET request, and the cookie value is absent a cookie will be set.
  61. *
  62. * Once a cookie is set it will be copied into request->params['_csrfToken']
  63. * so that application and framework code can easily access the csrf token.
  64. *
  65. * RequestAction requests do not get checked, nor will
  66. * they set a cookie should it be missing.
  67. *
  68. * @param \Cake\Event\Event $event Event instance.
  69. * @return void
  70. */
  71. public function startup(Event $event) {
  72. $controller = $event->subject();
  73. $request = $controller->request;
  74. $response = $controller->response;
  75. $cookieName = $this->_config['cookieName'];
  76. $cookieData = $request->cookie($cookieName);
  77. if ($cookieData) {
  78. $request->params['_csrfToken'] = $cookieData;
  79. }
  80. if ($request->is('requested')) {
  81. return;
  82. }
  83. if ($request->is('get') && $cookieData === null) {
  84. $this->_setCookie($request, $response);
  85. }
  86. if ($request->is(['patch', 'put', 'post', 'delete'])) {
  87. $this->_validateToken($request);
  88. }
  89. }
  90. /**
  91. * Events supported by this component.
  92. *
  93. * @return array
  94. */
  95. public function implementedEvents() {
  96. return [
  97. 'Controller.startup' => 'startup',
  98. ];
  99. }
  100. /**
  101. * Set the cookie in the response.
  102. *
  103. * Also sets the request->params['_csrfToken'] so the newly minted
  104. * token is available in the request data.
  105. *
  106. * @param \Cake\Network\Request $request The request object.
  107. * @param \Cake\Network\Response $response The response object.
  108. * @return void
  109. */
  110. protected function _setCookie(Request $request, Response $response) {
  111. $value = Security::hash(String::uuid(), 'sha1', true);
  112. $request->params['_csrfToken'] = $value;
  113. $response->cookie([
  114. 'name' => $this->_config['cookieName'],
  115. 'value' => $value,
  116. 'expiry' => $this->_config['expiry'],
  117. 'path' => $request->base,
  118. 'secure' => $this->_config['secure'],
  119. ]);
  120. }
  121. /**
  122. * Validate the request data against the cookie token.
  123. *
  124. * @param \Cake\Network\Request $request The request to validate against.
  125. * @throws \Cake\Error\ForbiddenException when the CSRF token is invalid or missing.
  126. * @return void
  127. */
  128. protected function _validateToken(Request $request) {
  129. $cookie = $request->cookie($this->_config['cookieName']);
  130. $post = $request->data($this->_config['field']);
  131. $header = $request->header('X-CSRF-Token');
  132. if ($post !== $cookie && $header !== $cookie) {
  133. throw new ForbiddenException(__d('cake', 'Invalid CSRF token.'));
  134. }
  135. }
  136. }