Problem/Motivation
Route user.cancel_confirm contains {hashed_pass}.
For users without a password (e.g. external authenticated like OpenID Connect) this measurement doesn't add any safety other than the "last login time" property of an account.
See UserController::confirmCancel and user_pass_rehash implementations for details.
Proposed resolution
Find some other measurement to prevent misuse of this route.
Issue fork drupal-3277003
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:
- 3277003-user-pass-rehash
changes, plain diff MR !5823
Comments
Comment #2
damienmckennaComment #6
xjmComment #7
mcdruid commentedThis issue was discussed by the Drupal Security Team.
It was agreed that it can be handled in public as a security hardening; we can use this issue.
The problem is that there is significantly less entropy in the inputs to
user_pass_rehash()if the user has an empty password field in the database.Users may have empty passwords if - for example - a site uses a Single Sign On integration to offload authentication.
Improvements could be made to the implementation to reduce the likelihood that an attacker could brute force e.g. a password reset URL if the user has no password stored in Drupal.
It may also be advisable for sites / projects that do not use Drupal's passwords for authentication to avoid leaving the password field empty in the database, but that can be looked at in a (public) follow-up. It wouldn't help much to set the password field to the same value for all users.
Comment #15
mcdruid commentedAdding credit from the private DST discussion - which is where the idea for this
patchMR came from.Comment #16
mcdruid commented#3409043: [D7] Harden user_pass_rehash() against attack
Comment #17
smustgrave commentedNot sure if tests can be a follow up with security improvements but think this should get some test coverage.
Comment #18
mcdruid commentedMore tests are always good, but I'm not sure what additional tests we'd add here.
There are already tests which cover functionality where
user_pass_rehash()is used to construct URLs - e.g.:The idea of the improvement being suggested is to reduce the chances of a successful brute force attack, and that's not necessarily an easy thing to write reliable automated tests for.
Open to suggestions of what testing we could add, but I think we may have to be satisfied that existing tests prove this change is not introducing a regression.
Comment #19
catchWhat about a test that checks that two users with an empty password and the same last login timestamp get different hashes? That wouldn't prove that there's not some other vector for hash collision, but it would prove that we got rid of this one.
Comment #20
mcdruid commentedRight, good plan.
Added a basic KernelTest that illustrates a possible hash collision when there are empty passwords in the db.
I've manually verified that the test fails without the change to
user_pass_hash().Sorry, I've not kept up with how we're doing (the equivalent of) test-only patches these days, but I'll try to find out in the next couple of days.
Comment #21
catch@mcdruid in the gitlab pipeline UI, there's a 'test only' job which reverts any non-test changes, then runs only the tests changed in the MR, it doesn't work for everything but it works for most.
Comment #22
mcdruid commentedAh, interesting thanks.
The test only job seems to have passed, but also shows the expected failure:
https://git.drupalcode.org/project/drupal/-/jobs/506479
Do we need anything else in the MR?
I'd think this will need a CR; apart from anything else any reset URLs that have been sent out but not yet used will become invalid (although they only last for 24hrs by default IIRC).
Comment #23
poker10 commentedThe MR is not rebased, but I thought we fixed that in #3401971: Test-only job shouldn't require constant rebases to detect which files were changed., so there is no need to rebase when running test-only pipeline.
See (https://git.drupalcode.org/project/drupal/-/jobs/506479):
Comment #24
poker10 commentedI have created a simple draft CR here: https://www.drupal.org/node/3409738
And also created two follow-ups to fix the behavior of the test-only job from above here:
#3409732: Test-only job reverts unrelated changes in non-rebased MRs
#3409733: Test-only job does not detect failures correctly
Comment #25
smustgrave commentedReviewing MR 5823
Test coverage is there per #22
CR reads fine to me.
Think this is good.
Comment #26
catchOne small comment on the test, otherwise this looks good to me.
Comment #27
mcdruid commentedThanks; thought I'd tried that but I must have been doing something wrong..
Comment #28
catchThanks! Back to RTBC.
Comment #29
bramdriesenOut of interest, but can someone explain (if allowed) how separating the values (which are unique) with a
:makes it so that the hashes are unique? I guess that was explained in private DST, but we can't see that discussion 🙂Comment #30
fjgarlin commentedSee the test.
When concatenating unique id and email like these two pairs:
- 123 / example@test.com
- 12 / 3example@test.com
You’d get the same string without the “:”.
Comment #31
bramdriesenAh, I see, makes perfect sense. Kind of overlooked that in the test case I guess. Thanks @fjgarlin!
Comment #32
catchCommitted/pushed to 11.x and cherry-picked to 10.2.x, thanks!
Comment #36
quietone commentedPublish CR