Merge request discussion JSON endpoint exposes all User attributes/columns including secrets, some encrypted, some not
Steps to reproduce:
- Create a merge request
- Start a discussion on the diff
- Resolve the discussion
- Request
/:namespace/:project/merge_requests/:iid/discussions.json - Each JSON array element represents a discussion. Each discussion has a
resolved_byattribute that contains the full JSON representation of theUserrecord of the person who resolved the discussion, which in this case would be you.
In my case it looks like this:
"resolved_by": {
"id": 87854,
"email": "douwe@gitlab.com",
"created_at": "2015-01-20T14:14:06.510Z",
"updated_at": "2018-09-27T08:21:41.743Z",
"name": "Douwe Maan",
"admin": false,
"projects_limit": 100000,
"skype": "",
"linkedin": "",
"twitter": "DouweM",
"bio": "Engineering Manager, Create at GitLab",
"username": "DouweM",
"can_create_group": true,
"can_create_team": false,
"state": "active",
"color_scheme_id": 2,
"password_expires_at": null,
"created_by_id": null,
"avatar": {
"url": "https://storage.googleapis.com/gitlab-gprd-uploads/user/avatar/87854/avatar.png"
},
"hide_no_ssh_key": false,
"website_url": "https://douwe.me",
"last_credential_check_at": null,
"admin_email_unsubscribed_at": null,
"notification_email": "douwe@gitlab.com",
"hide_no_password": false,
"password_automatically_set": false,
"location": "Utrecht, the Netherlands",
"public_email": "",
"encrypted_otp_secret": "<snip, encrypted using `Gitlab::Application.secrets.otp_key_base` and the next two attributes>",
"encrypted_otp_secret_iv": "<snip>",
"encrypted_otp_secret_salt": "<snip>",
"otp_required_for_login": true,
"otp_backup_codes": [
"<snip, hashed using BCrypt>",
"<snip>",
"<snip>",
"<snip>",
"<snip>",
"<snip>",
"<snip>",
"<snip>",
"<snip>",
"<snip>"
],
"dashboard": "projects",
"project_view": "files",
"consumed_timestep": 51259923,
"layout": "fluid",
"hide_project_limit": false,
"note": "",
"otp_grace_period_started_at": "2017-12-05T10:58:54.326Z",
"external": false,
"organization": "GitLab",
"incoming_email_token": "<snip, unencrypted>",
"auditor": false,
"ghost": null,
"require_two_factor_authentication_from_group": true,
"two_factor_grace_period": 48,
"notified_of_own_activity": null,
"support_bot": null,
"last_activity_on": "2018-09-27",
"preferred_language": "en",
"email_opted_in": null,
"email_opted_in_ip": null,
"email_opted_in_source_id": null,
"email_opted_in_at": null,
"theme_id": 1,
"accepted_term_id": 3,
"feed_token": "<snip, unencrypted>",
"private_profile": false,
"roadmap_layout": "months",
"include_private_contributions": null
}
All of the <snip>s (encrypted_otp_secret, encrypted_otp_secret_iv, encrypted_otp_secret_salt, otp_backup_codes, incoming_email_token, feed_token) are obviously sensitive. The first 4 are encrypted, but the incoming_email_token and feed_token are not, and the one can be used to create issues/comments as the user in question, and the other to read RSS feeds that can contain all kinds of sensitive information.
The underlying issue is that expose :resolved_by inside the DiscussionEntity does not specify using: NoteUserEntity like it does on NoteEntity, which would have limited the exposed attributes to a safe set.
We include BlocksJsonSerialization on User to prevent exactly this scenario; I don't know why it didn't work in this case. cc @rspeicher
The issue was introduced in https://gitlab.com/gitlab-org/gitlab-ce/commit/3e66795ef1ff1228906239763910b051d8afcc37, which went into 11.1.0.