SuperAPI AI Directory

web-security-audit

Security & auditing

View repository

Updated 10-15 19:56

About this skill

Web Security Audit — Professional Black-Box Pentest

External web security audit based on OWASP Top 10 2025, PTES, and real production pentesting experience (Frontera, Tripath). Covers Next.js, React, Vue, WordPress, Django, Rails, Laravel, and any modern web stack. Generates a professional HTML report in Spanish.

Context and why it matters

Modern websites have enormous attack surfaces: open APIs, databases with misconfigured RLS, missing security headers, and CVEs in popular frameworks. An attacker with basic knowledge and curl can exploit these vulnerabilities in minutes. This skill systematizes what a professional pentester would do, in a reproducible and documented format.

Execution flow

Phase 0: Authorization (mandatory)

Before any test, confirm with the user:

  1. You have the owner's authorization to audit the URL
  2. Restrictions (do not create accounts, do not touch production, etc.)
  3. Report language (default: Spanish)

If there is no explicit authorization, DO NOT proceed.

Phase 1: Reconnaissance

Run the 4 sub-phases in parallel with Bash:

1A — Security headers:

curl -sI "https://TARGET"

Headers checklist (each missing one is a finding):

Header Expected Severity if missing
Strict-Transport-Security max-age>=31536000; includeSubDomains Medium
Content-Security-Policy Restrictive policy, without unsafe-inline High
X-Frame-Options DENY (SAMEORIGIN = medium) Medium
X-Content-Type-Options nosniff Low
Referrer-Policy strict-origin-when-cross-origin or more restrictive Low
Permissions-Policy Block camera, microphone, geolocation Low
X-Powered-By It MUST NOT be present High (info disclosure)
Server It MUST NOT reveal the exact version Low

1B — Stack detection:

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 and infrastructure:

dig TARGET A +short
dig TARGET MX +short
dig TARGET TXT +short
dig TARGET NS +short
# Detectar CDN (Cloudflare, Vercel, etc.)

1D — robots.txt and sitemap:

curl -s "https://TARGET/robots.txt"
curl -s "https://TARGET/sitemap.xml" | head -50

Phase 2: Secret search in JavaScript

Client-side JS bundles often contain API keys, database URLs, and tokens. It is the first thing an attacker looks at.

# 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

Patterns to search for in the downloaded files:

# 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

If a Supabase JWT is found, decode it to obtain the 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))"

Phase 3: Route enumeration

Use an external script to avoid shell escaping issues:

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"

Classify results:

  • Routes that should require auth and return 200 = HIGH
  • Accessible admin routes = CRITICAL
  • .env, .git accessible = CRITICAL
  • API endpoints accessible without auth = HIGH

Phase 4: Supabase Deep Audit

Only run if Supabase was detected in Phase 2. Define variables:

SUPA_URL="https://PROJECT_REF.supabase.co"  # o custom domain
ANON_KEY="eyJ..."  # del JS

