Guía práctica para proteger aplicaciones web contra las amenazas más críticas: desde inyección SQL hasta vulnerabilidades de IA
Introducción
El OWASP Top 10 se ha consolidado como el estándar de facto para medir riesgos de seguridad en aplicaciones web, y la versión 2025 refleja una evolución dramática del panorama de amenazas: nuevas categorías emergen para abordar vulnerabilidades en inteligencia artificial, supply chain attacks sofisticados y configuraciones inseguras en arquitecturas serverless y containerizadas que no existían hace 5 años.
Según datos de OWASP Foundation y Verizon DBIR 2025, más del 90% de aplicaciones web contienen al menos una vulnerabilidad del Top 10, y el 43% de brechas de datos exitosas explotan directamente estas debilidades conocidas. Lo más preocupante: el tiempo promedio entre descubrimiento de vulnerabilidad y explotación activa en ataques se redujo de 30 días en 2020 a solo 7 días en 2025, dejando ventanas mínimas para remediar antes de que atacantes weaponizen exploits.
Esta guía desglosa las 10 vulnerabilidades más críticas de 2025 con enfoque práctico de producción: qué es cada vulnerabilidad, por qué importa, ejemplos reales de explotación, código vulnerable vs. código seguro, herramientas de detección automatizada, estrategias de mitigación implementables esta semana, y checklist de validación para DevSecOps. No es teoría académica; es arsenal operativo para desarrolladores, arquitectos de seguridad y equipos de operaciones que necesitan proteger aplicaciones en producción ahora.
OWASP Top 10 – 2025: Resumen Ejecutivo
| # | Vulnerabilidad | Cambio vs 2021 | Impacto | Prevalencia |
|---|---|---|---|---|
| A01 | Broken Access Control | ↔️ Mantiene #1 | Crítico | 94% apps |
| A02 | Cryptographic Failures | ↔️ (antes Sensitive Data) | Alto | 60% apps |
| A03 | Injection | ↓ De #1 a #3 | Crítico | 40% apps |
| A04 | Insecure Design | ↔️ Nueva categoría | Alto | 55% apps |
| A05 | Security Misconfiguration | ↔️ | Alto | 85% apps |
| A06 | Vulnerable Components | ↔️ | Crítico | 70% apps |
| A07 | Authentication Failures | ↔️ | Crítico | 45% apps |
| A08 | Software & Data Integrity | 🆕 Nueva (supply chain) | Alto | 30% apps |
| A09 | Logging & Monitoring Failures | ↔️ | Medio | 75% apps |
| A10 | Server-Side Request Forgery | 🆕 SSRF entra al Top 10 | Alto | 25% apps |
Nuevas tendencias 2025:
- AI/ML Vulnerabilities: Prompt injection, model poisoning, data poisoning (categoría emergente, aún no en Top 10 oficial)
- API-specific risks: Broken Object Level Authorization (BOLA), Mass Assignment
- Cloud misconfigurations: S3 buckets públicos, IAM roles sobre-privilegiados

