The NoSessionOpen RequestPolicy (Drupal\Core\PageCache\RequestPolicy\NoSessionOpen) returns static::ALLOW when the user is not logged in. In my opinion it should also return static::DENY when the user is already logged in, to prevent logged in pages to come up in the cache.

We created a custom page cache policy that returned a ALLOW and a DENY and found out that logged in pages were being served to anonymous users. This can all be fixed by returning DENY from the NoSessionOpen Policy.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jeroen.b created an issue. See original summary.

jeroen.b’s picture

Status: Active » Needs review
FileSize
525 bytes

Status: Needs review » Needs work

The last submitted patch, 2: 2829892-deny-page-cache-NoSessionOpen.patch, failed testing.

jeroen.b’s picture

Status: Needs work » Needs review
FileSize
1.11 KB

Fix for tests.

Wim Leers’s picture

Status: Needs review » Postponed (maintainer needs more info)
Issue tags: +Security

to prevent logged in pages to come up in the cache.

Please write a test proving that "logged in pages" are being stored in Page Cache. I cannot reproduce this. If you can, this is a critical security issue.

jeroen.b’s picture

@Wim Leers, this is only a problem when you implement a custom RequestPolicy that always returns a DENY or ALLOW.

Wim Leers’s picture

Title: NoSessionOpen RequestPolicy should return DENY when logged in » Possible to break PageCache when adding a RequestPolicy that unconditionally returns DENY or ALLOW
Issue tags: -Security +Security improvements, +Needs tests

Aha! Adjusting title accordingly.

That suggests that \Drupal\Core\PageCache\ChainRequestPolicy::check() needs hardening. Hardening NoSessionOpen is pointless, because another request policy can then have the exact same "weakness". Hardening \Drupal\Core\PageCache\ChainRequestPolicy::check() would fix it in all request policies at once.

Right?

alexpott’s picture

Status: Postponed (maintainer needs more info) » Needs work

Looks like we have the more info. But

elseif (isset($result)) {
throw new \UnexpectedValueException('Return value of RequestPolicyInterface::check() must be one of RequestPolicyInterface::ALLOW, RequestPolicyInterface::DENY or NULL');
}

So NULL was part of the design.

* @return string|null
* One of static::ALLOW, static::DENY or NULL. Calling code may attempt to
* deliver a cached page if static::ALLOW is returned. Returns NULL if the
* policy is not specified for the given request.

@Wim Leers so what hardening where you thinking of?

jeroen.b’s picture

I don't think so. When one Policy says DENY, then nothing get's through.
So when NoSessionOpen always return ALLOW or DENY, it should be fixed.
I think the main problem is that there is no Policy saying DENY right now.

Wim Leers’s picture

@alex.pott: hardening to prevent the problem that @jeroen.b described from also causing problems in request policies in contrib/custom modules.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

znerol’s picture

Status: Needs work » Closed (works as designed)

I see that the documentation around page cache request/response policies is far from complete. Sorry for that.

I try to clarify the meaning of the three possible results of a request policy rule:

ALLOW
The request satisfies the condition encoded in the policy rule and as a result it is safe to lookup/deliver a cached response
DENY
The request satisfies the condition encoded in the policy rule and as a result it is unsafe to lookup/deliver a cached response
NULL
The request does not satisfy the condition encoded in the rule

The combination of multiple rules follows common sense: The result is DENY if any rule returned DENY and it is NULL unless at least one rule returned ALLOW - then it is obviously ALLOW.

It follows that a policy rule should be designed in a way that it either returns ALLOW|NULL or DENY|NULL. A rule returning ALLOW|DENY cannot be combined in any useful way with other policy rules.

We have the following request policy rules in core:

$ git grep -l implements.*RequestPolicy
core/lib/Drupal/Core/PageCache/ChainRequestPolicy.php
core/lib/Drupal/Core/PageCache/RequestPolicy/CommandLineOrUnsafeMethod.php
core/lib/Drupal/Core/PageCache/RequestPolicy/NoSessionOpen.php
core/modules/basic_auth/src/PageCache/DisallowBasicAuthRequests.php
core/modules/toolbar/src/PageCache/AllowToolbarPath.php

The notable allow examples are NoSessionOpen and AllowToolbarPath, the relevant deny-rule is CommandLineOrUnsafeMethod. The ChainRequestPolicy let's us combine all the rules.

NoSessionOpen returns ALLOW if there is no session on the request, it returns NULL otherwise. AllowToolbarPath returns ALLOW if the path matches the toolbar/subtree route and returns NULL otherwise. The combination of those two policy rules results in an ALLOW for any request which is either session-less or directed to the toolbar subtree endpoint. By adding CommandLineOrUnsafeMethod to the mix we additionally require the request to be either HTTP GET or HEAD.

Does that make sense?