Problem/Motivation

Let's assume we have the following routing definition:

features.export_download:
  path: '/admin/config/development/configuration/features/download/{uri}'
  defaults:
    _controller: 'Drupal\features\Controller\FeaturesController::downloadExport'
  requirements:
    _permission: 'export configuration'
    _csrf_token: 'TRUE'

And the following code in a form submit function:

  public function exportFormSubmit(array &$form, FormStateInterface $form_state) {
    // Redirect to the archive file download.
    $form_state->setRedirect('features.export_download', ['uri' => $this->archiveName]);
  }

Result: The token has the sha1 hash, not the actual CSRF token

Proposed resolution

  • Apply the CSRF token replacement in \Drupal\Core\Form\FormSubmitter::redirectForm (sounds wrong)
  • Provide a way to generate the URL with the CSRF token (seems impossible at the moment, we always pass along GeneratedUrl)

Remaining tasks

User interface changes

API changes

Data model changes

Issue fork drupal-2630920

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

dawehner created an issue. See original summary.

dawehner’s picture

Well, even if they bubble, for this case you still need the actual replacement , otherwise nothing happens.

damiankloip’s picture

Yeha, seems like a different issue. This will always just leave the 'raw' sha1 hash as your link token. You use that as a redirect or something, no banana.

berdir’s picture

Yes, that's exactly the problem I had in token too, you just get the placeholder hash instead of the token and then you spend hours debugging why the token doesn't match ;)

I don't think those issues are that different, maybe a bit since in my case, it *should* work as it ends up in a render array while it's just a redirect for you.

That said, it might actually be exactly the same. RouteProcessorCsrf has two modes. Using a placeholder or not. And I think we incorrectly pass down that in this case, the placeholder approach won't work. It's either the same fix or the same fix in two places AFAICS.

dawehner’s picture

Yeah sure, I don't care as long its fixed.

wim leers’s picture

Component: routing system » forms system
Status: Active » Needs review
Issue tags: +Needs tests
StatusFileSize
new1.72 KB

Agreed they're duplicates.

I think this should fix it.

wim leers’s picture

Issue tags: +D8 cacheability
dawehner’s picture

+++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
@@ -132,19 +133,21 @@ public function redirectForm(FormStateInterface $form_state) {
-      return new RedirectResponse($url, Response::HTTP_SEE_OTHER);
+      $response = new CacheableRedirectResponse($url, Response::HTTP_SEE_OTHER);
+      $response->addCacheableDependency($url);
+      return $response;
     }

This is a odd one, honestly. We have a POST request going one when we redirect which is not cacheable anyway, right? So we use that mostly to do the token replacement?

wim leers’s picture

Perhaps a cacheable response is not necessary. But there sure is cacheability metadata associated with this URL and therefore with this redirect.

You're right of course that responses to POST requests are never cacheable.

But what about GET forms?

Status: Needs review » Needs work

The last submitted patch, 8: 2630920-8.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.73 KB
new877 bytes

Oops.

Status: Needs review » Needs work

The last submitted patch, 13: 2630920-13.patch, failed testing.

dawehner’s picture

You're right of course that responses to POST requests are never cacheable.
But what about GET forms?

Fair point, I wonder though whether people take that into account when the call code in submit handlers. In those cases for GET forms those redirects need
some URL cache context at least.

mustanggb’s picture

I have the same issue of links being rendered with a placeholder rather than the actual token, however I'm not trying to use it with forms, but rather in a hook_preprocess_page_title(), it seems like renderPlaceholderCsrfToken() is never called.

wim leers’s picture

#16: can you post your hook_preprocess_page_title() implementation?

mustanggb’s picture

Of course:

function mymodule_preprocess_page_title(&$variables) {
  $route = \Drupal::routeMatch();
  $account = \Drupal::currentUser();
  if ($route->getRouteName() == 'view.myview.mydisplay' && $account->hasPermission('mymodule permission')) {
    $url = Url::fromRoute('myroute');
    $link = Link::fromTextAndUrl('mylinktext', $url);
    $variables['title_suffix'] = array(
      'mykey' => array(
        '#markup' => '(' . $link->toString() . ')',
      ),
    ) + $variables['title_suffix'];
  }
}
mustanggb’s picture

Actually if I make the following change it works; is this intended?

- '#markup' => '(' . $link->toString() . ')',
+ '#markup' => '(' . render($link->toRenderable()) . ')',
catch’s picture

Issue tags: +Triaged core major

Tagging as triaged after discussion with alexpott, effulgentsia, xjm and cottser.

Not sure of a better title, but just to clarify the issue here is that the link won't work, we're not leaking tokens anywhere, so firmly major rather than critical.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

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

pwolanin’s picture

yesct’s picture

Issue tags: +CSRF, +CSRF Token
markhalliwell’s picture

Version: 8.1.x-dev » 8.3.x-dev
Related issues: +#2793109: [PP-1] _csrf_token in route breaks custom #ajax url

This is a problem when setting $element['#ajax']['url'] to a custom route that has a _csrf_token requirement as well. Related issue has a temporary "workaround" as well as some step through debugging for "why" this is happening.

dawehner’s picture

I guess one more generic solution would be to replace the render tokens on redirect responses, if not all, as well, not just on HTML ones.

timmillwood’s picture

I look to be having the same / similar issue with drupal_set_message.

pwolanin’s picture

@timmillwood - any more specifics?

amateescu’s picture

wim leers’s picture

wim leers’s picture

Version: 8.3.x-dev » 8.2.x-dev
Component: forms system » render system
Issue tags: +Contributed project blocker, +Dublin2016

