Problem/Motivation

When using domain language negotiation the destination parameter can be stripped.

Steps to reproduce:

  1. Install standard
  2. Install the language module
  3. Add second language
  4. Go to Configuration > Regional and language > Languages > Detection, click on Domain and make up some configuration and save it.
  5. Visit /admin/structure/block
  6. Click the seven link in local tasks
  7. Click remove on the first block
  8. Click click the remove button
  9. You should be on admin/structure/block/list/seven but you're on admin/structure/block

This can also cause errors with disabled javascript, domain language negotiation and Big Pipe enabled:

"Symfony\Component\HttpKernel\Exception\HttpException: The original location is missing. Drupal\big_pipe\Controller\BigPipeController->setNoJsCookie() (line 50 /core/modules/big_pipe/src/Controller/BigPipeController.php)"

Proposed resolution

Fix the \Drupal\Core\Security\RequestSanitizer to use \Drupal\Component\Utility\UrlHelper::externalIsLocal() to determine is an external URL is really external.

Remaining tasks

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

N/a

Issue fork drupal-2980527

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

wim leers’s picture

Title: Domain based language ang Big Pipe error » Domain based language ang BigPipe error
Project: Drupal core » Domain
Version: 8.6.x-dev » 8.x-1.x-dev
Component: big_pipe.module » Code
Priority: Normal » Major

That would be a bug in the Domain module then; it's stripping a query argument from a redirect URL when it shouldn't. Should be pretty easy to fix!

wim leers’s picture

This is the relevant code:

    if (!$request->query->has('destination')) {
      throw new HttpException(400, 'The original location is missing.');
    }

i.e. the destination URL query argument is being stripped.

agentrickard’s picture

Title: Domain based language ang BigPipe error » Domain based language negotiation and BigPipe error
Project: Domain » Drupal core
Version: 8.x-1.x-dev » 8.6.x-dev
Component: Code » big_pipe.module
Status: Active » Postponed (maintainer needs more info)

@Wim

I'm not sure this is Domain module related. Core language negotiation by domain prefix is the likely issue here.

There is nothing in this very thin error report to indicate that Domain module is involved.

Needs more information from the original reporter.

agentrickard’s picture

@Wim-

If this is Domain related, where is that code snippet from?

wim leers’s picture

Title: Domain based language negotiation and BigPipe error » Domain-based language negotiation strips "destination" URL query argument, causing BigPipe error
Component: big_pipe.module » language system
Status: Postponed (maintainer needs more info) » Active

The code snippet is from \Drupal\big_pipe\Controller\BigPipeController::setNoJsCookie().

It's totally possible the problem is not in Domain, but in core's language negotiation. What's certain is that something is stripping that query string. I read Domain in the issue title and assumed the OP meant domain module. I now see that it totally could've been domain-based language negotiation in core :) Sorry!

agentrickard’s picture

No worries. But we still need more context from the original reporter.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexpott’s picture

I can reproduce the bug only with core. The problem is that core/lib/Drupal/Core/Security/RequestSanitizer.php is being too aggressive.

Here's how to reproduce:

  1. Install standard
  2. Install the language module
  3. Add second language
  4. Go to Configuration > Regional and language > Languages > Detection, click on Domain and make up some configuration and save it.
  5. Visit /admin/structure/block
  6. Click the seven link in local tasks
  7. Click remove on the first block
  8. Click click the remove button
  9. You should be on admin/structure/block/list/seven but you're on admin/structure/block

This is being caused by \Drupal\Core\Security\RequestSanitizer::processParameterBag() stripping all external destinations. However all destination URLs will be external when domain language negotiation is configured.

alexpott’s picture

alexpott’s picture

Status: Active » Needs review
Issue tags: +Needs tests
StatusFileSize
new2.8 KB

Here's a fix.

We're too early for $GLOBALS['base_url'] - that's done in \Drupal\Core\DrupalKernel::initializeRequestGlobals() which means we're not setting the base URL completely as expected but I think this check is good enough - if there's an insecure and malicious site available at the same domain then you've got more problems then redirects.

Status: Needs review » Needs work

The last submitted patch, 11: 2980527-11.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new3.17 KB
new5.14 KB

Fixed the tests and made the code a bit more robust if the Request object doesn't have all the information. Added a test for the case when the destination and the request are for the same domain.

alexpott’s picture

Issue summary: View changes
StatusFileSize
new1.03 KB
new5.3 KB

Improved the issue summary and the comment in the patch.

krzysztof domański’s picture

StatusFileSize
new5.73 KB
new2.38 KB

After adding new parameter $request we do not need parameter $bag ($bag = $request->$bag_name).

