annotate core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php @ 13:5fb285c0d0e3

Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've been lucky to get away with this so far, as we don't support self-registration which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5 was vulnerable to.
author Chris Cannam
date Mon, 23 Apr 2018 09:33:26 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Access;
Chris@0 4
Chris@0 5 use Drupal\Core\Session\AccountInterface;
Chris@0 6 use Drupal\Core\Session\SessionConfigurationInterface;
Chris@0 7 use Symfony\Component\Routing\Route;
Chris@0 8 use Symfony\Component\HttpFoundation\Request;
Chris@0 9
Chris@0 10 /**
Chris@0 11 * Access protection against CSRF attacks.
Chris@0 12 */
Chris@0 13 class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
Chris@0 14
Chris@0 15 /**
Chris@0 16 * A string key that will used to designate the token used by this class.
Chris@0 17 */
Chris@0 18 const TOKEN_KEY = 'X-CSRF-Token request header';
Chris@0 19
Chris@0 20 /**
Chris@0 21 * The session configuration.
Chris@0 22 *
Chris@0 23 * @var \Drupal\Core\Session\SessionConfigurationInterface
Chris@0 24 */
Chris@0 25 protected $sessionConfiguration;
Chris@0 26
Chris@0 27 /**
Chris@0 28 * The token generator.
Chris@0 29 *
Chris@0 30 * @var \Drupal\Core\Access\CsrfTokenGenerator
Chris@0 31 */
Chris@0 32 protected $csrfToken;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * Constructs a new rest CSRF access check.
Chris@0 36 *
Chris@0 37 * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
Chris@0 38 * The session configuration.
Chris@0 39 * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
Chris@0 40 * The token generator.
Chris@0 41 */
Chris@0 42 public function __construct(SessionConfigurationInterface $session_configuration, CsrfTokenGenerator $csrf_token) {
Chris@0 43 $this->sessionConfiguration = $session_configuration;
Chris@0 44 $this->csrfToken = $csrf_token;
Chris@0 45 }
Chris@0 46
Chris@0 47 /**
Chris@0 48 * {@inheritdoc}
Chris@0 49 */
Chris@0 50 public function applies(Route $route) {
Chris@0 51 $requirements = $route->getRequirements();
Chris@0 52 // Check for current requirement _csrf_request_header_token and deprecated
Chris@0 53 // REST requirement.
Chris@0 54 $applicable_requirements = [
Chris@0 55 '_csrf_request_header_token',
Chris@0 56 // @todo Remove _access_rest_csrf in Drupal 9.0.0.
Chris@0 57 '_access_rest_csrf',
Chris@0 58 ];
Chris@0 59 $requirement_keys = array_keys($requirements);
Chris@0 60
Chris@0 61 if (array_intersect($applicable_requirements, $requirement_keys)) {
Chris@0 62 if (isset($requirements['_method'])) {
Chris@0 63 // There could be more than one method requirement separated with '|'.
Chris@0 64 $methods = explode('|', $requirements['_method']);
Chris@0 65 // CSRF protection only applies to write operations, so we can filter
Chris@0 66 // out any routes that require reading methods only.
Chris@0 67 $write_methods = array_diff($methods, ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
Chris@0 68 if (empty($write_methods)) {
Chris@0 69 return FALSE;
Chris@0 70 }
Chris@0 71 }
Chris@0 72 // No method requirement given, so we run this access check to be on the
Chris@0 73 // safe side.
Chris@0 74 return TRUE;
Chris@0 75 }
Chris@0 76 }
Chris@0 77
Chris@0 78 /**
Chris@0 79 * Checks access.
Chris@0 80 *
Chris@0 81 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 82 * The request object.
Chris@0 83 * @param \Drupal\Core\Session\AccountInterface $account
Chris@0 84 * The currently logged in account.
Chris@0 85 *
Chris@0 86 * @return \Drupal\Core\Access\AccessResultInterface
Chris@0 87 * The access result.
Chris@0 88 */
Chris@0 89 public function access(Request $request, AccountInterface $account) {
Chris@0 90 $method = $request->getMethod();
Chris@0 91
Chris@0 92 // This check only applies if
Chris@0 93 // 1. this is a write operation
Chris@0 94 // 2. the user was successfully authenticated and
Chris@0 95 // 3. the request comes with a session cookie.
Chris@0 96 if (!in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'])
Chris@0 97 && $account->isAuthenticated()
Chris@0 98 && $this->sessionConfiguration->hasSession($request)
Chris@0 99 ) {
Chris@0 100 if (!$request->headers->has('X-CSRF-Token')) {
Chris@0 101 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
Chris@0 102 }
Chris@0 103 $csrf_token = $request->headers->get('X-CSRF-Token');
Chris@0 104 // @todo Remove validate call using 'rest' in 8.3.
Chris@0 105 // Kept here for sessions active during update.
Chris@0 106 if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
Chris@0 107 && !$this->csrfToken->validate($csrf_token, 'rest')) {
Chris@0 108 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
Chris@0 109 }
Chris@0 110 }
Chris@0 111 // Let other access checkers decide if the request is legit.
Chris@0 112 return AccessResult::allowed()->setCacheMaxAge(0);
Chris@0 113 }
Chris@0 114
Chris@0 115 }