Skip to content

Improper Inventory Management

CWE: CWE-1059 - Incomplete API Documentation
Problema: Falta de control sobre versiones de API, endpoints expuestos, etc


¿Qué es?

No mantener un inventario adecuado de:

  • Versiones de API (v1, v2, v3)
  • Endpoints activos/deprecados
  • Recursos expuestos
  • Hosts/dominios
  • Documentación actualizada

Vectores de Ataque

1. Versiones Antiguas Sin Actualizar

bash
# API actual
GET /api/v5/users
# Requiere autorización y validación

# Versión antigua aún funcional
GET /api/v1/users
# Sin validación, BOLA vulnerable
GET /api/v2/users
# Validación débil

2. Endpoints Deprecados Activos

bash
# Endpoint antiguo aún retorna datos
GET /api/admin/backup-download
# Retorna backup de la DB

# Endpoint de testing aún expuesto
GET /api/test/all-credentials

# Endpoint internal exposición
GET /api/internal/debug

3. Descubrimiento de Endpoints

bash
# Escanear endpoints comunes
endpoints = [
    '/api/admin',
    '/api/superadmin',
    '/api/debug',
    '/api/test',
    '/api/internal',
    '/api/backup',
    '/api/export',
    '/admin/users',
    '/superadmin/settings'
]

for endpoint in endpoints:
    curl "https://api.app.com$endpoint" -w "%{http_code}\n"

4. Documentación Pública con Secretos

bash
# Documentación expone detalles
GET /api/docs          # Swagger/OpenAPI
GET /api/v1/docs
GET /graphql
GET /graphiql

# Swagger revela:
- Todos los endpoints
- Parámetros esperados
- Tipos de respuesta
- Fallos de seguridad

curl https://api.app.com/api/docs | jq '.paths | keys'
# Ver todos los endpoints

5. Múltiples Dominios/Hosts

bash
# API en múltiples locaciones
api.app.com
api-v1.app.com
api-dev.app.com
api-staging.app.com
api.app.co.uk
api-backup.app.com

# Algunos sin protección
curl https://api-dev.app.com/api/users
# Sin autenticación

curl https://api-staging.app.com/api/admin
# Credenciales test

6. Falta de Control de Versiones

bash
# Endpoints sin versión
/api/users
/api/products
# Imposible suportar versiones antiguas

# Resultado: Cambios breaking afectan a todos

Enumeración y Explotación

python
import requests
import json
from itertools import product

def discover_api_inventory(target):
    """
    Descubrir todos los endpoints y versiones
    """
    
    # Versiones comunes
    versions = ['v1', 'v2', 'v3', 'v4', 'v5', 'api', 'beta', 'alpha']
    
    # Paths comunes
    paths = [
        'users', 'products', 'orders', 'admin', 'settings',
        'test', 'debug', 'internal', 'backup', 'export',
        'reports', 'analytics', 'logs', 'config'
    ]
    
    # Dominios
    domains = [
        'api',
        'api-v1', 'api-v2',
        'api-dev', 'api-staging',
        'api-prod', 'api-backup',
        'internal-api', 'test-api'
    ]
    
    endpoints_found = []
    
    print("[*] Buscando endpoints...")
    
    # Probar versiones
    for version in versions:
        for path in paths:
            url = f"https://{target}/api/{version}/{path}"
            
            try:
                r = requests.head(url, timeout=3, allow_redirects=False)
                if r.status_code < 500:
                    endpoints_found.append((url, r.status_code))
                    print(f"[✓] {url} ({r.status_code})")
            except:
                pass
    
    # Probar dominios
    domain_base = target.split('.')[0]
    tld = '.'.join(target.split('.')[1:])
    
    for subdomain in domains:
        host = f"{subdomain}.{tld}"
        
        for path in ['', '/api', '/api/users', '/api/admin']:
            url = f"https://{host}{path}"
            
            try:
                r = requests.head(url, timeout=3, allow_redirects=False)
                if r.status_code < 500:
                    endpoints_found.append((url, r.status_code))
                    print(f"[✓] {url} ({r.status_code})")
            except:
                pass
    
    return endpoints_found

# Ejecutar
discover_api_inventory("app.com")

Parsing de Swagger/GraphQL

bash
# Extraer endpoints de Swagger
curl -s "https://api.app.com/api/swagger.json" | jq '.paths | keys'

# Resultado:
[
  "/api/users",
  "/api/users/{id}",
  "/api/admin/settings",
  "/api/internal/debug",
  ...
]

# Extraer parameters
curl -s "https://api.app.com/api/swagger.json" | \
  jq '.paths["/api/users"]["get"].parameters'

# GraphQL - Introspection
curl -X POST "https://api.app.com/graphql" \
  -H "Content-Type: application/json" \
  -d '{"query": "{__schema{types{name,fields{name}}}}"}'

Mitigación

1. Versionamiento Explícito

python
# ✓ Correcto
/api/v1/users    # Versión 1
/api/v2/users    # Versión 2 con cambios
/api/v3/users    # Versión 3

# Soportar múltiples versiones
@app.route('/api/v1/users')
def get_users_v1():
    # Lógica v1
    pass

@app.route('/api/v2/users')
def get_users_v2():
    # Lógica v2 (mejorada)
    pass

# Deprecar versiones antiguas después de tiempo

2. Inventario de Endpoints

python
# Mantener registro de todos los endpoints
ENDPOINTS = {
    'v1': {
        '/users': {'deprecated': True, 'migration': '/v2/users'},
        '/products': {'status': 'active'},
    },
    'v2': {
        '/users': {'status': 'active'},
        '/products': {'status': 'active'},
    },
    'v3': {
        '/users': {'status': 'active'},
        '/products': {'status': 'active'},
    }
}

# Middleware para validar versión
@app.before_request
def validate_version():
    path = request.path
    match = re.match(r'/api/v(\d+)/', path)
    
    if match:
        version = f"v{match.group(1)}"
        
        # Si versión deprecada
        if version not in ENDPOINTS:
            return {"error": f"API {version} is deprecated"}, 410

3. Documentación Segura

python
# No exponer toda la documentación públicamente

@app.route('/api/docs')
def api_docs():
    # Requiere autenticación
    if not request.headers.get('Authorization'):
        return {"error": "Unauthorized"}, 401
    
    # Retornar solo endpoints permitidos para el usuario
    user = get_current_user()
    filtered_docs = filter_docs_by_role(docs, user.role)
    
    return filtered_docs

4. Removir Endpoints Antiguos

python
# Deshabilitar versiones antiguas después de fecha

from datetime import datetime

VERSION_SUPPORT = {
    'v1': {'end_of_life': datetime(2024, 1, 1)},
    'v2': {'end_of_life': datetime(2024, 6, 1)},
    'v3': {'end_of_life': datetime(2025, 1, 1)},
}

@app.before_request
def check_version_support():
    version = extract_version_from_path(request.path)
    
    if version in VERSION_SUPPORT:
        eol = VERSION_SUPPORT[version]['end_of_life']
        
        if datetime.now() > eol:
            return {
                "error": f"API {version} is no longer supported",
                "migrate_to": "v3"
            }, 410

5. Limitar Información en Documentación

python
# Swagger/OpenAPI - No exponer todos los detalles

SWAGGER_CONFIG = {
    "swagger": "2.0",
    "info": {
        "title": "API",
        "version": "3.0"
    },
    "host": "api.app.com",
    "basePath": "/api/v3",
    "schemes": ["https"],
    "paths": {
        "/users": {
            "get": {
                "summary": "List users",
                "parameters": [...],
                "responses": {...},
                # No incluir ejemplos con datos reales
                # No exponer endpoints internos
            }
        }
    }
}

Checklist

  • [ ] Inventario de todas las versiones de API
  • [ ] Endpoint registry/documentation actualizada
  • [ ] Versiones antiguas deprecadas con timeline
  • [ ] Documentación no expone endpoints internos
  • [ ] No hay endpoints test/debug en producción
  • [ ] Todos los hosts/dominios registrados
  • [ ] Versionamiento explícito en URLs
  • [ ] Migración path para endpoints deprecados
  • [ ] EOL date para versiones antiguas