Marked #2804327: CSRF links in a drupal_set_message don't render correctly as a duplicate of this. Inheriting its tags.

wim leers’s picture

Title: _csrf_token is broken due to cacheability metadata integration » _csrf_token is broken due to cacheability metadata integration, results in rendered links without valid CSRF tokens
amateescu’s picture

We also need to inherit the tests from that issue, and people who worked on it :)

wim leers’s picture

Yep!

xjm’s picture

Ensuring issue credit for the people who worked on #2804327: CSRF links in a drupal_set_message don't render correctly.

xjm’s picture

If this turns out to fix #2787083: [PP-1] CSRF tokens broken for check updates or run cron for update module are 403, we should add tests for that as well.

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.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

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

dalin’s picture

For anyone looking for a workaround, you basically need to render the link first. e.g.

$link = Link::createFromRoute('Do stuff', 'some.route', ['foo' => $foo, 'bar' => $bar]);
$link_render = $link->toRenderable();
$link = \Drupal::service('renderer')->renderPlain($link_render);
drupal_set_message(t('This is awesome. @do_stuff_link', ['@do_stuff_link' => $link]));

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

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

dawehner’s picture

One small thing I realized what this issue could result into: field_ui uses route_names / route_parameters for its destinations.

Once this issue is in you could construct links like: http://d8.loc/admin/structure/types/manage/article/fields/node.article.field_test_link/storage?destinations[0][route_name]=entity.view.disable&destinations[0][route_parameters][view]=comment
which will result into a form, which redirects to disabling a view.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.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.

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

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.

dieterholvoet’s picture

I'm having the same issue, but in a different situation: I'm adding urls with token placeholders to drupalSettings, which are never replaced with the actual CSRF token. This is because drupalSettings are not yet rendered when the placeholders are replaced by their actual values during the rendering process. See HtmlResponseAttachmentsProcessor::renderPlaceholders.

I'm not sure if there's a possible solution that would fix both this and the other reported issues.

catch’s picture

Issue tags: -Dublin2016 +Bug Smash Initiative
StatusFileSize
new1.77 KB

The test-only patch from #2804327: CSRF links in a drupal_set_message don't render correctly never made it here, so uploading that.

jamsilver’s picture

I just bumped into what feels like something related to this issue on Drupal 9.4.5 writing a KernelTestCase for a custom route.

This is the kind of pseudo-code I had:

        $url = $this->urlGenerator->generateFromRoute(
            'my_cool_route',
            ['my_node_id' => 1234]
        );
        $this->makeGetRequest($url);
        $this->assertThatThePageReturnedTheRightStuffAndDidTheRightThingEtcEtc();

Issue is $url had ?token placed in the query-string, but it was the sha1 placeholder, and not the actual proper token. I experimented with two workarounds, and went with the latter in my case, although it feels fragile:

1. Spin up a render context and render the link:

/**
     * A drop-in replacement for route() or GeneratorUrl::generateFromRoute() that supports CSRF tokens.
     *
     * @param $name
     *   See UrlGeneratorInterface::generateFromRoute().
     * @param $parameters
     *   See UrlGeneratorInterface::generateFromRoute().
     * @param $options
     *   See UrlGeneratorInterface::generateFromRoute().
     *
     * @return string
     *   The URL.
     */
    private function generateUrlFromRouteSupportingCsrf(
        string $name,
        array $parameters = [],
        array $options = []
    ): string {
        return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($name, $parameters, $options) {
            $generatedUrl = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE);
            $build = [
                '#markup' => $generatedUrl->getGeneratedUrl(),
            ];
            $generatedUrl->applyTo($build);
            return $this->renderer->render($build, true);
        });
    }

2. Use the (private) inner 'url_generator.non_bubbling' service directly to avoid all the bubbling stuff:


    protected function setUp(): void
    {
        parent::setUp();
        ...
        // The following is equivalent to:
        //     $this->urlGenerator = $this->container->get('url_generator.non_bubbling');
        // See https://www.drupal.org/project/drupal/issues/2630920
        $this->urlGenerator = new \Drupal\Core\Routing\UrlGenerator(
            $this->container->get('router.route_provider'),
            $this->container->get('path_processor_manager'),
            $this->container->get('route_processor_manager'),
            $this->container->get('request_stack'),
            $this->container->getParameter('filter_protocols')
        );
    }

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.

bardiuk’s picture

Is this the reason why I can not download the archive? I get 404 Not Found error in Docker container

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 (maintainer needs more info)
Issue tags: +Needs steps to reproduce

This came up as a daily BSI target

Reading the comments sounds like we need to add the test patch from #48 but for solution not 100% sure the older patches still apply here. Not just physically but with todays core infrastructure.

But think it would be good to get concrete steps to reproduce.

Thanks all

smustgrave’s picture

Wanted to bump this 1 more time, if still an issue in D11.

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

jacktonkin’s picture

This is still an issue, at least as described in #2804327: CSRF links in a drupal_set_message don't render correctly. I've rerolled the test-only patch and ensured the test user is logged in so that it's actually testing the CSRF token.

The workaround of calling:

  /** @var \Drupal\Core\Link $link */
  $build = $link->toRenderable();
  $this->messenger->addStatus($this->renderer->renderInIsolation($build));

Still works in this case.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

mohit_aghera’s picture

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

I came across this issue while doing bug-smash triage.
I see that test-only patch is failing as expected.
The patch seems to be derived from #48.
Moving this to needs work to fix the issue.