-  protected static function processParameterBag(ParameterBag $bag, $whitelist, $log_sanitized_keys, $bag_name, $message, Request $request) {
+  protected static function processParameterBag(Request $request, $whitelist, $log_sanitized_keys, $bag_name, $message) {
+    $bag = $request->$bag_name;
alexpott’s picture

@Krzysztof Domański yep that looks good. Nice one.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

AndyThornton’s picture

The patch in #15 is working for me on Drupal 8.7.3 - thanks a lot.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

This has sufficient testcoverage and I can't see anything at all that should change for this patch.

wim leers’s picture

Wow! Excellent investigative work in #9, @alexpott!

krzysztof domański’s picture

StatusFileSize
new800 bytes
new6.38 KB

Fixed test failure #15.

--- a/core/modules/block/tests/src/Functional/BlockUiTest.php
+++ b/core/modules/block/tests/src/Functional/BlockUiTest.php
@@ -337,9 +337,7 @@ public function testBlockPlacementIndicator() {
     // Removing a block will remove the block placement indicator.
     $this->clickLink('Remove');
     $this->submitForm([], 'Remove');
-    // @todo https://www.drupal.org/project/drupal/issues/2980527 this should be
-    //   'admin/structure/block/list/classy' but there is a bug.
-    $this->assertSession()->addressEquals('admin/structure/block');
+    $this->assertSession()->addressEquals('admin/structure/block/list/classy');
alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Security/RequestSanitizer.php
    @@ -105,11 +105,23 @@ protected static function processParameterBag(ParameterBag $bag, $whitelist, $lo
    +        try {
    +          $is_local = UrlHelper::externalIsLocal($destination, $request->getSchemeAndHttpHost());
    +        }
    +        catch (\InvalidArgumentException $e) {
    +          $is_local = FALSE;
    +        }
    

    There needs to be a comment as to why catching the exception is required. Why at this point would either $destination or $request->getSchemeAndHttpHost() fail

        $url_parts = parse_url($url);
        $base_parts = parse_url($base_url);
    
        if (empty($base_parts['host']) || empty($url_parts['host'])) {
          throw new \InvalidArgumentException('A path was passed when a fully qualified domain was expected.');
        }
    

    Or is this being ultra defensive?

  2. +++ b/core/lib/Drupal/Core/Security/RequestSanitizer.php
    @@ -48,7 +47,7 @@ public static function sanitize(Request $request, $whitelist, $log_sanitized_key
    -        if (static::processParameterBag($request->$bag, $whitelist, $log_sanitized_keys, $bag, $message)) {
    +        if (static::processParameterBag($request, $whitelist, $log_sanitized_keys, $bag, $message)) {
    
    @@ -78,9 +77,10 @@ public static function sanitize(Request $request, $whitelist, $log_sanitized_key
    -  protected static function processParameterBag(ParameterBag $bag, $whitelist, $log_sanitized_keys, $bag_name, $message) {
    +  protected static function processParameterBag(Request $request, $whitelist, $log_sanitized_keys, $bag_name, $message) {
    
    @@ -105,11 +105,23 @@ protected static function processParameterBag(ParameterBag $bag, $whitelist, $lo
    +          $is_local = UrlHelper::externalIsLocal($destination, $request->getSchemeAndHttpHost());
    

    Let's pass in $request->getSchemeAndHttpHost() as an additional argument rather than changing the parameters.

alexpott’s picture

Lol I added the try catch in #13

made the code a bit more robust if the Request object doesn't have all the information

So the answer is more defensive. I guess as this is security code that makes sense. So let's ignore #22.1

So once upon a time I thought the parameter change was a good idea too but looking at the loop that calls processParameterBag() I'm now not so sure.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

krzysztof domański’s picture

Issue tags: +Bug Smash Initiative

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

nikitagupta’s picture

Status: Needs work » Needs review
StatusFileSize
new6.38 KB

rerolled patch #21.

nikitagupta’s picture

StatusFileSize
new6.38 KB

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

parisek made their first commit to this issue’s fork.

parisek’s picture

parisek’s picture

created MR for 9.5

anothergasteizone’s picture

Version: 9.5.x-dev » 9.4.x-dev
StatusFileSize
new6.37 KB

The patch 2980527-29.patch did not work for 9.4.5 because classy has been replaced with stark. This patch should do the trick.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new988 bytes

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

raphaelbertrand’s picture

Simplier solution might be to change this line (61) in big_pipe.module,
method big_pipe_page_attachments to set destination parametter to local uri instead of absolute
by the way it will not be detected as external. The right host be already set by the route of big_pipe.nojs .

'content' => '0; URL=' . Url::fromRoute('big_pipe.nojs', [], ['query' => \Drupal::service('redirect.destination')->getAsArray()])->toString(),

nginex’s picture

#37 worked for me, thanks

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs work » Postponed

Think this would be a good plan. Lets postpone this one for the fix in #3424701: Domain-based language negotiation should retain "destination" URL query argument then we can reopen this one for expanding test coverage and removing the todo in the code.

smustgrave’s picture

Status: Postponed » Closed (duplicate)

So the fix in #44 needed to update the tests too. So going to close as duplicate and move over credit.

catch’s picture

I just committed #3424720: LanguageNegotiationUrl unnecessarily adds domain to outbound URL's which fixes this issue. Credit wasn't transferred over originally, but we can assign credit for duplicate issues now, so doing that here.