A01: Broken Access Control
Qué es: Fallas que permiten a usuarios acceder a recursos/funcionalidades sin autorización apropiada; atacante puede ver, modificar o eliminar datos de otros usuarios.
Por qué es #1: 94% de aplicaciones testadas tienen algún tipo de falla de control de acceso; es la vulnerabilidad más prevalente y explotada.
Ejemplos de explotación:
1. Insecure Direct Object Reference (IDOR)
javascript// ❌ VULNERABLE: No valida que usuario tenga permiso para ver documento
app.get('/api/documents/:id', async (req, res) => {
const document = await db.documents.findById(req.params.id);
res.json(document); // Cualquier usuario autenticado puede ver cualquier documento
});
// ✅ SEGURO: Valida ownership
app.get('/api/documents/:id', authMiddleware, async (req, res) => {
const document = await db.documents.findById(req.params.id);
if (!document) {
return res.status(404).json({ error: 'Documento no encontrado' });
}
// Verificar que usuario actual es owner o tiene permiso
if (document.userId !== req.user.id && !req.user.hasRole('admin')) {
return res.status(403).json({ error: 'Acceso denegado' });
}
res.json(document);
});
2. Path Traversal
python# ❌ VULNERABLE: Permite acceder a archivos fuera del directorio permitido
@app.route('/download')
def download():
filename = request.args.get('file')
return send_file(f'/uploads/{filename}') # ../../../etc/passwd
# ✅ SEGURO: Valida y sanitiza path
from werkzeug.utils import secure_filename
import os
@app.route('/download')
def download():
filename = request.args.get('file')
safe_filename = secure_filename(filename)
# Construir path completo y validar que está dentro de directorio permitido
base_dir = os.path.abspath('/uploads')
file_path = os.path.abspath(os.path.join(base_dir, safe_filename))
if not file_path.startswith(base_dir):
return jsonify({'error': 'Acceso denegado'}), 403
if not os.path.exists(file_path):
return jsonify({'error': 'Archivo no encontrado'}), 404
return send_file(file_path)
3. Missing Function Level Access Control
javascript// ❌ VULNERABLE: Endpoint admin sin validación de rol
app.delete('/api/users/:id', authMiddleware, async (req, res) => {
await db.users.delete(req.params.id);
res.json({ success: true });
});
// ✅ SEGURO: Valida rol antes de permitir acción
const requireRole = (role) => {
return (req, res, next) => {
if (!req.user.roles.includes(role)) {
return res.status(403).json({ error: 'Permisos insuficientes' });
}
next();
};
};
app.delete('/api/users/:id', authMiddleware, requireRole('admin'), async (req, res) => {
await db.users.delete(req.params.id);
res.json({ success: true });
});
Mitigaciones:
- Deny by default: todo acceso denegado por defecto; whitelist explícita
- Centralized access control: middleware/decorators reutilizables
- Validar ownership: cada request verifica que usuario tiene permiso sobre recurso
- Rate limiting: limita intentos de enumeración
- Logging: audita accesos a recursos sensibles
Herramientas de detección:
- Burp Suite: escáner de IDOR y path traversal
- OWASP ZAP: active scan para access control
- Postman/Newman: tests automatizados de autorización
A02: Cryptographic Failures
Qué es: Fallas en protección de datos sensibles en tránsito y reposo; incluye uso de algoritmos débiles, llaves hardcodeadas, transmisión en claro.
Impacto: Exposición de PII, credenciales, datos financieros, secretos comerciales.
Ejemplos de explotación:
1. Datos sensibles sin cifrar
python# ❌ VULNERABLE: Passwords en texto plano
user = User(
username="ana.garcia",
password="MyP@ssw0rd123" # Almacenado tal cual en DB
)
# ✅ SEGURO: Hash con bcrypt + salt
import bcrypt
password_hash = bcrypt.hashpw(
"MyP@ssw0rd123".encode('utf-8'),
bcrypt.gensalt(rounds=12) # Cost factor 12 (2^12 iteraciones)
)
user = User(
username="ana.garcia",
password_hash=password_hash
)
2. Algoritmos criptográficos débiles
javascript// ❌ VULNERABLE: MD5/SHA1 son criptográficamente rotos
const crypto = require('crypto');
const hash = crypto.createHash('md5').update(password).digest('hex');
// ✅ SEGURO: Usar algoritmos modernos
const argon2 = require('argon2');
const hash = await argon2.hash(password, {
type: argon2.argon2id, // Resistente a GPU/ASIC attacks
memoryCost: 65536, // 64 MB
timeCost: 3, // 3 iteraciones
parallelism: 4 // 4 threads
});
3. Transmisión sin TLS
text# ❌ VULNERABLE: HTTP sin cifrado
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend:3000;
}
}
# ✅ SEGURO: Forzar HTTPS con TLS 1.3
server {
listen 80;
server_name app.example.com;
return 301 https://$server_name$request_uri; # Redirect a HTTPS
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/ssl/certs/app.crt;
ssl_certificate_key /etc/ssl/private/app.key;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
# HSTS: forzar HTTPS en cliente
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location / {
proxy_pass http://backend:3000;
}
}
4. Secretos hardcodeados en código
python# ❌ VULNERABLE: API key en código fuente
DATABASE_URL = "postgresql://user:P@ssw0rd123@db.example.com/prod"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# ✅ SEGURO: Variables de entorno + secrets manager
import os
from aws_secretsmanager import get_secret
DATABASE_URL = os.environ.get('DATABASE_URL')
AWS_CREDENTIALS = get_secret('prod/aws/credentials')
Mitigaciones:
- Clasificar datos: identifica qué datos son sensibles (PII, PCI, PHI)
- Cifrado en reposo: AES-256 para databases, discos, backups
- Cifrado en tránsito: TLS 1.3 obligatorio, HTTP Strict Transport Security
- Key management: AWS KMS, Azure Key Vault, HashiCorp Vault; rotación automática
- No almacenar si no es necesario: minimiza retención de datos sensibles
Herramientas:
- TruffleHog: detecta secretos en código/git history
- GitGuardian: escaneo continuo de repos
- SSL Labs: test configuración TLS
- Qualys SSL Scanner: auditoría de cifrado
A03: Injection
Qué es: Inyección de código malicioso (SQL, NoSQL, OS commands, LDAP) cuando entrada no confiable se ejecuta sin validación.
Por qué bajó de #1: Frameworks modernos (ORMs, prepared statements) han reducido prevalencia, pero sigue siendo crítico.
Ejemplos de explotación:
1. SQL Injection
javascript// ❌ VULNERABLE: Concatenación de strings
app.get('/users/search', async (req, res) => {
const name = req.query.name;
const query = `SELECT * FROM users WHERE name = '${name}'`;
// Input: ' OR '1'='1' --
// Query resultante: SELECT * FROM users WHERE name = '' OR '1'='1' --'
const results = await db.query(query);
res.json(results);
});
// ✅ SEGURO: Prepared statements / Parameterized queries
app.get('/users/search', async (req, res) => {
const name = req.query.name;
const query = 'SELECT * FROM users WHERE name = $1';
const results = await db.query(query, [name]); // Parámetro escapado automáticamente
res.json(results);
});
// ✅ SEGURO: ORM (Sequelize, TypeORM, Prisma)
const results = await User.findAll({
where: { name: req.query.name } // ORM previene injection
});
2. NoSQL Injection (MongoDB)
javascript// ❌ VULNERABLE: Query injection en MongoDB
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username, password });
// Input: {"username": {"$ne": null}, "password": {"$ne": null}}
// Bypasses authentication
});
// ✅ SEGURO: Validar tipos y sanitizar
const { username, password } = req.body;
// Validar que son strings
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Datos inválidos' });
}
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
3. Command Injection
python# ❌ VULNERABLE: Ejecutar comandos OS con input del usuario
import os
@app.route('/ping')
def ping():
host = request.args.get('host')
result = os.system(f'ping -c 4 {host}') # host: 8.8.8.8; rm -rf /
return result
# ✅ SEGURO: Whitelist + subprocess con argumentos separados
import subprocess
import re
@app.route('/ping')
def ping():
host = request.args.get('host')
# Validar formato IP o hostname
if not re.match(r'^[\w\.-]+$', host):
return jsonify({'error': 'Host inválido'}), 400
# subprocess con argumentos separados (no shell=True)
try:
result = subprocess.run(
['ping', '-c', '4', host],
capture_output=True,
timeout=10,
check=True
)
return jsonify({'output': result.stdout.decode()})
except subprocess.TimeoutExpired:
return jsonify({'error': 'Timeout'}), 408
Mitigaciones:
- Parameterized queries: usar siempre prepared statements
- ORMs: prefiere ORMs modernos que previenen injection por defecto
- Input validation: whitelist de caracteres permitidos
- Escape output: si debes construir queries dinámicamente, escapa apropiadamente
- Least privilege: cuentas de DB con permisos mínimos (no usar root/admin)
- WAF: Web Application Firewall con reglas anti-injection
Herramientas:
- SQLMap: testing de SQL injection
- NoSQLMap: NoSQL injection scanner
- Burp Suite: active scan para injection
- Semgrep: análisis estático de código para detectar patterns vulnerables
A04: Insecure Design
Qué es: Fallas de diseño arquitectónico que no pueden resolverse solo con implementación correcta; ausencia de controles de seguridad desde diseño.
Nueva categoría 2021: Enfatiza “shift-left” security; diseñar con seguridad desde inicio.
Ejemplos:
1. Falta de rate limiting
javascript// ❌ VULNERABLE: Sin protección contra brute force
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (user && await bcrypt.compare(password, user.passwordHash)) {
return res.json({ token: generateToken(user) });
}
res.status(401).json({ error: 'Credenciales inválidas' });
});
// ✅ SEGURO: Rate limiting + account lockout
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // 5 intentos
message: 'Demasiados intentos de login, intenta en 15 minutos'
});
app.post('/login', loginLimiter, async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
// Account lockout después de N intentos fallidos
if (user && user.failedAttempts >= 5) {
return res.status(423).json({
error: 'Cuenta bloqueada por múltiples intentos fallidos'
});
}
if (user && await bcrypt.compare(password, user.passwordHash)) {
await User.updateOne({ _id: user._id }, { failedAttempts: 0 });
return res.json({ token: generateToken(user) });
}
if (user) {
await User.updateOne(
{ _id: user._id },
{ $inc: { failedAttempts: 1 } }
);
}
res.status(401).json({ error: 'Credenciales inválidas' });
});
2. Business logic flaws
python# ❌ VULNERABLE: Puede comprar con saldo negativo
@app.route('/purchase', methods=['POST'])
def purchase():
user = get_current_user()
amount = request.json.get('amount')
user.balance -= amount # No valida que balance >= amount
db.session.commit()
return jsonify({'success': True})
# ✅ SEGURO: Validar lógica de negocio
@app.route('/purchase', methods=['POST'])
def purchase():
user = get_current_user()
amount = request.json.get('amount')
# Validaciones de negocio
if amount <= 0:
return jsonify({'error': 'Monto inválido'}), 400
if user.balance < amount:
return jsonify({'error': 'Saldo insuficiente'}), 402
# Transacción atómica
try:
with db.session.begin_nested():
user.balance -= amount
transaction = Transaction(
user_id=user.id,
amount=amount,
timestamp=datetime.now()
)
db.session.add(transaction)
db.session.commit()
except Exception as e:
db.session.rollback()
return jsonify({'error': 'Error procesando compra'}), 500
return jsonify({'success': True, 'new_balance': user.balance})
Mitigaciones:
- Threat modeling: STRIDE, PASTA durante fase de diseño
- Security requirements: definir requisitos de seguridad antes de codificar
- Secure design patterns: usar patrones probados (circuit breaker, rate limiter)
- Abuse cases: documentar cómo atacantes podrían abusar del sistema
- Defense in depth: múltiples capas de control
A05: Security Misconfiguration
Qué es: Configuraciones inseguras en cualquier capa del stack: servidores web, bases de datos, frameworks, cloud services.
Prevalencia: 85% de aplicaciones tienen alguna misconfiguration.
Ejemplos:
1. Headers de seguridad faltantes
text# ❌ VULNERABLE: Sin security headers
server {
listen 443 ssl;
server_name app.example.com;
location / {
proxy_pass http://backend:3000;
}
}
# ✅ SEGURO: Headers de seguridad completos
server {
listen 443 ssl http2;
server_name app.example.com;
# Prevenir clickjacking
add_header X-Frame-Options "DENY" always;
# XSS protection
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Permissions Policy
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location / {
proxy_pass http://backend:3000;
}
}
2. Información sensible en mensajes de error
javascript// ❌ VULNERABLE: Stack traces en producción
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack // Expone estructura interna
});
});
// ✅ SEGURO: Mensajes genéricos en producción
app.use((err, req, res, next) => {
// Log completo internamente
console.error('Error:', err);
// Respuesta genérica al cliente
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Error interno del servidor'
: err.message // Stack trace solo en dev
});
});
3. Configuración por defecto insegura
text# ❌ VULNERABLE: MongoDB sin autenticación
# docker-compose.yml
services:
mongodb:
image: mongo:7.0
ports:
- "27017:27017" # Expuesto a internet sin auth
# ✅ SEGURO: Autenticación + red interna
services:
mongodb:
image: mongo:7.0
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
networks:
- backend # Solo accesible desde containers de backend
# No exponer puerto públicamente
api:
image: myapi:latest
environment:
MONGODB_URI: mongodb://admin:${MONGO_PASSWORD}@mongodb:27017/prod?authSource=admin
networks:
- backend
- frontend
networks:
backend:
internal: true # Red interna sin acceso a internet
frontend:
Mitigaciones:
- Hardening guides: seguir CIS Benchmarks, NIST guidelines
- Configuration management: Ansible, Terraform con configs versionadas
- Security headers: implementar todos los headers recomendados
- Disable features no usadas: servicios, puertos, endpoints innecesarios
- Auditorías regulares: scanners de configuración (Prowler, ScoutSuite)
Herramientas:
- Mozilla Observatory: analiza security headers
- Security Headers: checklist de headers HTTP
- Docker Bench: audita configuración de Docker
- AWS Config / Azure Policy: auditoría continua de cloud
A06: Vulnerable and Outdated Components
Qué es: Uso de bibliotecas, frameworks, dependencias con vulnerabilidades conocidas (CVEs).
Prevalencia: 70% de aplicaciones usan componentes con vulnerabilidades conocidas.
Impacto real:
- Equifax breach 2017: Apache Struts vulnerable (CVE-2017-5638) → 147M registros expuestos
- Log4Shell 2021: Log4j vulnerable (CVE-2021-44228) → millones de sistemas comprometidos
Ejemplos:
json// ❌ VULNERABLE: package.json con versiones antiguas
{
"dependencies": {
"express": "4.16.0", // Versión 2018 con vulnerabilidades conocidas
"lodash": "4.17.11", // Prototype pollution
"axios": "0.18.0" // SSRF vulnerability
}
}
// ✅ SEGURO: Versiones actualizadas
{
"dependencies": {
"express": "^4.19.2",
"lodash": "^4.17.21",
"axios": "^1.6.8"
}
}
Mitigaciones:
- Inventario de componentes: Software Bill of Materials (SBOM)
- Vulnerability scanning: Dependabot, Snyk, WhiteSource
- Update regularly: políticas de actualización (ej: mensual para minor, inmediato para critical)
- Monitor advisories: suscribirse a GitHub Security Advisories, NVD
- Pruning: eliminar dependencias no usadas
Herramientas:
bash# npm audit
npm audit
npm audit fix
# Snyk
snyk test
snyk monitor
# OWASP Dependency-Check
dependency-check --project myapp --scan ./
# Trivy (para containers)
trivy image myapp:latest
Automation en CI/CD:
text# GitHub Actions
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Fail on high severity
run: snyk test --severity-threshold=high
A07: Identification and Authentication Failures
Qué es: Fallas en verificación de identidad, gestión de sesiones, autenticación; permite credential stuffing, brute force, session hijacking.
Ejemplos:
1. Passwords débiles permitidos
python# ❌ VULNERABLE: Sin requisitos de complejidad
def register(username, password):
if len(password) < 6:
raise ValueError("Password muy corta")
user = User(username=username, password=hash(password))
db.save(user)
# ✅ SEGURO: Validación robusta
import re
from zxcvbn import zxcvbn # Password strength estimator
def validate_password(password):
errors = []
if len(password) < 12:
errors.append("Mínimo 12 caracteres")
if not re.search(r'[A-Z]', password):
errors.append("Debe contener mayúsculas")
if not re.search(r'[a-z]', password):
errors.append("Debe contener minúsculas")
if not re.search(r'\d', password):
errors.append("Debe contener números")
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
errors.append("Debe contener caracteres especiales")
# Validar contra diccionario y patrones comunes
strength = zxcvbn(password)
if strength['score'] < 3: # 0-4, donde 4 es máximo
errors.append("Password demasiado común o predecible")
return errors
def register(username, password):
errors = validate_password(password)
if errors:
raise ValueError(f"Password inválida: {', '.join(errors)}")
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))
user = User(username=username, password_hash=password_hash)
db.save(user)
2. Session fixation
javascript// ❌ VULNERABLE: Reutilizar session ID después de login
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.username, req.body.password);
if (user) {
req.session.userId = user.id; // Mantiene mismo session ID
res.json({ success: true });
}
});
// ✅ SEGURO: Regenerar session ID después de login
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.username, req.body.password);
if (user) {
req.session.regenerate((err) => { // Nuevo session ID
if (err) {
return res.status(500).json({ error: 'Error de sesión' });
}
req.session.userId = user.id;
res.json({ success: true });
});
}
});
3. Ausencia de MFA
javascript// ✅ SEGURO: Implementar TOTP MFA
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Setup MFA
app.post('/mfa/setup', authMiddleware, async (req, res) => {
const secret = speakeasy.generateSecret({
name: `MyApp (${req.user.email})`
});
await User.updateOne(
{ _id: req.user.id },
{ mfaSecret: secret.base32, mfaEnabled: false }
);
const qrCode = await QRCode.toDataURL(secret.otpauth_url);
res.json({ qrCode, secret: secret.base32 });
});
// Verify y activar MFA
app.post('/mfa/verify', authMiddleware, async (req, res) => {
const { token } = req.body;
const user = await User.findById(req.user.id);
const verified = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: token
});
if (verified) {
await User.updateOne({ _id: user.id }, { mfaEnabled: true });
res.json({ success: true });
} else {
res.status(401).json({ error: 'Código inválido' });
}
});
// Login con MFA
app.post('/login', async (req, res) => {
const { username, password, mfaToken } = req.body;
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
if (user.mfaEnabled) {
if (!mfaToken) {
return res.status(403).json({ error: 'MFA token requerido' });
}
const mfaValid = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: mfaToken
});
if (!mfaValid) {
return res.status(401).json({ error: 'MFA token inválido' });
}
}
const token = generateJWT(user);
res.json({ token });
});
Mitigaciones:
- MFA obligatorio: especialmente para cuentas admin/privilegiadas
- Password policies: longitud mínima 12, complejidad, no reutilización
- Account lockout: bloqueo temporal después de N intentos fallidos
- Session management: timeouts, regeneración de IDs, secure cookies
- OAuth/OIDC: delegar autenticación a providers confiables
A08: Software and Data Integrity Failures
Qué es: Nueva categoría enfocada en supply chain attacks; código/datos sin verificación de integridad pueden ser comprometidos.
Ejemplos:
1. Dependencias sin verificación de integridad
xml<!-- ❌ VULNERABLE: CDN sin SRI -->
<script src="https://cdn.example.com/jquery-3.6.0.min.js"></script>
<!-- ✅ SEGURO: Subresource Integrity (SRI) -->
<script
src="https://cdn.example.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
crossorigin="anonymous">
</script>
2. CI/CD pipeline sin verificación
text# ❌ VULNERABLE: Pipeline sin signing
steps:
- name: Build
run: npm run build
- name: Deploy
run: aws s3 sync ./dist s3://my-bucket
# ✅ SEGURO: Verificar artifacts con firma digital
steps:
- name: Build
run: npm run build
- name: Sign artifact
run: |
cosign sign-blob --key cosign.key dist/bundle.js > bundle.js.sig
- name: Verify signature
run: |
cosign verify-blob --key cosign.pub --signature bundle.js.sig dist/bundle.js
- name: Deploy
run: aws s3 sync ./dist s3://my-bucket
3. Deserialization insegura
python# ❌ VULNERABLE: pickle permite arbitrary code execution
import pickle
def load_user_data(data):
return pickle.loads(data) # RCE si data es maliciosa
# ✅ SEGURO: Usar JSON o validar estructura
import json
from jsonschema import validate
user_schema = {
"type": "object",
"properties": {
"username": {"type": "string"},
"email": {"type": "string", "format": "email"}
},
"required": ["username", "email"]
}
def load_user_data(data):
try:
user = json.loads(data)
validate(instance=user, schema=user_schema)
return user
except (json.JSONDecodeError, ValidationError) as e:
raise ValueError(f"Datos inválidos: {e}")
Mitigaciones:
- Software signing: firmar releases con GPG/code signing certificates
- SRI para dependencies: Subresource Integrity en CDNs
- SBOM: Software Bill of Materials para auditoría de supply chain
- Verificar checksums: validar integridad de packages descargados
- Secure pipelines: protect CI/CD con secrets management, branch protection
A09: Security Logging and Monitoring Failures
Qué es: Insuficiente logging, monitoreo y alertas; imposibilita detectar brechas en tiempo útil.
Impacto: Tiempo promedio de detección 287 días sin logging adecuado.
Qué loggear:
javascript// ✅ SEGURO: Logging comprehensivo
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'security.log' })
]
});
// Login attempts
app.post('/login', async (req, res) => {
const { username, password } = req.body;
logger.info('Login attempt', {
username,
ip: req.ip,
userAgent: req.get('user-agent'),
timestamp: new Date().toISOString()
});
const user = await authenticate(username, password);
if (user) {
logger.info('Login successful', { username, userId: user.id });
return res.json({ token: generateToken(user) });
} else {
logger.warn('Login failed', { username, ip: req.ip });
return res.status(401).json({ error: 'Invalid credentials' });
}
});
// Access to sensitive resources
app.get('/api/customers/:id', authMiddleware, async (req, res) => {
logger.info('Customer data access', {
userId: req.user.id,
customerId: req.params.id,
action: 'READ',
timestamp: new Date().toISOString()
});
// ... fetch customer data
});
// Failed authorization
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
logger.warn('Unauthorized access attempt', {
userId: req.user?.id || 'anonymous',
path: req.path,
method: req.method,
ip: req.ip
});
}
next(err);
});
Mitigaciones:
- Log security events: logins, authorization failures, input validation errors
- Centralized logging: ELK, Splunk, Datadog, CloudWatch
- Alerting: notificaciones automáticas para eventos críticos
- Log retention: mantener logs suficiente tiempo para forensics (90+ días)
- Protect logs: inmutables, cifrados, acceso restringido
A10: Server-Side Request Forgery (SSRF)
Qué es: Aplicación hace requests HTTP a URLs controladas por atacante; puede acceder a servicios internos, metadata endpoints.
Nueva en Top 10: Prevalencia aumentó con arquitecturas serverless y microservicios.
Ejemplo de explotación:
python# ❌ VULNERABLE: Fetch URL sin validación
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
response = requests.get(url) # SSRF: puede acceder metadata AWS
return response.text
# Exploit: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# ✅ SEGURO: Whitelist + validación
import urllib.parse
import ipaddress
ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']
def is_safe_url(url):
try:
parsed = urllib.parse.urlparse(url)
# Solo HTTPS
if parsed.scheme != 'https':
return False
# Whitelist de dominios
if parsed.hostname not in ALLOWED_DOMAINS:
return False
# Resolver DNS y verificar que no es IP privada
import socket
ip = socket.gethostbyname(parsed.hostname)
ip_obj = ipaddress.ip_address(ip)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
return False
return True
except Exception:
return False
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
if not is_safe_url(url):
return jsonify({'error': 'URL no permitida'}), 403
try:
response = requests.get(url, timeout=5)
return response.text
except requests.RequestException as e:
return jsonify({'error': 'Error fetching URL'}), 500
Mitigaciones:
- Whitelist: permitir solo dominios específicos
- Validar esquema: solo https://
- Bloquear IPs privadas: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.169.254
- Network segmentation: servicios internos en red aislada
- Disable redirects: prevenir bypass vía redirecciones
Checklist de Implementación
Semana 1: Quick Wins
- Habilitar security headers (CSP, HSTS, X-Frame-Options)
- Implementar rate limiting en login/API endpoints
- Configurar MFA para cuentas admin
- Scan de dependencias con npm audit/Snyk
Semana 2: Access Control
- Auditar endpoints sin autorización
- Implementar RBAC o ABAC
- Validar ownership en cada request a recursos
- Rate limiting y account lockout
Semana 3: Injection Prevention
- Migrar queries a prepared statements/ORM
- Validación de inputs con whitelisting
- Escape de outputs
- WAF con reglas anti-injection
Semana 4: Monitoring & Logging
- Centralized logging (ELK, Splunk)
- Alertas para eventos críticos
- SIEM para correlación
- Incident response playbooks
Herramientas de Seguridad Esenciales
SAST (Static Application Security Testing):
- SonarQube
- Semgrep
- Checkmarx
- Fortify
DAST (Dynamic Application Security Testing):
- OWASP ZAP
- Burp Suite
- Acunetix
- Netsparker
SCA (Software Composition Analysis):
- Snyk
- WhiteSource
- Black Duck
- Dependabot
Runtime Protection:
- WAF: ModSecurity, Cloudflare WAF, AWS WAF
- RASP: Contrast Security, Sqreen
Conclusión: Seguridad como Proceso Continuo
Las vulnerabilidades del OWASP Top 10 no son bugs ocasionales sino patrones sistemáticos que persisten porque equipos priorizan features sobre seguridad, carecen de training adecuado o implementan seguridad como afterthought. La versión 2025 del Top 10 refleja evolución de amenazas —supply chain, SSRF, insecure design— pero los fundamentos siguen siendo los mismos: validar inputs, proteger outputs, implementar least privilege, loggear todo y asumir que serás atacado.
No necesitas implementar todo simultáneamente; prioriza basándote en riesgo real. Empieza con quick wins de alto impacto (security headers, MFA, dependency scanning), continúa con access control robusto y prevención de injection, y finalmente implementa monitoring comprehensivo. Usa este artículo como checklist operativo, no como documento para archivar.
La seguridad no es estado final; es práctica continua de identificar, priorizar y remediar vulnerabilidades antes de que atacantes las exploten. Implementa scanning automatizado en CI/CD, capacita a tu equipo en secure coding, realiza code reviews con enfoque en seguridad y mide progreso con métricas concretas.
El OWASP Top 10 es tu mapa; tu responsabilidad es ejecutar el recorrido. Empieza hoy.
