Skip to content
GitLab
Next
    • GitLab: the DevOps platform
    • Explore GitLab
    • Install GitLab
    • How GitLab compares
    • Get started
    • GitLab docs
    • GitLab Learn
  • Pricing
  • Talk to an expert
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
    Projects Groups Snippets
  • Sign up now
  • Login
  • Sign in / Register
  • GitLab FOSS GitLab FOSS
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 1
    • Issues 1
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
    • Requirements
  • Merge requests 1
    • Merge requests 1
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Container Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Metrics
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • Code review
    • Insights
    • Issue
    • Repository
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Commits
  • Issue Boards
Collapse sidebar
  • GitLab.orgGitLab.org
  • GitLab FOSSGitLab FOSS
  • Issues
  • #14900
Closed
Open
Issue created Apr 04, 2016 by Jose Torres@balamebContributor

Bypassing password authentication of users that have 2FA enabled

HO: 128085
ZD: https://gitlab.zendesk.com/agent/tickets/18967

copied over form HO as to maintain quality of report

Summary

When a user has 2FA enabled, it's possible to sign in as that user without the need to know its password.

Reproduction

To reproduce this attack, you need two users that both have 2FA enabled. For the sake of this PoC, lets call them Jane and John. Jane is the attacker and wants to get access to John's account. John his username is john. Jane knows John's username. Here's how you can reproduce it:

  • as Jane, go to the sign in page and enter your username and password
  • in the background, it sets Jane's user ID in session[:otp_user_id]
  • you now need to enter Jane's 2FA code in order to get access to the account
  • now intercept all your network traffic with a tool like Burp Suite and capture the request that is send when you submit the 2FA token - it looks like this:
> POST /users/sign_in HTTP/1.1
> Host: 159.xxx.xxx.xxx
> ...

> ----------1881604860
> Content-Disposition: form-data; name="user[otp_attempt]"
> 
> 212421
> ----------1881604860--
  • now add the login header to the request - the request now looks like:
> POST /users/sign_in HTTP/1.1
> Host: 159.xxx.xxx.xxx
> ...

> ----------1881604860
> Content-Disposition: form-data; name="user[otp_attempt]"
> 
> 212421
> ----------1881604860
> Content-Disposition: form-data; name="user[login]"
> 
> john
> ----------1881604860--
  • now, instead of 212421, send a valid OTP code for john to the server
  • Jane is now signed in as John by entering her own password and John's OTP code - Jane still doesn't know John's password

Impact

The OTP codes are 6 numbers that change every 30 seconds. I haven't looked whether the server allows time drift. This would increase the chance that an attacker guesses the right OTP code for the account. As a PoC, I could run a small attack against GitLab.com, but I haven't been able to reach @Sytse to ask for permission. ;)

Origin of the issue

This issue originates from the find_user method in the SessionsController. It returns a User object in two different ways: the first returns the object based on params[:login] parameter. The second one if sessions[:otp_user_id]. The params[:login] parameter takes precedence over the ID stored in the session. This means that if the params[:login] is specified in the request when the 2FA code needs to be verified, a different user can be selected to verify the code against. Here's the method:

# app/controllers/sessions_controller.rb:58
def find_user
  if user_params[:login]
    User.by_login(user_params[:login])
  elsif user_params[:otp_attempt] && session[:otp_user_id]
    User.find(session[:otp_user_id])
  end
end

Side note

This also leaks if someone has 2FA enabled. If the request is sent with a username or email from someone that doesn't have 2FA enabled, the server responds with the error "Invalid username/password". In case the user has 2FA enabled, it responds with "Invalid two-factor code". To proof this, @DouweM has 2FA enabled (good job!) on gitlab.com and @Sytse hasn't (you should enable it ;).

Fix

Here's a fix (needs specs to proof that it works): 2fa-password-bypass.diff (F83019).


/cc @DouweM

Assignee
Assign to
Time tracking