js-secrets-extraction について
js-secrets-extractionは、JavaScriptバンドルやソースマップから露出したシークレット、エンドポイント、ルート、ソースコードを調べるWebセキュリティテスト用スキルです。許可された評価での取得、検索、再構成、検証の手順を示します。
主な機能
- 01JavaScriptバンドルの取得とスキャン
- 02ソースマップの発見と再構成
- 03シークレットと認証情報のパターン検索
- 04API、バックエンドURL、ルートの抽出
- 05ポートやアプリのチャンクを横断した分析
- 06検出内容の検証ガイダンス
活用シーン
- 01Web列挙時のSPAバンドル検査
- 02ソースマップから元コードやシークレットを確認
- 03管理アプリからバックエンドURLやAPIパスを発見
- 04報告前に疑わしいキーを検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill js-secrets-extractionインストール後、Claude Code で/js-secrets-extractionを入力して読み込まれたか確認します。
同梱ファイルscripts/scan_js.py
詳細
JS バンドルとソースマップの分析 — シークレットの抽出
使用するタイミング
- Web の初期調査後に必ず実施します
- モダンな SPA(React、Angular、Vue)が見つかった場合
- 対象が Firebase、Supabase、Auth0 を使用している場合
- 多くの対象では、ディレクトリスキャンよりも高い成果が得られます
JS バンドルを分析する理由
モダンな JavaScript バンドル(Webpack、Vite、esbuild)には、次の情報が含まれていることがよくあります。
- ハードコードされた API キーとトークン
- 内部 API URL
- Firebase、Auth0、Supabase の設定
- 環境変数(VITE_、REACT_APP_、NEXT_PUBLIC_*)
- 内部ルート
バンドルのダウンロードと分析
curl --max-time 30 --connect-timeout 10 -s "https://target.com" > index.html
grep -Eo 'src="[^"]*\.js"' index.html | cut -d'"' -f2 | while read js; do
curl --max-time 30 --connect-timeout 10 -s "https://target.com$js" > "$(basename $js)"
done
# Search for secrets in bundles
grep -rEn "(apiKey|api_key|API_KEY|token|secret|password|clientId|client_id|auth0|firebase|supabase)[\"'\"]?[[:space:]]*[:=][[:space:]]*[\"'\'][^\"'\']{8,}" *.js
ソースマップの再構築
curl --max-time 30 --connect-timeout 10 -sI "https://target.com/assets/index-abc123.js.map"
curl --max-time 30 --connect-timeout 10 -sI "https://target.com/static/js/main.12345.js.map"
# If HTTP 200, use for reconstruction:
# https://unminify.com
# https://source-map-visualization.netlify.app
実際の事例:企業向け Angular SPA の管理画面では、2 つの JS バンドル(各 250KB)から次の情報が見つかりました。
- 内部 API URL(apiv3.empresa.com.br)
- Firebase API キー(AIzaSy...2GXA)
- 暗号化キー(AD5oDjsJaTJOzLe1Llj9mz)
- Cloudinary のアップロードエンドポイント
ポートごとの URL 分析
モダンなデプロイ環境では、メインの SPA をポート 443 で、管理画面や API を別のポート(8080、8081、8084)で提供することがよくあります。発見したすべてのポートで、必ず JS バンドルを確認してください。
# Check source maps on every open port
for port in 443 8080 8081 8084; do
curl --max-time 30 --connect-timeout 10 -sI "https://target.com:$port/static/js/main.*.js.map" 2>/dev/null
curl --max-time 30 --connect-timeout 10 -sI "https://target.com:$port/assets/index-*.js.map" 2>/dev/null
done
管理画面や代替ポート上のアプリケーションのソースマップには、公開 SPA とは異なるルートや設定一式が含まれている場合があります。許可された各アプリケーションを個別に分析してください。
管理ポータルの JS 分析パターン
別のポートで管理ポータルを見つけた場合、JS バンドルにはメインサイトとは異なるシークレットが含まれていることがよくあります。
base = "https://target.com:8080" # Admin portal
js = requests.get(f"{base}/static/js/main.*.js").text
# 1. Extract ALL API URLs
api_urls = re.findall(r'https?://[^\"\'[[:space:]]\\n,)>\\]]+', js)
# 2. Find base API URL (the backend this admin talks to)
# 3. Look for hardcoded credentials, API keys, auth patterns
# 4. Extract route paths for the admin app
routes = re.findall(r'[\"\'](/[a-zA-Z0-9_/.-]*(?:admin|chat|bot|message|user|auth|login|token|config|setting|dashboard|hospital|pharmacy|drug|payment)[a-zA-Z0-9_/.-]*)[\"\']', js, re.IGNORECASE)
ソースマップの内容分析(1,200 ファイル以上)
ソースマップが利用可能な場合は、sourcesContent 配列を分析してハードコードされたシークレットを探してください。
import json, re
data = json.loads(open("bundle.js.map").read())
all_source = " ".join(data.get("sourcesContent", []))
# Search for credentials in the original source
patterns = {
"password": r'[\"\']([^\"\']*(?:password|passwd|pwd)[^\"\']*)[\"\']\s*[:=]\s*[\"\']([^\"\']+)[\"\']',
"token": r'[\"\']([^\"\']*(?:token|jwt|api_key|apikey|secret)[^\"\']*)[\"\']\s*[:=]\s*[\"\']([^\"\']+)[\"\']',
}
for name, pat in patterns.items():
matches = re.findall(pat, all_source, re.IGNORECASE)
if matches:
print(f"[{name}] {matches[:5]}")
- Cloudinary のアップロードエンドポイント
シークレット用正規表現パターン集
import re
patterns = {
"Firebase API Key": r'apiKey:\s*[\"\']([^\"\']{30,})',
"AWS Key": r'(?:AKIA|ASIA)[A-Z0-9]{16}',
"Google API Key": r'AIza[0-9A-Za-z\\-_]{35}',
"JWT": r'eyJ[A-Za-z0-9_\\-]{20,}\.[A-Za-z0-9_\\-]{20,}\.[A-Za-z0-9_\\-]{10,}',
"Mercado Pago": r'APP_USR-[a-f0-9]{8,}',
"Stripe": r'(?:sk_live|pk_live)_[A-Za-z0-9]{24,}',
"Auth0 Domain": r'(?:domain|auth0_domain):\s*[\"\']([^\"\']+\.auth0\.com)',
"Auth0 Client ID": r'(?:client_id|clientId|AUTH0_CLIENT_ID):\s*[\"\']([^\"\']{20,})',
"Supabase URL": r'(?:supabaseUrl|SUPABASE_URL):\s*[\"\'](https://[^\"\']+\.supabase\.co)',
"Supabase Key": r'(?:supabaseKey|anonKey|SUPABASE_ANON_KEY):\s*[\"\'](eyJ[A-Za-z0-9_\\-]+\.[A-Za-z0-9_\\-]+\.[A-Za-z0-9_\\-]+)',
"Heroku": r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}',
"Generic Secret": r'(?:secret|password|token|key):\s*[\"\']([^\"\']{8,})',
}
バンドルの一括ダウンロードと grep
import requests, re, json
base = "https://target.com"
html = requests.get(base).text
# Extract all JS URLs
js_urls = re.findall(r'src="([^"]*\.js)"', html)
for js_url in js_urls:
if js_url.startswith("/"):
js_url = base + js_url
content = requests.get(js_url).text
for name, pattern in patterns.items():
matches = re.findall(pattern, content)
for m in matches:
if isinstance(m, tuple):
m = m[0]
if len(m) > 6:
print(f"[{name}] {m[:80]}")
注意点
| 問題 | 解決策 |
|---|---|
| バンドルが大きすぎる | 特定のパターンを指定して grep -Eo を使用します |
| コードが最小化されている(1 文字の名前) | 再構築にはソースマップを使用します |
| 誤検出 | API エンドポイントをテストしてキーを検証します |
| レート制限 | バンドルのダウンロード間に遅延を入れます |
バックエンド URL の発見
JS バンドルから本番環境のバックエンド URL が漏れることがよくあり、CDN/WAF を迂回した直接の API 攻撃が可能になります。
# Platform-specific backend URL patterns
grep -Eo 'https?://[a-zA-Z0-9.\-]+\.(fly\.dev|azurewebsites\.net|onrender\.com|vercel\.app|netlify\.app)[^"'\'' ]{0,40}' /tmp/*.js
grep -Eo 'https?://[a-zA-Z0-9.\-]+\.(supabase\.co|r2\.dev|blob\.vercel-storage\.com)[^"'\'' ]{0,40}' /tmp/*.js
# Edge function URLs
grep -Eo 'functions/v1/[a-zA-Z0-9_\-]+' /tmp/*.js
# Internal API paths
grep -Eo '["\x60]/api/v1/[a-zA-Z0-9_\-/]+["\x60]' /tmp/*.js
実際の現場で見られるパターン
| パターン | プラットフォーム | 例 | シークレット? |
|---|---|---|---|
*.fly.dev | Fly.io | ht-prod-backend.fly.dev | ✅ バックエンド URL |
*.azurewebsites.net | Azure | consigpro-api-prod-... | ✅ バックエンド URL |
*.onrender.com | Render | clickcity-api.onrender.com | ✅ バックエンド URL |
*.supabase.co | Supabase | jxhvjufqtabpeieyhkgk.supabase.co | ✅ Anon キーは公開情報。バックエンド URL は有用な情報です |
*.r2.dev | Cloudflare R2 | pub-xxx.r2.dev | ✅ ストレージ URL |
functions/v1/* | Supabase Edge | provision-openrouter-key | ✅ エンドポイント名 |
dpl_* | Vercel DPL | dpl_BCoyPsxxYLZ... | ❌ シークレットではありません — 公開デプロイ ID |
検証
# Test Firebase API key
curl --max-time 30 --connect-timeout 10 -s "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIza..."
# Test Supabase anon key
curl --max-time 30 --connect-timeout 10 -s "https://PROJECT.supabase.co/rest/v1/users?limit=1" -H "apikey: ANON_KEY" -H "Authorization: Bearer ANON_KEY"
フェーズ 5 — ソースマップの悪用
.js.map ファイルが本番環境に残されている場合、コンパイル前のソースコード全体を復元します。
# Find .map files via Wayback Machine
curl --max-time 30 --connect-timeout 10 -s "https://web.archive.org/cdx/search/cdx?url=*.target.com/*&collapse=urlkey&output=text&fl=original&filter=original:.*\.js\.map$" \
| sort -u > map_urls.txt
# Download and extract source
wget https://target.com/static/app.js.map
node -e "
const map = require('./app.js.map');
map.sources.forEach((src, i) => {
const fs = require('fs');
fs.writeFileSync(src.split('/').pop(), map.sourcesContent[i]);
});
print('Extracted ' + map.sources.length + ' source files');
"
# Quick check: does a JS file have an available map?
curl --max-time 30 --connect-timeout 10 -skI "https://target.com/static/app.js.map" | grep "200\|Content-Type"
フェーズ 6 — JS の詳細なクローリング
JS ファイルを再帰的にクロールし、埋め込まれた URL、API、IP アドレスを探します。
# lazyegg — crawls JS files for links, APIs, IPs
python3 lazyegg.py https://target.com
python3 lazyegg.py https://target.com/js/auth.js
# Combine with waybackurls for deep coverage
waybackurls target.com \
| grep '\.js$' \
| awk -F '?' '{print $1}' \
| sort -u \
| xargs -I{} bash -c 'python3 lazyegg.py "{}" --js_urls --domains --ips' \
> lazyegg_output.txt
# subjs — extract JS URLs from any URL list
cat all_urls.txt | subjs | tee js_files_full.txt
フェーズ 7 — ファイルごとの AI 支援コードレビュー
JS バンドルは、最小化されていてもソースコードです。ファイル(チャンク)ごとに規律あるレビューを行うことで、自律型エージェントが見逃すものを発見できます。
# 1. Download all JS chunks
curl --max-time 30 --connect-timeout 10 -sk "https://target.com" | grep -Eo 'src="[^"]+\.js[^"]*"' | \
cut -d'"' -f2 | while read js; do
curl --max-time 30 --connect-timeout 10 -sk "$js" -o "chunks/$(basename $js)"
done
# 2. Per-chunk pattern review for dangerous sinks
for chunk in chunks/*.js; do
echo "=== $chunk ==="
# eval / new Function (arbitrary code execution)
grep -Eon 'eval\s*\(|new\s+Function\s*\(' "$chunk"
# Hardcoded API keys/secrets
grep -Eon '(?:api[_-]?key|secret|token|password|bearer)\s*[:=]\s*["\x27][^"\x27]{8,}' "$chunk"
# postMessage without origin check
grep -Eon 'postMessage\s*\(' "$chunk"
# Prototype pollution patterns
grep -Eon '__proto__|constructor\.prototype' "$chunk"
# Debug/test code in production
grep -Eoin 'debug|test|staging|localhost' "$chunk"
# Client-trusted flags
grep -Eon '(?:isAdmin|isVip|isPremium|isModerator|role)\s*[=:]\s*true' "$chunk"
done > ai_review_findings.txt
# 3. Review findings — each is a CANDIDATE, not confirmed
grep -c "===" ai_review_findings.txt # files reviewed
grep -c ":" ai_review_findings.txt # candidate findings
重要なポイント:コードベース全体から「バグを見つける」よう自律型エージェントに指示すると、予算を消費する一方で、問題を見逃します。出力形式を固定したうえで、ファイルごとに漏れなく確認することで、再現可能な検出ができます。検出結果はそれぞれ、手動で PoC を検証する必要があります。