| name | web-security-audit |
| description | Auditoria de seguridad web completa tipo pentest black-box. Ejecuta 70+ tests contra cualquier URL (OWASP Top 10, CVEs, CORS, headers, BBDD, auth, rate limiting, inyeccion) y genera informe HTML profesional con vulnerabilidades, pruebas de concepto y plan de accion. Usa esta skill siempre que el usuario mencione auditoria de seguridad, pentest, revisar seguridad, buscar vulnerabilidades, ciberseguridad, test de penetracion, o quiera verificar que una web es segura antes de lanzar. |
| version | 2.0.0 |
| author | Angel Aparicio / Solutech IA |
Web Security Audit — Pentest Black-Box Profesional
Auditoria de seguridad web externa basada en OWASP Top 10 2025, PTES, y experiencia real de pentesting en produccion (Frontera, Tripath). Cubre Next.js, React, Vue, WordPress, Django, Rails, Laravel, y cualquier stack web moderno. Genera informe HTML profesional en espanol.
Contexto y por que importa
Las webs modernas tienen superficies de ataque enormes: APIs abiertas, bases de datos con RLS mal configurado, headers de seguridad ausentes, y CVEs en frameworks populares. Un atacante con conocimientos basicos y curl puede explotar estas vulnerabilidades en minutos. Esta skill sistematiza lo que un pentester profesional haria, en un formato reproducible y documentado.
Flujo de ejecucion
Fase 0: Autorizacion (obligatorio)
Antes de cualquier test, confirmar con el usuario:
- Tiene autorizacion del propietario para auditar la URL
- Restricciones (no crear cuentas, no tocar produccion, etc.)
- Idioma del informe (por defecto: espanol)
Si no hay autorizacion explicita, NO proceder.
Fase 1: Reconocimiento
Ejecutar las 4 sub-fases en paralelo con Bash:
1A — Headers de seguridad:
curl -sI "https://TARGET"
Checklist de headers (cada uno faltante es un hallazgo):
| Header | Esperado | Severidad si falta |
|---|---|---|
| Strict-Transport-Security | max-age>=31536000; includeSubDomains | Media |
| Content-Security-Policy | Politica restrictiva, sin unsafe-inline | Alta |
| X-Frame-Options | DENY (SAMEORIGIN = media) | Media |
| X-Content-Type-Options | nosniff | Baja |
| Referrer-Policy | strict-origin-when-cross-origin o mas restrictivo | Baja |
| Permissions-Policy | Bloquear camera, microphone, geolocation | Baja |
| X-Powered-By | NO debe estar presente | Alta (info disclosure) |
| Server | NO debe revelar version exacta | Baja |
1B — Deteccion de stack:
PAGE=$(curl -s "https://TARGET")
# Detectar frameworks
echo "$PAGE" | grep -oiE '(_next/|__next|nuxt|vue|angular|react|svelte|gatsby|remix|astro|wordpress|wp-content|wp-includes|django|laravel|rails|flask|express)' | sort -u
# Scripts externos (tracking, analytics, APIs)
echo "$PAGE" | grep -oE 'https?://[^"'\''> ]+\.js' | sort -u
# Buscar Supabase, Firebase, Stripe, etc.
echo "$PAGE" | grep -oiE '(supabase|firebase|googleapis|stripe|algolia|sentry|mixpanel|amplitude|segment|intercom|crisp|hubspot)' | sort -u
1C — DNS e infraestructura:
dig TARGET A +short
dig TARGET MX +short
dig TARGET TXT +short
dig TARGET NS +short
# Detectar CDN (Cloudflare, Vercel, etc.)
1D — robots.txt y sitemap:
curl -s "https://TARGET/robots.txt"
curl -s "https://TARGET/sitemap.xml" | head -50
Fase 2: Busqueda de secretos en JavaScript
Los JS bundles client-side a menudo contienen claves de API, URLs de bases de datos y tokens. Es lo primero que mira un atacante.
# Descargar todos los chunks JS
CHUNKS=$(curl -s "https://TARGET" | grep -oE '(src="|href=")[^"]*\.js' | sed 's/src="//;s/href="//' | sort -u)
mkdir -p /tmp/audit_js
for chunk in $CHUNKS; do
fname=$(basename "$chunk" | head -c 50)
curl -s "https://TARGET${chunk}" > "/tmp/audit_js/${fname}" 2>&1
done
Patrones a buscar en los archivos descargados:
# Supabase / Firebase keys
grep -rhoiE 'eyJ[A-Za-z0-9_/+-]{30,}' /tmp/audit_js/ | sort -u
grep -rhoiE 'sb_publishable[A-Za-z0-9_-]+' /tmp/audit_js/
grep -rhoiE 'AIza[A-Za-z0-9_-]{35}' /tmp/audit_js/ # Firebase
grep -rhoiE 'sk_live_[A-Za-z0-9]{20,}' /tmp/audit_js/ # Stripe secret
grep -rhoiE 'pk_live_[A-Za-z0-9]{20,}' /tmp/audit_js/ # Stripe public
# URLs internas
grep -rhoiE 'https?://[a-z0-9.-]+\.(supabase\.co|firebaseio\.com|firebaseapp\.com)' /tmp/audit_js/ | sort -u
# Env vars
grep -rhoiE 'NEXT_PUBLIC_[A-Z_]+' /tmp/audit_js/ | sort -u
Si se encuentra un JWT de Supabase, decodificar para obtener el project ref:
echo "JWT_TOKEN" | cut -d. -f2 | python3 -c "import sys,base64,json; p=sys.stdin.read().strip(); p+='='*(-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))"
Fase 3: Enumeracion de rutas
Usar un script externo para evitar problemas de escaping en shell:
cat > /tmp/audit_routes.sh << 'SCRIPT'
#!/bin/bash
TARGET=$1
for path in /admin /login /register /signup /api /api/auth /api/auth/callback /api/users /api/admin /api/admin/users /api/rooms /api/bookings /api/payments /api/webhooks /api/stripe /api/contact /api/newsletter /dashboard /panel /perfil /profile /account /settings /checkout /booking /reserva /mis-reservas /.env /.env.local /.env.production /.git/config /.git/HEAD /graphql /swagger /api-docs /health /status /_next/data /debug /info /wp-admin /wp-login.php /wp-json /xmlrpc.php /phpmyadmin /adminer /server-status /server-info; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://${TARGET}${path}")
if [ "$code" != "404" ] && [ "$code" != "000" ]; then
echo " ${path} -> HTTP ${code}"
fi
done
SCRIPT
chmod +x /tmp/audit_routes.sh
bash /tmp/audit_routes.sh "TARGET"
Clasificar resultados:
- Rutas que deberian requerir auth y devuelven 200 = ALTO
- Rutas admin accesibles = CRITICO
- .env, .git accesibles = CRITICO
- API endpoints accesibles sin auth = ALTO
Fase 4: Supabase Deep Audit
Solo ejecutar si se detecto Supabase en Fase 2. Definir variables:
SUPA_URL="https://PROJECT_REF.supabase.co" # o custom domain
ANON_KEY="eyJ..." # del JS
4.1 — Enumeracion de tablas con hints de error:
for tbl in users profiles public_users community_profiles rooms listings bookings reservations payments transactions properties apartments tenants landlords owners contracts messages chats notifications reviews verifications documents invoices blog_posts leads contacts waitlist settings admins; do
result=$(curl -s "${SUPA_URL}/rest/v1/${tbl}?limit=1" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}")
if echo "$result" | grep -q "PGRST"; then
hint=$(echo "$result" | grep -oE '"hint":"[^"]*"' | head -1)
[ -n "$hint" ] && echo " ${tbl}: NO EXISTE ${hint}"
elif echo "$result" | grep -q "permission denied\|row-level security"; then
echo " ${tbl}: PROTEGIDA (RLS activo) ✓"
elif echo "$result" | grep -q "restricted"; then
echo " ${tbl}: PROYECTO SUSPENDIDO/RESTRINGIDO"
elif [ "$result" = "[]" ]; then
echo " ${tbl}: ACCESIBLE (vacia)"
else
echo " ${tbl}: *** ACCESIBLE CON DATOS *** — $(echo "$result" | wc -c) bytes"
fi
done
4.2 — Para cada tabla accesible, contar registros:
curl -s "${SUPA_URL}/rest/v1/TABLE?select=id" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}" \
-H "Prefer: count=exact" -H "Range: 0-0" -I | grep -i content-range
4.3 — Test de escritura (INSERT/UPDATE/DELETE):
# INSERT — usar datos dummy
curl -s -X POST "${SUPA_URL}/rest/v1/TABLE" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}" \
-H "Content-Type: application/json" -H "Prefer: return=minimal" \
-d '{"test_field":"SECURITY_AUDIT_TEST"}'
# Si funciona = CRITICO. Intentar DELETE inmediato para limpiar.
# UPDATE — cambiar campo de un registro conocido
curl -s -X PATCH "${SUPA_URL}/rest/v1/TABLE?id=eq.KNOWN_ID" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}" \
-H "Content-Type: application/json" -d '{"field":"AUDIT_TEST"}'
# Si funciona = CRITICO. REVERTIR inmediatamente.
4.4 — Auth config publica:
curl -s "${SUPA_URL}/auth/v1/settings" -H "apikey: ${ANON_KEY}"
Verificar:
disable_signup: false+ plataforma cerrada = ALTO (signup abierto no intencionado)mailer_autoconfirm: true= ALTO (cuentas sin verificacion email)- OAuth providers inesperados = MEDIO
4.5 — Test de signup abierto:
curl -s -X POST "${SUPA_URL}/auth/v1/signup" \
-H "apikey: ${ANON_KEY}" -H "Content-Type: application/json" \
-d '{"email":"security-audit-test@nonexistent-domain.invalid","password":"AuditTest2026!"}'
Si devuelve user object con UUID = ALTO (cualquiera puede crear cuenta)
4.6 — Storage buckets:
curl -s "${SUPA_URL}/storage/v1/bucket" -H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}"
# Para cada bucket encontrado, intentar listar archivos:
curl -s "${SUPA_URL}/storage/v1/object/list/BUCKET_NAME" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}" \
-H "Content-Type: application/json" -d '{"prefix":"","limit":20}'
4.7 — RPC functions:
for fn in get_users get_all_users admin_get_users export_users get_user_emails list_users get_members get_subscribers get_leaderboard get_user_tier; do
result=$(curl -s -X POST "${SUPA_URL}/rest/v1/rpc/${fn}" \
-H "apikey: ${ANON_KEY}" -H "Authorization: Bearer ${ANON_KEY}" \
-H "Content-Type: application/json" -d '{}')
echo " rpc/${fn}: $(echo "$result" | head -c 150)"
done
Fase 5: Next.js Specific
Solo si se detecto Next.js.
5.1 — Middleware Bypass CVE-2025-29927 (CRITICO, CVSS 9.1):
# Comparar respuesta normal vs con header de bypass
NORMAL=$(curl -s -o /dev/null -w "%{http_code}" "https://TARGET/admin")
BYPASS=$(curl -s -o /dev/null -w "%{http_code}" -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" "https://TARGET/admin")
echo "Normal: $NORMAL | Bypass: $BYPASS"
# Si NORMAL=307/401 pero BYPASS=200 → CRITICO (CVE confirmada)
# Si ambos son 200 → la ruta no tiene middleware (problema diferente)
Probar variantes: middleware, src/middleware, pages/_middleware
5.2 — RSC Data Leakage:
RSC=$(curl -s "https://TARGET" -H "RSC: 1" -H "Next-Router-State-Tree: %5B%22%22%5D")
# Buscar datos sensibles en el payload
echo "$RSC" | grep -oiE '(email|password|secret|token|apikey|phone|dni|iban|credit|ssn)' | sort -u
# Probar en rutas protegidas tambien:
curl -s "https://TARGET/dashboard" -H "RSC: 1" -H "Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22dashboard%22%2C%7B%7D%5D%7D%5D"
5.3 — Server Actions:
curl -s -X POST "https://TARGET" -H "Next-Action: 0000000000" -H "Content-Type: text/plain" -d '[]'
# "Server action not found" = endpoint existe (info disclosure menor)
5.4 — Source Maps:
for chunk in $(curl -s "https://TARGET" | grep -oE '/_next/static/chunks/[^"]+\.js' | sort -u); do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://TARGET${chunk}.map")
[ "$code" = "200" ] && echo " SOURCE MAP EXPUESTO: ${chunk}.map"
done
Source map accesible = MEDIA (expone codigo fuente original)
Fase 6: WordPress Specific
Solo si se detecto WordPress.
# Version de WP
curl -s "https://TARGET" | grep -oE 'content="WordPress [0-9.]+"'
curl -s "https://TARGET/wp-json/" | head -c 200
# xmlrpc activo (bruteforce vector)
curl -s -X POST "https://TARGET/xmlrpc.php" -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'
# Users enumeration
curl -s "https://TARGET/wp-json/wp/v2/users"
curl -s "https://TARGET/?author=1" -o /dev/null -w "%{redirect_url}"
# Login bruteforce protection
curl -s -X POST "https://TARGET/wp-login.php" -d "log=admin&pwd=wrongpass"
Fase 7: CORS
# Reflected origin (CRITICO si refleja con credentials)
curl -sI "https://TARGET" -H "Origin: https://evil-attacker.com" | grep -i "access-control"
# Null origin (exploitable via sandboxed iframe)
curl -sI "https://TARGET" -H "Origin: null" | grep -i "access-control"
# Subdomain wildcard
curl -sI "https://TARGET" -H "Origin: https://anything.TARGET" | grep -i "access-control"
# Prefix/suffix bypass
curl -sI "https://TARGET" -H "Origin: https://TARGET.evil.com" | grep -i "access-control"
Si hay API (Supabase, etc.), probar CORS en esos endpoints tambien:
access-control-allow-origin: *en API = ALTO- Origin reflejado +
Access-Control-Allow-Credentials: true= CRITICO
Fase 8: Auth y Sesiones
# Cookies
curl -sI "https://TARGET" | grep -i "set-cookie"
# Cada cookie debe tener: Secure, HttpOnly, SameSite=Strict|Lax
# Open redirect
for param in redirect url next return_to redirect_uri callback goto dest; do
loc=$(curl -s -o /dev/null -w "%{redirect_url}" "https://TARGET/login?${param}=https://evil.com")
[ -n "$loc" ] && echo "OPEN REDIRECT: ${param} -> ${loc}"
done
# Password reset host injection
curl -s -X POST "https://TARGET/api/auth/reset-password" \
-H "Host: evil.com" -H "Content-Type: application/json" \
-d '{"email":"test@nonexistent.invalid"}'
Fase 9: HTTP Methods y Error Handling
# Metodos HTTP peligrosos
for method in OPTIONS TRACE PUT DELETE PATCH; do
code=$(curl -s -o /dev/null -w "%{http_code}" -X "$method" "https://TARGET/")
echo " ${method}: HTTP ${code}"
done
# TRACE 200 = MEDIO, PUT/DELETE 200 inesperado = ALTO
# Error handling — provocar errores y buscar info leaks
curl -s "https://TARGET/api/users/undefined"
curl -s -X POST "https://TARGET/api/contact" -H "Content-Type: application/json" -d '{"invalid":'
# Stack traces, paths internos, versiones = MEDIO
Fase 10: Rate Limiting
# Test basico — 20 peticiones rapidas
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{http_code} " "https://TARGET/"
done
echo ""
# Si todos 200 = sin rate limiting = ALTO
# Bypass via X-Forwarded-For
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code} " -H "X-Forwarded-For: 10.0.0.${i}" "https://TARGET/api/users"
done
Fase 11: Cloudflare/CDN Bypass
Solo si se detecto Cloudflare u otro CDN.
# Buscar IP real via registros MX
dig MX TARGET +short
# Subdominios no proxied
for sub in mail ftp cpanel direct api staging dev test; do
ip=$(dig +short ${sub}.TARGET 2>/dev/null)
[ -n "$ip" ] && echo " ${sub}.TARGET: ${ip}"
done
# Certificate Transparency logs
curl -s "https://crt.sh/?q=%25.TARGET&output=json" | python3 -c "import sys,json; [print(x['name_value']) for x in json.load(sys.stdin)]" 2>/dev/null | sort -u
Fase 12: Archivos sensibles
cat > /tmp/audit_files.sh << 'SCRIPT'
#!/bin/bash
TARGET=$1
for path in .env .env.local .env.production .env.development .git/config .git/HEAD .DS_Store wp-config.php server-status server-info debug info package.json composer.json Dockerfile docker-compose.yml .dockerignore backup.sql dump.sql database.sql db.sql; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://${TARGET}/${path}")
if [ "$code" != "404" ] && [ "$code" != "000" ]; then
echo " ${path}: HTTP ${code}"
fi
done
SCRIPT
bash /tmp/audit_files.sh "TARGET"
Generacion del informe HTML
Al completar todas las fases, generar un archivo HTML con este formato. El HTML debe usar CSS puro sin JavaScript (compatible WhatsApp/Telegram) y dark theme.
Estructura del informe:
- Header — Fecha, objetivo (URL), gravedad general, nombre del auditor
- Score cards — 4 tarjetas: criticas, altas, medias, OK (con contadores)
- Resumen ejecutivo — 2-3 frases en lenguaje NO tecnico, con fondo rojo degradado
- Analogia — Bloque azul explicando los hallazgos como si la web fuera un edificio, hotel, etc. Adaptar la analogia al tipo de negocio
- Info tecnica — Tabla con stack, dominio, DB, CDN, claves encontradas
- Vulnerabilidades criticas — Cards con borde rojo (#ff4444), cada una con: titulo descriptivo, descripcion en lenguaje sencillo, prueba de concepto en bloque
<pre>, fix recomendado - Vulnerabilidades altas — Cards con borde naranja (#ff8c00), misma estructura
- Vulnerabilidades medias — Cards con borde amarillo (#f5a623)
- Lo que esta bien — Tabla con fondo verde (#00c853) para items que pasan
- Resultados del pentest — Tabla resumen: ataque | resultado | gravedad (badges)
- Comandos de reproduccion — Bloques desplegables
<details><summary> - Plan de accion — Timeline con dots de color: rojo=ahora, naranja=hoy, amarillo=esta semana, gris=antes de lanzar
- Implicaciones legales — RGPD, LOPD, ePrivacy si aplica. Adaptar al pais del target
- Footer — "CONFIDENCIAL", auditor, fecha
Estilos CSS base:
- Fondo:
#0a0a0a, texto:#e0e0e0 - Critico:
#ff4444, Alto:#ff8c00, Medio:#f5a623, OK:#00c853 - Code/pre: font
SF Mono, Fira Code, monospace, fondo#1a1a1a - Max-width: 900px, responsive para movil
- Badges con border-radius 20px y colores segun severidad
Guardar en:
- Si hay proyecto de cliente:
~/Desktop/Empresa/SolutechIA/clientes/[CLIENTE]/ - Si no: directorio actual del usuario
- Nombre:
auditoria-seguridad-[dominio]-[YYYY-MM-DD].html
Tabla de severidades
| Hallazgo | Severidad |
|---|---|
| BBDD abierta sin auth (RLS desactivado) | CRITICA |
| Modificacion/borrado de datos de otros usuarios | CRITICA |
| Contenido de pago accesible publicamente | CRITICA |
| Middleware bypass (CVE-2025-29927) | CRITICA |
| Service role key expuesta en JavaScript | CRITICA |
| SQL injection confirmada | CRITICA |
| .env o .git accesible publicamente | CRITICA |
| Signup abierto no intencionado | ALTA |
| Rutas protegidas accesibles sin auth (/admin, /dashboard) | ALTA |
| Sin Content-Security-Policy | ALTA |
| Sin rate limiting | ALTA |
| CORS reflected origin + credentials | ALTA |
| x-powered-by / Server version expuestos | ALTA |
| Auth config publica (settings endpoint) | ALTA |
| xmlrpc.php activo (WordPress) | ALTA |
| WP users enumeration via REST API | ALTA |
| CORS wildcard sin credentials | MEDIA |
| X-Frame-Options SAMEORIGIN en vez de DENY | MEDIA |
| Tracking (Facebook Pixel, etc.) sin consentimiento cookies | MEDIA |
| Storage buckets publicos con archivos listables | MEDIA |
| Source maps expuestos (.js.map accesible) | MEDIA |
| Error handling revela info interna (stack traces) | MEDIA |
| Sin HSTS | MEDIA |
| Server Action endpoint descubierto | MEDIA |
| PUT/DELETE devuelven 200 en rutas inesperadas | MEDIA |
| Header Server revela version exacta | BAJA |
| Imagenes placeholder de servicios externos | BAJA |
Reglas eticas de la auditoria
Estas reglas existen porque una auditoria de seguridad tiene el poder de causar dano real si se hace de forma irresponsable. Seguirlas protege tanto al auditor como al propietario del sitio.
- Solo prueba de concepto minima — no explotar vulnerabilidades mas alla de confirmar que existen
- Revertir inmediatamente cualquier modificacion hecha durante la auditoria (si un UPDATE funciona, restaurar el valor original)
- No crear cuentas reales — usar emails @nonexistent-domain.invalid
- No exfiltrar ni almacenar datos personales reales de usuarios
- No realizar denial of service ni tests de carga agresivos
- Documentar cada paso para que sea reproducible
- Informar al propietario inmediatamente si se encuentra algo critico
- El informe es CONFIDENCIAL y solo para el propietario autorizado
- Limpiar archivos temporales al finalizar:
rm -rf /tmp/audit_*