mcpField
H
hunt-source-leak

hunt-source-leak

github.com/uphiago安全1.3k42 次安装8 周前更新
代码仓库

关于 hunt-source-leak

用于查找 Web 应用中泄露源代码和构建产物的安全测试技能,覆盖 source map、API 规范、仓库元数据、环境文件及打包的 JavaScript。

主要功能

  1. 01Source map 发现与分析
  2. 02Swagger 和 OpenAPI 文档探测
  3. 03暴露的 .git 与 .env 检查
  4. 04构建产物和调试文件检查
  5. 05原始 JavaScript 包分析

适用场景

  1. 01从 source map 和 JavaScript 包中查找密钥
  2. 02根据 Swagger 或 OpenAPI 文件梳理未公开的 API 路由
  3. 03检查网站是否暴露仓库或环境文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/uphiago/recon-skills --skill hunt-source-leak

安装后在 Claude Code 中输入/hunt-source-leak确认已加载。

附带文件references/react-api-extraction.md

详细介绍

HUNT-SOURCE-LEAK — 源代码与构建产物泄露

核心目标

暴露 TypeScript 源代码的源映射文件 = 查看所有 API 路由、身份验证逻辑和机密。Swagger/OpenAPI JSON = 完整的 API 接口图谱。

最高价值的发现:

  • .js.map 源映射文件 — 还原完整的 TypeScript/ES6 源代码 → 查找硬编码的 API 密钥、内部端点、身份验证逻辑绕过漏洞
  • swagger.json / openapi.json — 完整的 REST API 规范,包含所有端点、参数、身份验证方案和内部路由名称
  • .env / .env.production — 明文存储的 APP_KEY、DB_PASSWORD、API_KEY、SECRET_KEY
  • .git/ 暴露 — git clone 整个源代码历史 → 查找所有过去硬编码的机密
  • asset-manifest.json / _next/static/ — 所有 JS 包路径 → 系统性地发现源映射文件
  • build-info / info.json — git 提交哈希、构建时间戳、依赖版本 → 用于 CVE 定向攻击

注意事项(探测前阅读)

1. 伪源映射文件 — 将 HTML 作为 .map 提供

某些 SPA(运行在 nginx 上的 Angular/React)会对任何未处理的路由提供其 index.html,包括 .js.map URL。URL 以 .map 结尾且返回 HTTP 200,但内容是 HTML,而非 JSON。

处理前先检测:

# First 80 bytes tell the story
head -c 80 /tmp/map_file
# "<!DOCTYPE" or "<html" = faux map (SPA serving HTML)
# "{" or ")))}" = real source map

python3 -c "
with open('/tmp/map_file') as f:
    d = f.read()
if d.strip().startswith('{'): print('Real source map')
elif '<!DOCTYPE' in d or '<html' in d: print('FAUX — SPA serving HTML')
else: print(f'Unknown, first 80b: {repr(d[:80])}')
"

备用方案: 下载原始 JS 包并直接 grep(阶段 8)。

2. BusyBox / Alpine grep -P

在精简容器(Alpine Linux)中,grep -P(Perl 兼容正则表达式)不可用。这会影响阶段 2 步骤 2、阶段 7 和 Swagger 提取。改用 Python3 re:

# Instead of: grep -Eo 'pattern' file
python3 -c "import sys, re; print(*(re.findall(r'pattern', sys.stdin.read())), sep='\n')" < file
3. crt.sh 速率限制

crt.sh API 的速率限制很严格——查询间隔过短会返回空 JSON 或超时。两次查询之间等待 10-15 秒。使用 &limit=20 减少响应大小:https://crt.sh/?q=%25.TARGET&output=json&limit=20


阶段 1 — 快速收获(先运行)

# These 10 requests take <30 seconds and often yield Critical findings
for PATH in \
  "/.env" \
  "/.env.production" \
  "/.env.local" \
  "/.git/HEAD" \
  "/swagger.json" \
  "/api/swagger.json" \
  "/v1/swagger.json" \
  "/openapi.json" \
  "/api/openapi.json" \
  "/api-docs"; do
  STATUS=$(curl --max-time 30 --connect-timeout 10 -s -o /tmp/sl_test -w "%{http_code}" "https://$TARGET$PATH")
  if [ "$STATUS" = "200" ]; then
    echo "[+] HIT: https://$TARGET$PATH"
    head -5 /tmp/sl_test
    echo "---"
  fi
done

阶段 2 — 发现源映射文件