4.1 — Table enumeration with error hints:

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 — For each accessible table, count records:

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 — Write test (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 — Public auth config:

curl -s "${SUPA_URL}/auth/v1/settings" -H "apikey: ${ANON_KEY}"

Verify:

  • disable_signup: false + closed platform = HIGH (unintended open signup)
  • mailer_autoconfirm: true = HIGH (accounts without email verification)
  • Unexpected OAuth providers = MEDIUM

4.5 — Open signup test:

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!"}'

If it returns a user object with UUID = HIGH (anyone can create an account)

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

Phase 5: Next.js Specific

Only if Next.js was detected.

5.1 — Middleware Bypass CVE-2025-29927 (CRITICAL, 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)

Test variants: 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 accessible = MEDIUM (exposes original source code)

Phase 6: WordPress Specific

Only if WordPress was detected.

# 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"

Phase 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"

If there is an API (Supabase, etc.), test CORS on those endpoints as well:

  • access-control-allow-origin: * on API = HIGH
  • Origin reflected + Access-Control-Allow-Credentials: true = CRITICAL

Phase 8: Auth and Sessions

# 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"}'

Phase 9: HTTP Methods and 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

Phase 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

Phase 11: Cloudflare/CDN Bypass

Only if Cloudflare or another CDN was detected.

# 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

Phase 12: Sensitive files

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"

HTML report generation

After completing all phases, generate an HTML file with this format. The HTML must use pure CSS without JavaScript (WhatsApp/Telegram compatible) and a dark theme.

Report structure:

  1. Header — Date, target (URL), overall severity, auditor name
  2. Score cards — 4 cards: critical, high, medium, OK (with counters)
  3. Executive summary — 2-3 sentences in NON-technical language, with a red gradient background
  4. Analogy — Blue block explaining the findings as if the website were a building, hotel, etc. Adapt the analogy to the type of business
  5. Technical info — Table with stack, domain, DB, CDN, keys found
  6. Critical vulnerabilities — Cards with red border (#ff4444), each with: descriptive title, description in plain language, proof of concept in block <pre>, recommended fix
  7. High vulnerabilities — Cards with orange border (#ff8c00), same structure
  8. Medium vulnerabilities — Cards with yellow border (#f5a623)
  9. What is good — Table with green background (#00c853) for passing items
  10. Pentest results — Summary table: attack | result | severity (badges)
  11. Reproduction commands — Collapsible blocks <details><summary>
  12. Action plan — Timeline with colored dots: red=now, orange=today, yellow=this week, gray=before launch
  13. Legal implications — GDPR, LOPD, ePrivacy if applicable. Adapt to the target's country
  14. Footer — "CONFIDENTIAL", auditor, date

Base CSS styles:

  • Background: #0a0a0a, text: #e0e0e0
  • Critical: #ff4444, High: #ff8c00, Medium: #f5a623, OK: #00c853
  • Code/pre: font SF Mono, Fira Code, monospace, background #1a1a1a
  • Max-width: 900px, responsive for mobile
  • Badges with border-radius 20px and colors according to severity

Save to:

  • If there is a client project: ~/Desktop/Empresa/SolutechIA/clientes/[CLIENTE]/
  • If not: user's current directory
  • Name: auditoria-seguridad-[dominio]-[YYYY-MM-DD].html

Severity table

Finding Severity
Open DB without auth (RLS disabled) CRITICAL
Modification/deletion of other users' data CRITICAL
Paid content publicly accessible CRITICAL
Middleware bypass (CVE-2025-29927) CRITICAL
Service role key exposed in JavaScript CRITICAL
SQL injection confirmed CRITICAL
.env or .git publicly accessible CRITICAL
Unintended open signup HIGH
Protected routes accessible without auth (/admin, /dashboard) HIGH
No Content-Security-Policy HIGH
No rate limiting HIGH
CORS reflected origin + credentials HIGH
x-powered-by / Server version exposed HIGH
Public auth config (settings endpoint) HIGH
xmlrpc.php active (WordPress) HIGH
WP users enumeration via REST API HIGH
CORS wildcard without credentials MEDIUM
X-Frame-Options SAMEORIGIN instead of DENY MEDIUM
Tracking (Facebook Pixel, etc.) without cookie consent MEDIUM
Public storage buckets with listable files MEDIUM
Exposed source maps (.js.map accessible) MEDIUM
Error handling reveals internal info (stack traces) MEDIUM
No HSTS MEDIUM
Server Action endpoint discovered MEDIUM
PUT/DELETE return 200 on unexpected routes MEDIUM
Server header reveals exact version LOW
Placeholder images from external services LOW

Ethical rules of the audit

These rules exist because a security audit has the power to cause real harm if done irresponsibly. Following them protects both the auditor and the site owner.

  1. Only minimal proof of concept — do not exploit vulnerabilities beyond confirming they exist
  2. Immediately revert any modification made during the audit (if an UPDATE works, restore the original value)
  3. Do not create real accounts — use @nonexistent-domain.invalid emails
  4. Do not exfiltrate or store real personal data of users
  5. Do not perform denial of service or aggressive load tests
  6. Document each step so that it is reproducible
  7. Notify the owner immediately if something critical is found
  8. The report is CONFIDENTIAL and only for the authorized owner
  9. Clean up temporary files at the end: rm -rf /tmp/audit_*