1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Controller\Component;
16:
17: use Cake\Controller\Component;
18: use Cake\Event\Event;
19: use Cake\Http\Cookie\Cookie;
20: use Cake\Http\Exception\InvalidCsrfTokenException;
21: use Cake\Http\Response;
22: use Cake\Http\ServerRequest;
23: use Cake\I18n\Time;
24: use Cake\Utility\Security;
25:
26: /**
27: * Provides CSRF protection & validation.
28: *
29: * This component adds a CSRF token to a cookie. The cookie value is compared to
30: * request data, or the X-CSRF-Token header on each PATCH, POST,
31: * PUT, or DELETE request.
32: *
33: * If the request data is missing or does not match the cookie data,
34: * an InvalidCsrfTokenException will be raised.
35: *
36: * This component integrates with the FormHelper automatically and when
37: * used together your forms will have CSRF tokens automatically added
38: * when `$this->Form->create(...)` is used in a view.
39: *
40: * @deprecated 3.5.0 Use Cake\Http\Middleware\CsrfProtectionMiddleware instead.
41: */
42: class CsrfComponent extends Component
43: {
44:
45: /**
46: * Default config for the CSRF handling.
47: *
48: * - cookieName = The name of the cookie to send.
49: * - expiry = How long the CSRF token should last. Defaults to browser session.
50: * - secure = Whether or not the cookie will be set with the Secure flag. Defaults to false.
51: * - httpOnly = Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
52: * - field = The form field to check. Changing this will also require configuring
53: * FormHelper.
54: *
55: * @var array
56: */
57: protected $_defaultConfig = [
58: 'cookieName' => 'csrfToken',
59: 'expiry' => 0,
60: 'secure' => false,
61: 'httpOnly' => false,
62: 'field' => '_csrfToken',
63: ];
64:
65: /**
66: * Warn if CsrfComponent is used together with CsrfProtectionMiddleware
67: *
68: * @param array $config The config data.
69: * @return void
70: */
71: public function initialize(array $config)
72: {
73: if ($this->getController()->getRequest()->getParam('_csrfToken') !== false) {
74: deprecationWarning('Loading CsrfComponent while CsrfProtectionMiddleware is active ' .
75: 'will corrupt CSRF data and form submitting will fail.');
76: }
77: }
78:
79: /**
80: * Startup callback.
81: *
82: * Validates the CSRF token for POST data. If
83: * the request is a GET request, and the cookie value is absent a cookie will be set.
84: *
85: * Once a cookie is set it will be copied into request->getParam('_csrfToken')
86: * so that application and framework code can easily access the csrf token.
87: *
88: * RequestAction requests do not get checked, nor will
89: * they set a cookie should it be missing.
90: *
91: * @param \Cake\Event\Event $event Event instance.
92: * @return void
93: */
94: public function startup(Event $event)
95: {
96: /** @var \Cake\Controller\Controller $controller */
97: $controller = $event->getSubject();
98: $request = $controller->getRequest();
99: $response = $controller->getResponse();
100: $cookieName = $this->_config['cookieName'];
101:
102: $cookieData = $request->getCookie($cookieName);
103: if ($cookieData) {
104: $request = $request->withParam('_csrfToken', $cookieData);
105: }
106:
107: if ($request->is('requested')) {
108: $controller->setRequest($request);
109:
110: return;
111: }
112:
113: if ($request->is('get') && $cookieData === null) {
114: list($request, $response) = $this->_setCookie($request, $response);
115: $controller->setResponse($response);
116: }
117: if ($request->is(['put', 'post', 'delete', 'patch']) || $request->getData()) {
118: $this->_validateToken($request);
119: $request = $request->withoutData($this->_config['field']);
120: }
121: $controller->setRequest($request);
122: }
123:
124: /**
125: * Events supported by this component.
126: *
127: * @return array
128: */
129: public function implementedEvents()
130: {
131: return [
132: 'Controller.startup' => 'startup',
133: ];
134: }
135:
136: /**
137: * Set the cookie in the response.
138: *
139: * Also sets the request->params['_csrfToken'] so the newly minted
140: * token is available in the request data.
141: *
142: * @param \Cake\Http\ServerRequest $request The request object.
143: * @param \Cake\Http\Response $response The response object.
144: * @return array An array of the modified request, response.
145: */
146: protected function _setCookie(ServerRequest $request, Response $response)
147: {
148: $expiry = new Time($this->_config['expiry']);
149: $value = hash('sha512', Security::randomBytes(16), false);
150:
151: $request = $request->withParam('_csrfToken', $value);
152:
153: $cookie = new Cookie(
154: $this->_config['cookieName'],
155: $value,
156: $expiry,
157: $request->getAttribute('webroot'),
158: '',
159: (bool)$this->_config['secure'],
160: (bool)$this->_config['httpOnly']
161: );
162:
163: $response = $response->withCookie($cookie);
164:
165: return [$request, $response];
166: }
167:
168: /**
169: * Validate the request data against the cookie token.
170: *
171: * @param \Cake\Http\ServerRequest $request The request to validate against.
172: * @throws \Cake\Http\Exception\InvalidCsrfTokenException when the CSRF token is invalid or missing.
173: * @return void
174: */
175: protected function _validateToken(ServerRequest $request)
176: {
177: $cookie = $request->getCookie($this->_config['cookieName']);
178: $post = $request->getData($this->_config['field']);
179: $header = $request->getHeaderLine('X-CSRF-Token');
180:
181: if (!$cookie) {
182: throw new InvalidCsrfTokenException(__d('cake', 'Missing CSRF token cookie'));
183: }
184:
185: if (!Security::constantEquals($post, $cookie) && !Security::constantEquals($header, $cookie)) {
186: throw new InvalidCsrfTokenException(__d('cake', 'CSRF token mismatch.'));
187: }
188: }
189: }
190: