Security Misconfiguration
CWE: CWE-16 - Configuration
Problema: Configuración deficiente de seguridad
Tipos de Misconfiguration
1. Default Credentials
bash
# Credenciales por defecto en producción
curl -u admin:admin https://api.app.com/api/admin
curl -u root:root https://api.app.com/api/admin
curl -u test:test https://api.app.com/api/admin
# Acceso exitoso = Misconfiguration2. CORS Débil
bash
# CORS permite cualquier origen
curl "https://api.app.com/api/data" \
-H "Origin: https://attacker.com"
# Respuesta:
# Access-Control-Allow-Origin: *
# Access-Control-Allow-Credentials: true ← PELIGROSO
# Atacante accede desde su sitio:
fetch('https://api.app.com/api/data', {
method: 'GET',
credentials: 'include'
})3. HTTP en lugar de HTTPS
bash
# API accesible sin encriptación
curl http://api.app.com/api/data
# Devuelve datos en texto plano
# Man-in-the-middle intercepta credenciales4. Debug Mode Habilitado
bash
# Modo debug en producción
GET https://api.app.com/api/debug
# Respuesta:
{
"debug": true,
"database_url": "postgres://user:pass@db:5432/prod",
"api_keys": [...],
"stack_trace": "..."
}
# Stack traces revelan estructura interna5. Errores Detallados
bash
# API retorna información en errores
curl "https://api.app.com/api/users/999" \
-H "Authorization: Bearer invalid_token"
# Respuesta:
{
"error": "Token verification failed",
"details": "JWT secret is: 'super_secret_key_12345'",
"hint": "Check /config/.env for more info",
"database_error": "Connection to 192.168.1.100:5432 refused"
}6. Información en Headers
bash
curl -v https://api.app.com/api/data
# Response headers:
X-Powered-By: Express 4.17.1 ← Versión
X-AspNet-Version: 4.0.30319 ← Framework
Server: Apache/2.4.1 ← Versión servidor
X-Debug-Token: debugging_enabled
X-API-Version: v2.1.0 ← Versión API7. Archivos Accesibles
bash
# Archivos de configuración
.env
.env.backup
config.json
config.php
database.yml
secrets.yaml
# Archivos administrativos
.git/
.gitignore
.env.production
docker-compose.yml
Dockerfile
requirements.txt
# Acceso directo
curl https://api.app.com/.env
curl https://api.app.com/config.json
curl https://api.app.com/.git/HEAD8. API Versiones Sin Actualizar
bash
# Versiones antiguas aún funcionales
GET /api/v1/users # Versión 1 sin validación
GET /api/v2/users # Versión 2 con validación
GET /api/v3/users # Versión 3 corriente
# Atacante usa v1 que no tiene protecciones
curl "https://api.app.com/api/v1/users/admin/password" \
-d '{"new_password": "hacked"}'Explotación
python
import requests
import json
def check_misconfigurations(api_url):
"""
Verificar errores de configuración
"""
checks = [
{
"name": "Default credentials",
"url": f"{api_url}/api/admin",
"auth": ("admin", "admin"),
"check": lambda r: r.status_code == 200
},
{
"name": "CORS misconfiguration",
"url": f"{api_url}/api/data",
"headers": {"Origin": "https://attacker.com"},
"check": lambda r: "*" in r.headers.get("Access-Control-Allow-Origin", "")
},
{
"name": ".env accessible",
"url": f"{api_url}/.env",
"check": lambda r: r.status_code == 200 and "API_KEY" in r.text
},
{
"name": ".git accessible",
"url": f"{api_url}/.git/HEAD",
"check": lambda r: r.status_code == 200
},
{
"name": "Debug mode enabled",
"url": f"{api_url}/api/debug",
"check": lambda r: "debug" in r.text.lower()
},
{
"name": "Detailed error messages",
"url": f"{api_url}/api/invalid",
"check": lambda r: "stack" in r.text.lower() or "database" in r.text.lower()
}
]
for check in checks:
try:
if "auth" in check:
r = requests.get(check["url"], auth=check["auth"], timeout=5)
else:
headers = check.get("headers", {})
r = requests.get(check["url"], headers=headers, timeout=5)
if check["check"](r):
print(f"[✓] MISCONFIGURATION: {check['name']}")
except:
pass
# Ejecutar
check_misconfigurations("https://api.app.com")Mitigación
1. CORS Restrictivo
python
from flask_cors import CORS
# ✗ NUNCA hacer esto
CORS(app) # Permite todos los orígenes
# ✓ Whitelist de orígenes
CORS(app, resources={
r"/api/*": {
"origins": ["https://trusted-domain.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"max_age": 3600
}
})2. Headers de Seguridad
python
@app.after_request
def set_security_headers(response):
# Ocultar información del servidor
response.headers['Server'] = 'WebServer'
# Deshabilitar CORS genérico
response.headers['Access-Control-Allow-Origin'] = 'https://trusted.com'
# HSTS (HTTPS obligatorio)
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
# Prevenir clickjacking
response.headers['X-Frame-Options'] = 'DENY'
# Prevenir MIME sniffing
response.headers['X-Content-Type-Options'] = 'nosniff'
# CSP
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response3. Remover Información Sensible
python
@app.errorhandler(Exception)
def handle_error(error):
"""
No retornar stack traces en producción
"""
# Logging interno
logger.error(f"Error: {str(error)}", exc_info=True)
# Respuesta al cliente
if app.config['DEBUG']:
return {"error": str(error), "traceback": traceback.format_exc()}, 500
else:
return {
"error": "Internal server error",
"request_id": "xxx-yyy-zzz" # Para debugging
}, 5004. Proteger Archivos Sensibles
nginx
# Nginx - Bloquear acceso
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ /\.(env|git|env\.) {
deny all;
}
location ~ /(config|secrets|keys|credentials) {
deny all;
}5. HTTPS Obligatorio
python
@app.before_request
def enforce_https():
if not request.is_secure and app.config['ENV'] == 'production':
url = request.url.replace('http://', 'https://', 1)
return redirect(url, code=301)6. Deshabilitar Debug en Producción
python
# ✓ Correcto
app.config['DEBUG'] = False
app.config['TESTING'] = False
# ✗ Nunca hacer en producción
app.config['DEBUG'] = True
# Verificar
if app.config['DEBUG']:
raise RuntimeError("DEBUG MODE IS ENABLED IN PRODUCTION!")Checklist
- [ ] HTTPS en todos los endpoints
- [ ] CORS configurado restrictivamente
- [ ] Debug mode deshabilitado
- [ ] Errores sin stack traces
- [ ] Headers de seguridad configurados
- [ ] Sin credenciales por defecto
- [ ] Archivos .env/.git no accesibles
- [ ] Versiones antiguas de API removidas
- [ ] Información del servidor oculta