CsrfComponent.php 4.3 KB

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