The basic auth module allows brute-force login attacks.
(This is a security issue in the new basic_auth
module of Drupal 8, so it is critical by definition.)
Comment | File | Size | Author |
---|---|---|---|
#43 | 2160021.patch | 10.51 KB | klausi |
#22 | Selection_002.png | 51.64 KB | juampynr |
#13 | 2160021-diff-12-13.txt | 755 bytes | vijaycs85 |
#13 | 2160021-basic_auth-flood-13.patch | 9.32 KB | vijaycs85 |
#12 | interdiff.txt | 6.42 KB | juampynr |
Comments
Comment #1
catchOn #2041885: Move HTTP basic authentication provider to a separate module there was some discussion about removing this from core, however:
1. I don't think there's an issue yet
2. This issue would be critical against the contrib module too.
Comment #2
tim.plunkett#2159937: Remove basic_auth
Comment #3
catchThanks. Re-opened.
Comment #4
klausiNote that restws_basic_auth uses drupal_form_submit('user_login', $form_state); which processes all user login validators including flood control, so this works as designed in D7 RESTWS.
Comment #5
Crell CreditAttribution: Crell commentedSo what would the equivalent be for Drupal 8 for flood protection?
Comment #6
juampynr CreditAttribution: juampynr commentedWhile I investigate on how the Flood service works for the user login form; isn't it possible to suggest to install a piece of software like Failtoban at the server to prevent this?
Comment #7
juampynr CreditAttribution: juampynr commentedFirst pass.
I manually verified that both IP based and user based blocking mechanisms work. But for some reason one request implies 4 new entries written in the flood table instead of two (one for ip based and a second for user based, if the username was an existing one).
I am also using Drupal::service at the constructor (ugh). How could I use the dependency injector?
Finally, I still have to add tests to this.
Apart from the above. It works.
Comment #8
Crell CreditAttribution: Crell commentedYou can inject entity.manager, and then in the constructor only save the result of calling getStorageController if that's all you need.
Comment #9
dawehnerThe flood problem seems not to be specific with the basic_auth module. Could we move this logic maybe to a base class and let people reuse it?
Comment #10
juampynr CreditAttribution: juampynr commentedThanks @crell. Now injecting the entity manager.
I saw that the authenticate method is called twice: one to authenticate the user and then a second time to authenticate the request. This is the reason why two entries are logged at the flood table.
As for @dawehner's suggestion, I totally agree. I felt I was copying and pasting too much code while implementing this. Could we use a UserFlood service that loads the user.flood settings and wraps the long method calls that the flood service is doing at the
applies
method implementation of this patch?Comment #11
BerdirThe way this works for basic auth is quite different from cookie/session based logins. \Drupal\Core\Authentication\Provider\Cookie doesn't have to care about flood protection because all it does is validate the cookie, login/authentication/flood protection happens somewhere else and always will.
Same for OAuth, the authentiation provider just needs to validate the token, which doesn't need flood protection.
So not sure if it really makes sense to have this in a default implementation, what other mechanism could actually use it?
Also wondering if basic auth flood protection should affect normal logins or if they should be separated?
Comment #12
juampynr CreditAttribution: juampynr commentedAdded a separate config file for basic_auth flood protection, as suggested by @berdir. It gives admins more flexibility to decide whether it should behave differently than normal logins or not.
Also added a test. I still need to figure out why a request is authenticated twice and therefore two flood entries are logged in for a failed login.
Comment #13
vijaycs85Adding configuration schema for the new config basic_auth.flood...
Comment #14
juampynr CreditAttribution: juampynr commentedThe following dependency chain provokes an extra authentication before we actually want to authenticate the user against the request:
Note: this is extracted from the generated DependencyInjector container.
route_processor_csrf service instantiates csrf_token service, which in turn requests the current_user service and therefore it authenticates the request before expected.
I need some guidance here. There is one test that fails because flood attempts are logged twice because of this issue.
Comment #15
juampynr CreditAttribution: juampynr commentedAdding a label to the config schema definition and setting the right number of login attempts, which will fail because of what I explained in the previous comment.
Comment #17
juampynr CreditAttribution: juampynr commented15: 2160021-basic_auth-flood-15.patch queued for re-testing.
Comment #19
juampynr CreditAttribution: juampynr commentedGood. So now the testbot reported one failing test which demonstrates my open question at #14
Comment #20
Damien Tournoud CreditAttribution: Damien Tournoud commented@juampy: in the current state, it seems likely that all our authentication providers are run twice.
I would add a condition in
AuthenticationSubscriber.onKernelRequestAuthenticate
that checks if current_user is already defined.Comment #21
damiankloip CreditAttribution: damiankloip commentedI'm not sure how possible that is, as you don't really know if the current user service has a user. You can't check that in the container; has() will always have the service definition, and calling get() will invoke the authenticate stuff.
We may need to look at what exactly is calling current_user earlier than auth providers.
Comment #22
juampynr CreditAttribution: juampynr commentedSince yesterday I am facing this error while debugging this issue. There is a circular dependency reference. I have no idea why this is not happening on tests:
Steps:
1. Enable REST and allow GET on node with basic auth.
2. Create a user. Allow permission on GET method to authenticated users on Content.
3. Create a node.
4. Open http://d8.local/entity/node/1 >> provide username and password.
Comment #23
BerdirYes, I just had that as well, the problem for me was devel, which does a user_access() in devel_shutdown_real().
Comment #24
juampynr CreditAttribution: juampynr commentedCreated #2182055: Comment module causes Circular reference error on a REST request to address the circular reference issue before continuing on this one.
Comment #25
andypostIssue #2182055: Comment module causes Circular reference error on a REST request is not about comment module, this only about csrf that executed before the auth happens
Comment #26
klausiSo these are all copied comments and code. It's a bit sad that we need to duplicate this. Please add at least a @see reference and that this is duplicated code.
Calling global functions means it will be hard to unit test this at all. Looks like there is no OOP replacement yet for user_authenticate()?
Do not call user_load() here, you already got the user storage controller injected, so we should load from there.
So this is a slow web test. I would rather like to see a PHPUnit test to test the code in isolation. But on the other hand an integration test also makes sense here, so not sure about that.
Comment #27
damiankloip CreditAttribution: damiankloip commentedI already had a quick branch I created locally for that a while ago but forgot to open an issue/post the patch. So here we go #2206687: Replace user_authenticate with a UserAuth service
Comment #28
klausiklausi opened a new pull request for this issue.
Comment #29
klausiSo this is one of the worst hacks I ever did, but it ensures that we can make progress here while the authentication system is wrongly called twice. Interdiff: https://github.com/klausi/drupal/commit/a9c3656981fc4137c755eeb29219f1bc...
Comment #30
dawehnerThis introduces a workaround, but solves another critical. On top of that, this would certainly not make it harder to fix the current user issue.
It is kind of good to know that we found out about the two-times counting.
this is soooooo much nicer than the cookie based one.
Comment #31
damiankloip CreditAttribution: damiankloip commentedI still think we should just wait on https://drupal.org/comment/8615067#comment-8615067, as that will fix this issue.
Comment #32
damiankloip CreditAttribution: damiankloip commentedSo we can remove that now current user issue just got in? :)
Comment #33
sunWhy do we need to duplicate the user.flood config file into basic_auth's configuration?
It appears that basic_auth has a dependency on User module anyway already? (next to the sad fact that User module is a required module...)
Comment #34
damiankloip CreditAttribution: damiankloip commentedThat is a good point, I had not twigged this was user.flood incarnate :) I agree totally - Let's remove it.
Comment #37
sunwas renamed to
getStorage()
Comment #38
damiankloip CreditAttribution: damiankloip commentedYes, just realised :) Thanks. I think klausi is working on this now.
Comment #39
klausiklausi pushed some commits to the pull request.
For an interdiff please see the list of recent commits.
Comment #41
damiankloip CreditAttribution: damiankloip commentedLooks like the last reroll had a rebasing issue, well - lack of :)
Comment #43
klausiklausi opened a new pull request for this issue.
Comment #44
klausiSorry for the noise, somehow my pull request was rebased/broken.
The problem is that the authentication is still triggered twice if an exception occurs, the request is changed and the account proxy runs the authentication again triggering the flood control too early.
So I think we can just remove the synchronized request from the account proxy, as authentication should really happen once and not on subrequests.
Interdiff: https://github.com/klausi/drupal/commit/a2eed573b0dea17822c77b6fde290239...
Comment #45
dawehnerAwesome!
Comment #46
damiankloip CreditAttribution: damiankloip commentedYes, I think this looks good. Just removes the setRequest stuff and passes it into the constructor, which makes sense if we don't want to change it.
EDIT: x-posted with dawehner. I was also going to RTBC!
Comment #47
catchThis looks great, bonus to have one less subrequest.
Committed/pushed to 8.x, thanks!