DEFENSA - Mitigations & Theory
PART A: Mitigations - Cómo Defenderse
1. Prepared Statements (MEJOR SOLUCIÓN)
PHP - MySQLi
✗ VULNERABLE:
php
$username = $_GET['username'];
$query = "SELECT * FROM users WHERE username = '" . $username . "'";
$result = mysqli_query($conn, $query);✓ SEGURO - Procedural:
php
$username = $_GET['username'];
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $username); // "s" = string type
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row['username'];
}Tipos: "i" (int), "s" (string), "d" (double), "b" (blob)
PHP - PDO (Más moderno)
✓ SEGURO:
php
$username = $_GET['username'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
// Con named placeholders:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $username]);Python - SQLAlchemy
✓ SEGURO:
python
from sqlalchemy import text
username = request.args.get('username')
query = text("SELECT * FROM users WHERE username = :username")
result = db.session.execute(query, {"username": username})Python - Django ORM
✓ SEGURO (automático):
python
from django.contrib.auth.models import User
user = User.objects.filter(username=request.GET.get('username')).first()
# Django automáticamente escapa los parámetrosNode.js - Express
✓ SEGURO:
javascript
const username = req.query.username;
const query = 'SELECT * FROM users WHERE username = ?';
connection.query(query, [username], (error, results) => {
// ...
});Java - JDBC
✓ SEGURO:
java
String username = request.getParameter("username");
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, username); // Posición 1, tipo String
ResultSet rs = stmt.executeQuery();2. Input Validation
Validar Tipo de Dato
php
// ✓ ID debe ser número
$id = intval($_GET['id']); // Convierte a INT
$query = "SELECT * FROM products WHERE id = " . $id;
// O mejor:
$id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($id === false) {
die("ID inválido");
}Validar Formato
php
// ✓ Email debe ser email
$email = filter_var($_GET['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
die("Email inválido");
}
// ✓ URL debe ser URL
$url = filter_var($_GET['url'], FILTER_VALIDATE_URL);Whitelist de Valores
php
// ✓ Sort solo puede ser "name", "price", o "date"
$sort = $_GET['sort'];
$allowed = ['name', 'price', 'date'];
if (!in_array($sort, $allowed)) {
die("Sort inválido");
}
$query = "SELECT * FROM products ORDER BY " . $sort;Longitud Máxima
php
// ✓ Username máximo 50 caracteres
if (strlen($_GET['username']) > 50) {
die("Username muy largo");
}
$username = substr($_GET['username'], 0, 50);3. Least Privilege (Permisos Mínimos)
Crear usuario con permisos limitados
sql
-- ✗ MAL: Usuario con permisos amplios
CREATE USER 'app'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'app'@'localhost';
-- ✓ BIEN: Solo lo necesario
CREATE USER 'app'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON target_app.* TO 'app'@'localhost';
-- NO DROP, ALTER, CREATE, DELETEUsuario solo lectura
sql
CREATE USER 'read_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON target_app.* TO 'read_user'@'localhost';Usuario para reportes
sql
CREATE USER 'report_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON target_app.users, target_app.products TO 'report_user'@'localhost';4. Deshabilitar Funciones Peligrosas
MySQL - Deshabilitar FILE Access
bash
# En /etc/mysql/my.cnf
[mysqld]
# Deshabilitar LOAD_FILE y INTO OUTFILE
skip-name-resolve
bind-address = 127.0.0.1
# Prevenir acceso a archivos
set-variable=secure_file_priv=/tmp5. Error Handling
✗ MALO: Mostrar errores
php
if (!$result) {
echo "Error: " . mysqli_error($conn);
// ← Revela estructura de BD
}✓ BIEN: Ocultar detalles
php
if (!$result) {
error_log("DB Error: " . mysqli_error($conn)); // Log interno
echo "An error occurred. Please try again later."; // Usuario
}Configuración de PHP
ini
display_errors = Off
log_errors = On
error_log = /var/log/php-errors.log6. Web Application Firewall (WAF)
- ModSecurity: Open source, reglas para SQLi
- AWS WAF: Automático, cloudnative
- Cloudflare: DDoS + SQLi protection
7. Rate Limiting
php
// Limitar intentos fallidos
$failed_attempts = get_failed_attempts($_SERVER['REMOTE_ADDR']);
if ($failed_attempts > 5) {
http_response_code(429); // Too Many Requests
die("Too many attempts. Try again later.");
}
if (query_fails()) {
increment_failed_attempts($_SERVER['REMOTE_ADDR']);
}En Nginx
nginx
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api {
limit_req zone=api burst=20;
}8. Logging & Monitoring
php
// Detectar queries sospechosas
if (strpos($query, "UNION") !== false ||
strpos($query, "DROP") !== false) {
error_log("SUSPICIOUS: " . $query);
alert_security_team();
}Resumen de Defensas
| Defensa | Efectividad | Recomendación |
|---|---|---|
| Prepared Statements | ★★★★★ | USAR SIEMPRE |
| Input Validation | ★★★★☆ | Complementaria |
| Least Privilege | ★★★★☆ | Importante |
| Error Handling | ★★★☆☆ | Defensa en profundidad |
| WAF | ★★★★☆ | Capa extra buena |
| Rate Limiting | ★★★☆☆ | Complementaria |
LA REGLA DE ORO: Usa Prepared Statements. TODO. EL. TIEMPO.
PART B: Theory - Conceptos Fundamentales
¿Por Qué Ocurre SQL Injection?
Falta de Separación entre Código y Datos
php
// ✗ MEZCLA código con datos
$sql = "SELECT * FROM users WHERE id=" . $_GET['id'];
// Usuario inyecta: ?id=1 OR 1=1 --
// Ahora es código, no dato
// ✓ SEPARA código de datos
$sql = "SELECT * FROM users WHERE id=?";
$params = [$_GET['id']];
// El ? indica: "aquí va DATO, no código"
// Se escapa automáticamentePrincipios de Seguridad Violados
1. Principle of Least Privilege
Aplicación solo debe tener permisos necesarios.
sql
-- ✗ MAL
GRANT ALL PRIVILEGES ON *.* TO 'app'@'localhost';
-- Si SQLi ocurre = acceso total
-- ✓ BIEN
GRANT SELECT, INSERT, UPDATE ON target_app.* TO 'app'@'localhost';
-- Si SQLi ocurre = limitado2. Defense in Depth
Múltiples capas de seguridad:
Capa 1: Input Validation
Capa 2: Prepared Statements (PRINCIPAL)
Capa 3: Least Privilege en BD
Capa 4: WAF
Capa 5: Rate Limiting
Capa 6: MonitoringSi una falla, las otras protegen.
3. Never Trust User Input
NUNCA asumir entrada es válida.
php
// ✗ Asume validez
$sort = $_GET['sort'];
$query = "SELECT * FROM products ORDER BY " . $sort;
// ✓ Valida siempre
if (!in_array($sort, ['name', 'price', 'date'])) {
die("Invalid sort");
}OWASP Top 10 2021
#1 - Broken Access Control (puede resultar de SQLi)
#2 - Cryptographic Failures (SQLi + datos sin encriptar)
#3 - Injection ← SQL INJECTION AQUÍ
#4 - Insecure Design
#5 - Security Misconfiguration
...MITRE ATT&CK Framework
Tactic: Exploitation
Technique: T1190 - Exploit Public-Facing Application
Ciclo de Vida Completo
1. RECONNAISSANCE
└─ Identifica punto de entrada web vulnerable
2. WEAPONIZATION
└─ Prepara payload SQL malicioso
3. DELIVERY
└─ Inyecta payload en parámetro
4. EXPLOITATION
└─ BD ejecuta código SQL modificado
5. INSTALLATION
└─ Escribe shell web o backdoor
6. COMMAND & CONTROL
└─ Controla servidor comprometido
7. ACTIONS ON OBJECTIVES
└─ Roba datos, modifica, escala privilegiosCWE (Common Weakness Enumeration)
CWE-89: SQL Injection
- Impacto: Alto (confidentiality, integrity, availability)
- Prevalencia: Muy común en aplicaciones legadas
- Causa raíz: CWE-20 (Improper Input Validation)
CVSS Score
SQL Injection típica
Attack Vector: Network (0.85)
Attack Complexity: Low (0.77)
Privileges Required: None (0.85)
User Interaction: None (0.85)
Scope: Changed (1.0)
Confidentiality: High (0.56)
Integrity: High (0.56)
Availability: High (0.56)
= CVSS 9.9 CRÍTICOImpacto Empresarial
Confidentiality (Confidencialidad)
- Robo de datos de clientes
- Robo de credenciales
- Robo de API keys
- GDPR: Multa €20M o 4% revenueIntegrity (Integridad)
- Modificación de datos de usuarios
- Cambio de precios
- Inyección de malware
→ Pérdida de confianza, responsabilidad legalAvailability (Disponibilidad)
- DROP TABLE (eliminación permanente)
- DELETE (eliminación de datos)
- Crash de aplicación
→ Downtime operacional, pérdida de ingresosSecure Development Lifecycle (SDLC)
1. PLANNING
└─ Requerimientos de seguridad
2. ANALYSIS
└─ Threat modeling
3. DESIGN
└─ Diseño seguro (separar código de datos)
4. DEVELOPMENT
└─ Usar prepared statements SIEMPRE
5. TESTING
└─ Pruebas de seguridad (SAST, DAST, pentesting)
6. DEPLOYMENT
└─ Hardening, WAF, monitoring
7. MAINTENANCE
└─ Parches, actualizaciones, code reviewsCode Review Process
Cada línea de código que accede a BD debe:
✓ Usar prepared statements
✓ Tener input validation
✓ Tener error handling
✓ Tener logging
Pull Request rechazado si no cumpleTendencias Modernas (2024)
De:
- Manual (probaba parámetros uno por uno)
- Lento (blind SQLi demoraba horas)
A:
- Automatizado (sqlmap hace todo)
- Rápido (UNION en segundos)
- Sofisticado (bypass WAF automático)
Futuro:
- ORMs más seguros (SQLAlchemy, Django ORM)
- Type-safe databases (Prisma, TypeORM)
- Hardware security (TPM, Secure Enclaves)
- AI/ML detection
Resumen - Principios Clave
- Separar código de datos → prepared statements
- Never trust user input → validación siempre
- Least privilege → usuario de BD con permisos mínimos
- Defense in depth → múltiples capas de seguridad
- Secure by default → configuración segura por defecto
- Monitor and log → detectar intentos
- Keep updated → patches de seguridad
LA REGLA MÁS IMPORTANTE
SI RECUERDAS SOLO UNA COSA: USA PREPARED STATEMENTS
Checklist Implementación Segura
- [ ] Todos los queries usan prepared statements
- [ ] Validación de input en cada parámetro
- [ ] Usuario de BD tiene permisos limitados
- [ ] Errores SQL no se muestran al usuario
- [ ] Logging de queries sospechosas
- [ ] WAF configurado y activo
- [ ] Rate limiting habilitado
- [ ] Auditoría de cambios en BD
- [ ] Monitoreo en producción
- [ ] Testing de seguridad periódico