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
Comments
Comment #2
berdirI think I've opened an issue about this already.
See #2575519: Twig template variables containing result of Drupal::url() and Drupal:l:() don't bubble up their cacheability and attachment metadata (e.g. token placeholder) and #2541166: Browse available tokens not shown
Comment #3
dawehnerWell, even if they bubble, for this case you still need the actual replacement , otherwise nothing happens.
Comment #4
damiankloip commentedYeha, 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.
Comment #5
berdirYes, 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.
Comment #6
dawehnerYeah sure, I don't care as long its fixed.
Comment #7
mgiffordComment #8
wim leersAgreed they're duplicates.
I think this should fix it.
Comment #9
wim leersComment #10
dawehnerThis 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?
Comment #11
wim leersPerhaps 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?
Comment #13
wim leersOops.
Comment #15
dawehnerFair point, I wonder though whether people take that into account when the call code in submit handlers. In those cases for
GETforms those redirects needsome URL cache context at least.
Comment #16
mustanggb commentedI 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.
Comment #17
wim leers#16: can you post your
hook_preprocess_page_title()implementation?Comment #18
mustanggb commentedOf course:
Comment #19
mustanggb commentedActually if I make the following change it works; is this intended?
Comment #20
catchTagging 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.
Comment #22
pwolanin commenteddawehner points out this one I filed may be a duplicate (same underlying cause) #2787083: [PP-1] CSRF tokens broken for check updates or run cron for update module are 403
Comment #23
yesct commentedComment #24
markhalliwellThis is a problem when setting
$element['#ajax']['url']to a custom route that has a_csrf_tokenrequirement as well. Related issue has a temporary "workaround" as well as some step through debugging for "why" this is happening.Comment #25
dawehnerI 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.
Comment #26
timmillwoodI look to be having the same / similar issue with drupal_set_message.
Comment #27
pwolanin commented@timmillwood - any more specifics?
Comment #28
amateescu commented@pwolanin, see #2804327: CSRF links in a drupal_set_message don't render correctly.
Comment #29
wim leersComment #30
wim leersMarked #2804327: CSRF links in a drupal_set_message don't render correctly as a duplicate of this. Inheriting its tags.
Comment #31
wim leersComment #32
amateescu commentedWe also need to inherit the tests from that issue, and people who worked on it :)
Comment #33
wim leersYep!
Comment #34
xjmEnsuring issue credit for the people who worked on #2804327: CSRF links in a drupal_set_message don't render correctly.
Comment #35
xjmIf 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.
Comment #38
dalinFor anyone looking for a workaround, you basically need to render the link first. e.g.
Comment #40
dawehnerOne 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]=commentwhich will result into a form, which redirects to disabling a view.
Comment #47
dieterholvoet commentedI'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.
Comment #48
catchThe test-only patch from #2804327: CSRF links in a drupal_set_message don't render correctly never made it here, so uploading that.
Comment #49
jamsilver commentedI 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:
Issue is
$urlhad?tokenplaced 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:
2. Use the (private) inner 'url_generator.non_bubbling' service directly to avoid all the bubbling stuff:
Comment #51
bardiuk commentedIs this the reason why I can not download the archive? I get 404 Not Found error in Docker container
Comment #53
smustgrave commentedThis 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
Comment #54
smustgrave commentedWanted to bump this 1 more time, if still an issue in D11.
Comment #57
jacktonkin commentedThis 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:
Still works in this case.
Comment #59
mohit_aghera commentedI 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.