| name | tool-web-security-audit |
| description | Auditoría defensiva de seguridad web tipo pentest black-box para webs propias o con autorización explícita. Ejecuta tests OWASP, CVEs, CORS, headers, BBDD, auth, rate limiting e inyección, y genera informe HTML con vulnerabilidades, pruebas de concepto mínimas y plan de acción. Úsala para auditorías pre-lanzamiento, revisión defensiva o validación de una web autorizada antes de desplegar. |
| version | 2.0.0 |
Web Security Audit — Pentest Black-Box Profesional
Aviso de uso responsable
Usa esta skill solo sobre webs propias o con autorización explícita del propietario. Es una herramienta de auditoría defensiva y pre-lanzamiento. Si el usuario no confirma autorización, no ejecutes pruebas, no descargues bundles y no lances comandos contra el objetivo.
Auditoria de seguridad web externa basada en OWASP Top 10 2025, PTES, y experiencia real de pentesting en produccion. 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")
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
echo "$PAGE" | grep -oE 'https?://[^"'\''> ]+\.js' | sort -u
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
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.
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:
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/
grep -rhoiE 'sk_live_[A-Za-z0-9]{20,}' /tmp/audit_js/
grep -rhoiE 'pk_live_[A-Za-z0-9]{20,}' /tmp/audit_js/
grep -rhoiE 'https?://[a-z0-9.-]+\.(supabase\.co|firebaseio\.com|firebaseapp\.com)' /tmp/audit_js/ | sort -u
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'
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"
ANON_KEY="eyJ..."
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):
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"}'
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"}'
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}"
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):
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"
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")
echo "$RSC" | grep -oiE '(email|password|secret|token|apikey|phone|dni|iban|credit|ssn)' | sort -u
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 '[]'
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.
curl -s "https://TARGET" | grep -oE 'content="WordPress [0-9.]+"'
curl -s "https://TARGET/wp-json/" | head -c 200
curl -s -X POST "https://TARGET/xmlrpc.php" -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'
curl -s "https://TARGET/wp-json/wp/v2/users"
curl -s "https://TARGET/?author=1" -o /dev/null -w "%{redirect_url}"
curl -s -X POST "https://TARGET/wp-login.php" -d "log=admin&pwd=wrongpass"
Fase 7: CORS
curl -sI "https://TARGET" -H "Origin: https://evil-attacker.com" | grep -i "access-control"
curl -sI "https://TARGET" -H "Origin: null" | grep -i "access-control"
curl -sI "https://TARGET" -H "Origin: https://anything.TARGET" | grep -i "access-control"
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
curl -sI "https://TARGET" | grep -i "set-cookie"
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
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
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
curl -s "https://TARGET/api/users/undefined"
curl -s -X POST "https://TARGET/api/contact" -H "Content-Type: application/json" -d '{"invalid":'
Fase 10: Rate Limiting
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{http_code} " "https://TARGET/"
done
echo ""
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.
dig MX TARGET +short
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
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'
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:
- Carpeta por defecto:
projects/security-audit/<YYYY-MM-DD>/<dominio>/
- 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_*
Skill original de Angel Aparicio (IA Masters Academy), adaptada para iamasters-os.