Preface
On the 2nd of September, we reported a security issue to the security team. We identified that usernames are being leaked by JSON API, which we consider a personal information disclosure, not just username enumeration. The security team reacted quickly, but they said it is not an issue (as per Disclosure of usernames and user IDs is not considered a weakness) and we can go public with this problem. We truly believe that the problem is real, so this is a follow-up on that security report and also a follow-up on List of user accounts by get /user/user endpoint from 2o19, which was about the same problem with similar concerns.
Intro
(The original intro was moved to a new policy type issue as @cilefen suggested: #3241232)
Problem/Motivation
As we identified since the original security report, the actual problem is not with the JSON API module. The JSON API module just provides one presentation layer in Drupal core where the actual problem is perfectly demonstrated. If the admin/people page would be available to everyone without "administer users" permission, the same information would be exposed to that to all visitors.
The actual problem: the label of an entity is always considered public in Drupal core, the user entity's label is its username, precisely the display name of a user is by default its username, but that can be changed via a hook_user_format_name_alter() implementation (another way to expose personal data accidentally).
History:
* Anonymous user label can't be viewed and auth user labels are only accessible with 'access user profiles' permission
* JSON API now provides only the label of inaccessible entities ('view' access denied) when 'view label' access is allowed
* Show label of inaccessible entities ('view' access denied) when 'view label' access is allowed
Steps to reproduce
1. Enable the JSON API module
2. Register or create a user, use an email address as username for example, eg.: personal.information@example.com.
3. Switch to anonymous user, that can visit the default JSON API path + user/user endpoint ( /jsonapi/user/user)
3. In the response payload the anonymous user or possibly any other user without "View user information" permission can see the usernames.
(and not just user/user endpoint exposing this information, any other API endpoint that contains a reference to a user, e.g.: author on nodes.)
Proposed resolution
Since we cannot just get rid of an old design decision without consequences, we must slowly phase out the old design. My proposed solution is introducing new permission called "view usernames" for controlling who can access usernames on a site, and this permission would not be granted to any default role/persona. This would be a similar change for New permission to view user email.
All existing system components need to be prepared that the username is not always available to everyone, e.g.: authoring information on nodes.
With the help of the entity access API, downstream developers can further extend the default access provided by core - which by default, would be restrictive instead of permissive about usernames - and for example allow visitors to view usernames of internal personas, like content editors, or would only grant access to user A's username to other (non-admin) users if user A explicitly granted access somehow (e.g.: by checking a checkbox...)
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Issue fork drupal-3240913
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
Comment #3
mxr576Comment #4
mxr576MR1289 showcases that solving the problem is doable, currently, only a few tests are failing which I would need some help with because I am having hard times understanding why JSON API tests are failing - my only idea is that caching is not properly tested on HEAD and GET requests.
Comment #5
cilefen commentedRegarding the Disclosure of usernames and user IDs is not considered a weakness policy, it would be worth discussing changing it in a new "[policy] ..."-titled issue and re-title this one to the task at hand, because this one as titled looks like a policy discussion at a glance.
Comment #6
mxr576Comment #7
mxr576Comment #8
cilefen commented@mxr576 Regarding comment #5 above, are you going to open a policy change issue to discuss?
If Drupal took the opposite stance on user enumeration, we would work through those issues in public (like this issue), and in some release consider it “done”, then thereafter any reports would be security issues that would get Security Advisories. If we don't change the policy, then future unexpected user enumeration bugs would be public.
Comment #9
mxr576@cilefen, done, thanks for the suggestion
Comment #10
mxr576Comment #12
mxr576Let's bump the version, based on the current activity on this issue I am a bit skeptical if this can land in 9.4.x. Also, considering the size of the suggested change, it fits better to a new major version.
Comment #13
mxr576Comment #17
bserem commentedI'm taking a shot at this. I hid the old MR because it was 4 years old and I didn't feel like updating it.
Made a new MR (https://git.drupalcode.org/project/drupal/-/merge_requests/13617):
View usernamesIn my manual tests this works nicely for JSON:API, hiding usernames unless the new permission is granted.
Note:
Permission to
View user informationto anons allows them access to example.site/user/2 which could easily expose the username in the theme. This is not addressed in the MR and is not necessarily a core security issue.Not much I can do about it, themers must check for permissions before rendering the usernames in that case. The work in the MR does help in that direction but doesn't solve that case, it can't.
Please have a look
Comment #18
vensiresJust some thoughts in order to make sure we have taken everything API-related into account:
view labelto not fallback toviewthe entity class handler must setviewLabelOperationto TRUE; something already done by the core user module.Though I agree with the rest of the code introduced into the MR, I don't really like the following code:// Allow for the special anonymous user.
// Normally this user profile can't be seen, but it is exposed in JSON:API
// by default.
if ($entity->isAnonymous()) {
return AccessResult::allowed();
}
The main issue of this module is that we don't have the username access as a configurable option. By transferring this issue to the anonymous users only, I think we are actually hiding the issue in a subset of affected roles. I would prefer to not have this code and instead only check that the current role has the required permissions.(EDIT: As @bserem explained to me in Slack, this check is there to make sure that User #0 has the "Anonymous" username listed, so my point above is invalid)
As for the rest of the code:
Can you explain why we need an AND? Wouldn't OR be more suitable? That way we could even just check
if ($account->id() == $entity->id()) {...}and immediately after check for either of the following permissions:administer users,access user profilesorview usernames.As a last step, just to make sure that nothing breaks in current installations - that's the reason I turn this into
Needs work- we could make sure to have a post_update hook to assign the newview usernamespermission to all roles.Besides, using only permission-based checks we can make sure that anyone willing to change the system using the new Access Policy API is able to do so.
What do you think?
Comment #19
bserem commentedThanks for the feedback @vensires! Much appreciated.
I'm assigning the issue to me for now, but it might be a couple of days before I can jump to it again.
If anyone is willing to get this further by all means, be my guest.
@vensires from a quick overview I believe you got valid points, let me check them in detail and I'll get back to you with improvements.
Comment #20
bserem commentedImproved logic and order of checks, for better code readability. Also fixed CS issue added previously and adapted previous changes for new user registrations.
@vensires the idea behing
ANDis to do things the same as with emails:- right now you are not allowed to view the email unless you have BOTH `view user email` AND `access user profiles`
I do not object to the idea of an update hook to give `view user names` to all people, however since this is actually an open security-related issue where usernames are exposed in JSON:API I would prefer not to do so, not yet. Many of the sites that have JSON:API enabled might not know that usernames are exposed. So it would be better for us to apply the fix, rather than apply the fix and at same time undo it by giving anonymous users the permission to see usernames.
Open to discussion of course, as always.
I'll bump this to needs review, mostly because I'd love a fresh set of eyes to check it before I continue working on it again.
Thanks
Comment #21
prudloff commentedThanks for reviving this old issue, I agree this would be important to fix.
If you are using the email_registration module, jsonapi leaks the e-mail address of every user.
And even without email_registration, we noticed a lot of users tend to use their email address or their first name + last name as their username.
This is a GDPR violation and we are getting complaints from clients.
Comment #23
smustgrave commentedHave not fully reviewed but appears to have pipeline issues.
Comment #24
mxr576I just realized I never came back to share the good news after we open sourced the modules we built to mitigate this problem. As a heads-up, solving it turned out to be a bit more complicated than I expected. Once usernames were placed behind strict access control, we also had to introduce extra steps for granting access, and there are still some open issues related to that, see issue queues.
https://www.drupal.org/project/view_usernames
https://www.drupal.org/project/view_usernames_node_author
Comment #25
vensires@mxr576 thank you very much for coming back here and sharing your work on this as a contrib module!!!
It sure is a great place to start or to further decide what we expect core to have as features so that we can see this issue as "fixed".
Your module's code takes into account fields, JSON:API, bubble-ups and even emails.
Maybe we could focus on JSON:API and fields for this core issue?
Comment #26
bserem commentedI made an attempt and fixed the failing PHPUnit test. There are 2 more now but they seem related to gitlab artifacts and not to the changes in code.
I'm trying to understand what this is about.
Comment #27
vensires@bserem thanks for the update on the last day of the year :)
I would also like to know your opinion on @mxr576's work and which of his achieved goals we would like to have in core too.
PS: Happy new year everyone! Best wishes to all of you!
Comment #28
bserem commentedI'm failing to fix this properly, mainly because many tests need to be updated for the new permissions, but that generates a very important question:
Do we update the tests to assume the new permissions are in place or do we update the tests to assume they don't have access to usernames?
I'm gonna stop here and wait for some feedback.
Comment #30
prudloff commented@bserem in order to keep the scope of this issue somewhat manageable, I think we should keep the existing behavior (so I guess grant the permission to everyone at install and in a postupdate hook?).
Being able to remove the permission will already be an improvement for site admins.
And then we can open a followup to discuss changing the default.