完整的 React SPA 源映射 API 提取流程,请参阅 references/react-api-extraction.md。

# Step 1: Get asset manifest to find all JS bundle paths
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/asset-manifest.json" | python3 -m json.tool 2>/dev/null
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/static/js/main.*.js" 2>/dev/null | head -3

# Next.js
BUILD_ID=$(curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo '"buildId":"\K[^"]+')
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/_next/static/$BUILD_ID/_buildManifest.js" | head -5

# Step 2: For each JS bundle, check for source map reference at end of file
for JS_URL in $(curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo 'src="[^"]*\.js"' | sed 's/src="//;s/"//'); do
  LAST_LINE=$(curl --max-time 30 --connect-timeout 10 -s "https://$TARGET$JS_URL" | tail -1)
  echo "$LAST_LINE" | grep -q "sourceMappingURL" && echo "[+] Source map: $JS_URL"
done

# Step 3: Download and reconstruct source from .map files
JS_URL="https://$TARGET/static/js/main.abc123.js"
MAP_URL="${JS_URL}.map"
curl --max-time 30 --connect-timeout 10 -s "$MAP_URL" | python3 -c "
import sys, json, os
data = json.load(sys.stdin)
sources = data.get('sources', [])
contents = data.get('sourcesContent', [])
for i, (src, content) in enumerate(zip(sources, contents)):
    if content:
        path = '/tmp/sourcemap_extract/' + src.replace('../','').replace('./',''). replace('webpack://','')
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, 'w') as f:
            f.write(content)
        print(f'[+] Extracted: {src}')
"

# Step 4: Grep extracted source for secrets
grep -r "API_KEY\|SECRET\|PASSWORD\|TOKEN\|PRIVATE" /tmp/sourcemap_extract/ 2>/dev/null
grep -r "process\.env\." /tmp/sourcemap_extract/ 2>/dev/null | grep -v "NEXT_PUBLIC_" | head -20
grep -r "http://internal\|localhost\|127\.0\.0\.1\|10\.\|172\.\|192\.168" /tmp/sourcemap_extract/ 2>/dev/null | head -20

阶段 3 — 发现 Swagger / OpenAPI

# Common paths
SWAGGER_PATHS=(
  "/swagger.json" "/swagger.yaml" "/swagger/"
  "/api/swagger.json" "/api/swagger.yaml"
  "/v1/swagger.json" "/v2/swagger.json" "/v3/swagger.json"
  "/openapi.json" "/openapi.yaml"
  "/api/openapi.json" "/api-docs" "/api-docs.json"
  "/api/v1/swagger.json" "/api/v2/swagger.json"
  "/rest/swagger.json" "/rest/api-docs"
  "/.well-known/openapi.json"
  "/graphql/schema.json"
)

for PATH in "${SWAGGER_PATHS[@]}"; do
  STATUS=$(curl --max-time 30 --connect-timeout 10 -s -o /tmp/swagger_test -w "%{http_code}" "https://$TARGET$PATH")
  if [ "$STATUS" = "200" ]; then
    echo "[+] Found: https://$TARGET$PATH"
    # Extract all API paths from swagger
    python3 -c "
import sys, json
try:
    d = json.load(open('/tmp/swagger_test'))
    paths = list(d.get('paths', {}).keys())
    print(f'Endpoints: {len(paths)}')
    print('\n'.join(sorted(paths)))
except: pass
" | head -50
  fi
done

阶段 4 — .git 暴露

# Check if .git directory is accessible
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/.git/HEAD" | grep -q "ref:" && echo "[+] .git exposed!"

# If exposed, reconstruct repo
# Tool: git-dumper
pip3 install git-dumper
git-dumper "https://$TARGET/.git/" /tmp/dumped-repo/

# Grep for secrets in all git history
cd /tmp/dumped-repo && \
  git log --all --oneline 2>/dev/null | head -20
  git grep -i "password\|secret\|api_key\|token" $(git rev-list --all) 2>/dev/null | head -30

# trufflehog on git history
trufflehog git file:///tmp/dumped-repo/ 2>/dev/null | head -50

阶段 5 — 遗忘的文件与调试端点

# Build artifacts and debug files
DEBUG_PATHS=(
  "/build-info.json" "/build/build-info.json"
  "/info" "/actuator/info" "/api/info"
  "/version" "/api/version" "/_version"
  "/health" "/status" "/ping"
  "/robots.txt" "/security.txt" "/.well-known/security.txt"
  "/sitemap.xml" "/manifest.json" "/browserconfig.xml"
  "/crossdomain.xml" "/clientaccesspolicy.xml"
  "/phpinfo.php" "/info.php" "/test.php"
  "/server-status" "/server-info" "/.htaccess"
  "/web.config" "/applicationHost.config"
  "/WEB-INF/web.xml" "/META-INF/MANIFEST.MF"
  "/package.json" "/composer.json" "/Gemfile"
  "/Dockerfile" "/docker-compose.yml" "/.dockerenv"
)

for PATH in "${DEBUG_PATHS[@]}"; do
  STATUS=$(curl --max-time 30 --connect-timeout 10 -s -o /tmp/debug_test -w "%{http_code}" "https://$TARGET$PATH")
  if [ "$STATUS" = "200" ]; then
    echo "[+] Found: https://$TARGET$PATH ($STATUS, $(wc -c < /tmp/debug_test) bytes)"
    head -3 /tmp/debug_test
    echo "---"
  fi
done

阶段 6 — .DS_Store 文件列表

# .DS_Store files on macOS-deployed web servers reveal directory structure
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/.DS_Store" | xxd | head -10

# Parse .DS_Store to extract filenames
pip3 install ds_store
python3 -c "
from ds_store import DSStore
with DSStore.open('/tmp/ds_store_test', 'r') as d:
    for entry in d:
        print(entry.filename)
"

# Recursive .DS_Store enumeration
# Tool: https://github.com/lijiejie/ds_store_exp
python3 ds_store_exp.py "https://$TARGET/"

阶段 7 — webpack 分块分析

⚠ BusyBox/Alpine:跳过下面的 grep -Eo——改用 Python3 re(参阅“注意事项”部分)。

# Download and analyze webpack chunks for hardcoded values
# Find chunk files
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ | grep -Eo '"[^"]*\.chunk\.js"' | tr -d '"' | while read chunk; do
  echo "Analyzing: $chunk"
  curl --max-time 30 --connect-timeout 10 -s "https://$TARGET$chunk" | \
    grep -oE '"(api_key|apiKey|secret|password|token|key)"\s*:\s*"[^"]+"' | head -5
done

# Also grep for internal hostnames
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/static/js/main.*.js" | \
  grep -oE '"(https?://[^"]*internal[^"]*|http://[^"]*localhost[^"]*)"' | sort -u

# Check for Base64-encoded secrets
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/static/js/main.*.js" | \
  grep -Eo '"[A-Za-z0-9+/]{30,}={0,2}"' | while read b64; do
  DECODED=$(echo "$b64" | tr -d '"' | base64 -d 2>/dev/null)
  echo "$DECODED" | grep -iE "key|secret|password|token" && echo "  B64: $b64"
done

阶段 8 — 原始 JS 包备用方案(源映射失败时)

如果源映射 URL 返回 HTML(伪源映射文件)或 404,不要放弃分析——下载原始 JS 包并直接 grep。虽然比还原源映射慢,但仍能发现内部 API URL、硬编码凭据和平台内部信息。

8.1 下载所有 JS 包
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/" -o /tmp/homepage.html

# Extract ALL JS URLs (both absolute and relative)
python3 -c "
import sys, re
html = open('/tmp/homepage.html').read()
seen = set()
for m in re.finditer(r'src=\"([^\"]+\.js[^\"]*)\"', html):
    j = m.group(1)
    if j not in seen: seen.add(j); print(j)
for m in re.finditer(r'\"([^\"]+\.js[^\"]*)\"', html):
    j = m.group(1)
    if j not in seen and '.js' in j and 'node_module' not in j:
        seen.add(j); print(j)
" | while read js_url; do
  full=$(echo "$js_url" | grep -q "^http" && echo "$js_url" || echo "https://$TARGET$js_url")
  curl --max-time 30 --connect-timeout 10 -sk "$full" -o "/tmp/js_$(basename $js_url | cut -d? -f1)" 2>/dev/null
  sleep 1
done
8.2 搜索机密与内部端点
python3 -c "
import re, os

d = '/tmp'
for f in os.listdir(d):
    if not f.startswith('js_'): continue
    data = open(os.path.join(d, f)).read()

    # Firebase API keys
    for m in re.findall(r'AIza[0-9A-Za-z_-]{35}', data):
        print(f'[$f] FIREBASE_KEY: {m}')

    # AWS access keys
    for m in re.findall(r'AKIA[0-9A-Z]{16}', data):
        print(f'[$f] AWS_KEY: {m}')

    # Stripe keys    for m in re.findall(r's[kr]_(live|test)_[A-Za-z0-9]+', data):
        print(f'[$f] STRIPE_KEY: {m}')

    # Internal URLs with custom ports — HIGH VALUE
    for m in re.findall(r'[\"'"'"'](https?://[^\"'"'"'\\\\\)\s,;:]+:\d{3,5}[^\"'"'"'\\\\\)\s,;]*)', data):
        print(f'[$f] INTERNAL_URL: {m}')

    # Non-public env var references    for m in re.findall(r'process\.env\.(?!NEXT_PUBLIC_)([A-Z_]+)', data):
        print(f'[$f] ENV_REF: process.env.{m}')
"
8.3 针对框架的检查

Angular(真实案例:本次会话中的 [STAGING_ENV]):

  • 查找 environment 或 environments 变量
  • grep .com:PORT 模式——可发现内部 API 网关
  • 检查 manifest.json 中的所有 JS 包路径

React/Next.js:

  • 在 HTML 中搜索 __NEXT_DATA__(嵌入式状态/属性)
  • 检查 _next/static/chunks/pages/ 中的页面级 JS

Vue.js:

  • 在 HTML 中搜索 __NUXT__ 状态
  • 检查 /js/app.*.js 中打包的 API 调用
8.4 真实案例

在本次会话对 MedxGo 的侦察中,针对 [STAGING_ENV] 的源映射返回了 HTML(伪源映射文件),因此分析了原始 JS:

# 3.8MB Angular main.js — one grep line found the jackpot:
python3 -c "
import re
data = open('/tmp/js_main.js').read()
for m in re.findall(r'[\"'"'"'](https?://[^\"'"'"'\\\\\)\s,;:]+:\d+)[\"'"'"']', data):
    print(m)
"
# → https://mean.[STAGING_ENV]:446

这是一个未公开的内部 MEAN 栈 API 服务器。一行 grep 结果就发现了一个全新的攻击面。


链式利用表

源代码泄露发现后续利用影响
包含 API 密钥的源映射直接使用密钥 → 访问 API高危/严重
包含身份验证逻辑的源映射查找身份验证绕过路由严重
Swagger → 内部端点测试未公开的管理路由高危
暴露 .git完整源代码历史 → 所有过往密钥严重
包含 git 哈希的 build-info针对确切版本的 CVE高危
包含 DB_PASSWORD 的 .env直接访问数据库严重

工具

# git-dumper (reconstruct exposed .git)
pip3 install git-dumper
git-dumper "https://target.com/.git/" /tmp/repo/

# sourcemap-explorer (visualize what's in bundles)
npm install -g source-map-explorer
source-map-explorer main.js

# unwebpack-sourcemap (extract all source files)
npm install -g unwebpack-sourcemap

# trufflehog (secret scanning)
trufflehog filesystem /tmp/repo/

验证

✅ 源映射:还原的 TypeScript 源代码包含 API 端点或硬编码密钥 ✅ Swagger:JSON 包含 UI 中不可见的内部端点 ✅ .git 暴露:git-dumper 成功克隆仓库,历史记录中包含密钥 ✅ .env 暴露:DATABASE_URL、API_KEY、SECRET_KEY 以明文形式可见

严重性:

  • 包含凭据的 .env:严重
  • 历史记录中包含密钥的 .git:严重
  • 包含密钥的源映射:高危
  • 包含内部路由的 Swagger:中高危
  • 仅有 robots.txt:信息提示

验证

  1. 技能完整性 — 确认技能文件格式正确: 失败 失败 所有测试均用于验证技能结构是否正确。

相关技能

  • hunt-wordpress — wp-config.php、debug.log、wp-content/uploads 暴露
  • hunt-firebase — 在 JS 包中发现 Firebase API 密钥
  • hunt-supabase — 在 JS 和 .env 中发现 Supabase URL/密钥
  • hunt-laravel — .env、storage/logs/laravel.log、APP_KEY 暴露
  • hunt-nextjs — _next/static、buildId、源映射分析
  • security-arsenal — API 密钥、JWT、云凭据的正则表达式模式
  • offensive-osint — GitHub 搜索、暴露的仓库、Shodan/Censys
  • cors-chain-automation — 对源映射中的暴露 API 端点进行 CORS 测试

评论

支持 Markdown