Skip to content

Merge request discussion JSON endpoint exposes all User attributes/columns including secrets, some encrypted, some not

Steps to reproduce:

  1. Create a merge request
  2. Start a discussion on the diff
  3. Resolve the discussion
  4. Request /:namespace/:project/merge_requests/:iid/discussions.json
  5. Each JSON array element represents a discussion. Each discussion has a resolved_by attribute that contains the full JSON representation of the User record 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.

Edited by Douwe Maan