mcpField
S
staging-subdomain-hunt

staging-subdomain-hunt

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

关于 staging-subdomain-hunt

用于授权渗透测试的子域名发现技能,通过证书透明度记录、DNS 暴力枚举和 Web 探测寻找暂存、开发及内部主机。还提供与生产环境对比安全控制及核实发现的步骤。

主要功能

  1. 01证书透明度枚举
  2. 02DNS 子域名枚举
  3. 03存活主机探测
  4. 04暂存环境检查
  5. 05生产与暂存对比
  6. 06发现结果验证

适用场景

  1. 01为已加固的生产站点寻找其他测试入口
  2. 02检查暂存主机是否暴露调试页面或内部 API
  3. 03比较生产与暂存环境的安全控制

安装

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

$npx skills add https://github.com/uphiago/recon-skills --skill staging-subdomain-hunt

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

详细介绍

预发布环境与子域名猎查技能

通过证书透明度(crt.sh)、DNS 暴力枚举和 Web 探测来发现预发布、开发和内部子域名。利用预发布环境中的安全缺口——与生产环境相比,预发布环境的安全性通常较弱(没有 WAF、启用了调试功能、安装页面可访问)。已在 7 个美国目标上验证:预发布子域名暴露了生产环境中不可见的 phpinfo、WordPress 安装页面和内部 API。

适用场景

  • 在高价值目标上运行 deep-invade 第 5 阶段。
  • 生产目标防护完善(有 WAF、没有信息泄露)——转而探测预发布环境。
  • 目标的攻击面较大(电子商务、SaaS、加盟模式)。
  • 主站已加固,需要寻找其他入口点。
  • subdomain-enumeration 生成子域名列表后。

前提条件

  • 配备 curl、httpx、jq 的 terminal。
  • 目标域名(例如 example.com)。
  • DNS 暴力枚举所需的字典:./tools/subdomains.txt。

运行方法

DOMAIN="example.com"

# crt.sh discovery
curl --max-time 30 --connect-timeout 10 -sk "https://crt.sh/?q=%25.$DOMAIN&output=json" | jq -r '.[].name_value' | sed 's/\*\.//g' | sort -u > subs.txt

# Probe for live hosts
httpx -silent -l subs.txt -threads 50 -status-code -tech-detect -o alive_subs.txt

# Check for staging indicators
grep -iE 'staging|stage|dev|test|uat|beta' alive_subs.txt

快速参考

指标含义操作
/wp-admin/install.php 返回 200全新的 WordPress——尚未配置站点接管安装
/wp-admin/upgrade.php 返回 200WP 需要升级数据库数据库信息泄露
预发布环境中存在 info.php / phpinfo.php已启用调试分析 PHPInfo(参见 phpinfo-to-rce)
预发布环境没有 Cloudflare/WAF可直接访问源站对源站 IP 运行完整的 deep-invade
预发布环境启用了 CORS,生产环境没有预发布环境的 CORS 策略较弱从预发布环境上下文发起 CORS 攻击
预发布环境中存在 .env开发凭据暴露窃取凭据,转向生产环境

操作步骤

第 1 步——证书透明度枚举
DOMAIN="$1"
OUTDIR="$OUTDIR/staging/$DOMAIN"
mkdir -p "$OUTDIR"

echo "[*] crt.sh enumeration for *.$DOMAIN"

# Primary crt.sh query
curl -sk --max-time 30 --connect-timeout 10 "https://crt.sh/?q=%25.$DOMAIN&output=json" 2>/dev/null | \
  jq -r '.[].name_value' 2>/dev/null | sed 's/\*\.//g' | sed 's/^www\.//' | sort -u > "$OUTDIR/crtsh_subs.txt"

# Also try without wildcard prefix
curl -sk --max-time 30 --connect-timeout 10 "https://crt.sh/?q=$DOMAIN&output=json" 2>/dev/null | \
  jq -r '.[].name_value' 2>/dev/null | sed 's/\*\.//g' | sed 's/^www\.//' | sort -u >> "$OUTDIR/crtsh_subs.txt"

sort -u "$OUTDIR/crtsh_subs.txt" -o "$OUTDIR/crtsh_subs.txt"

sub_count=$(wc -l < "$OUTDIR/crtsh_subs.txt")
echo "[+] crt.sh: $sub_count unique subdomains"

# Categorize by pattern
echo ""
echo "[*] Staging/dev subdomains:"
grep -iE 'staging|stage|dev\.|development|test|uat|beta|sandbox|demo|preview|qa' "$OUTDIR/crtsh_subs.txt"

echo ""
echo "[*] Admin/internal subdomains:"
grep -iE 'admin|portal|internal|dashboard|manage|cp\.|control|panel|cpanel|webmail|mail\.' "$OUTDIR/crtsh_subs.txt"

echo ""
echo "[*] API subdomains:"
grep -iE 'api|rest|graphql|ws\.|websocket' "$OUTDIR/crtsh_subs.txt"

echo ""
echo "[*] Infrastructure subdomains:"
grep -iE 'cdn|static|assets|media|img|images|files|download|origin|proxy' "$OUTDIR/crtsh_subs.txt"

echo ""
echo "[*] Franchise/location subdomains:"
grep -iE 'franchise|location|store|shop|branch|office' "$OUTDIR/crtsh_subs.txt"
第 2 步——发现在线主机
DOMAIN="$1"
OUTDIR="$OUTDIR/staging/$DOMAIN"

echo "[*] Probing $(wc -l < "$OUTDIR/crtsh_subs.txt") subdomains..."

httpx -silent -l "$OUTDIR/crtsh_subs.txt" -threads 50 -status-code -tech-detect -title \
  -o "$OUTDIR/alive_subs.txt"

alive=$(wc -l < "$OUTDIR/alive_subs.txt")
echo "[+] $alive live hosts"

# Prioritize staging/dev
echo ""
echo "[*] Staging/dev LIVE:"
grep -iE 'staging|stage|dev\.|development|test|uat' "$OUTDIR/alive_subs.txt" | head -20
第 3 步——检查 WordPress 安装页面(预发布环境接管)

预发布站点经常已安装 WordPress,但尚未完成配置:

DOMAIN="$1"
OUTDIR="$OUTDIR/staging/$DOMAIN"

echo "[*] Checking for WordPress install pages on staging..."

for sub in $(grep -iE 'staging|stage|dev' "$OUTDIR/alive_subs.txt" | awk '{print $1}' | head -10); do
  echo "--- $sub ---"

  # Check install.php (fresh WP, no config)
  install_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 "$sub/wp-admin/install.php")
  if [[ "$install_code" == "200" ]]; then
    echo "  [TAKEOVER] /wp-admin/install.php — fresh WP install, can configure site!"
    # Extract form fields
    curl -sk --max-time 10 --connect-timeout 10 "$sub/wp-admin/install.php" | grep -Eo 'name="[^"]+"' | sort -u
  fi

  # Check upgrade.php (needs DB upgrade)
  upgrade_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 "$sub/wp-admin/upgrade.php")
  if [[ "$upgrade_code" == "200" ]]; then
    echo "  [INFO] /wp-admin/upgrade.php — DB upgrade page accessible"
  fi

  # Check setup-config.php (no wp-config)
  config_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 "$sub/wp-admin/setup-config.php")
  if [[ "$config_code" == "200" || "$config_code" == "409" ]]; then
    echo "  [INFO] /wp-admin/setup-config.php — wp-config missing or accessible"
  fi

  # Check for exposed info.php
  info_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$sub/info.php")
  [[ "$info_code" == "200" ]] && echo "  [CRITICAL] /info.php exposed on staging!"

  # Check for .env on staging
  env_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$sub/.env")
  [[ "$env_code" == "200" ]] && echo "  [CRITICAL] /.env exposed on staging!"
done
第 4 步——分析生产环境与预发布环境的安全差异
DOMAIN="$1"
PROD="https://$DOMAIN"
STAGING=$(grep -iE 'staging|stage' "$OUTDIR/staging/$DOMAIN/alive_subs.txt" | head -1 | awk '{print $1}')

if [[ -n "$STAGING" ]]; then
  echo "[*] Comparing $PROD vs $STAGING"

  # Compare HTTP headers
  echo "=== Production Headers ==="
  curl --max-time 30 --connect-timeout 10 -skI "$PROD" 2>/dev/null | head -20

  echo ""
  echo "=== Staging Headers ==="
  curl --max-time 30 --connect-timeout 10 -skI "$STAGING" 2>/dev/null | head -20

  # Check for common staging weaknesses
  echo ""
  echo "[*] Staging-specific checks:"

  # Directory listing
  listing=$(curl -sk --max-time 5 --connect-timeout 5 "$STAGING/wp-content/uploads/" | grep -i "Index of")
  [[ -n "$listing" ]] && echo "  [WEAK] Directory listing enabled on uploads"

  # Debug mode
  debug=$(curl -sk --max-time 5 --connect-timeout 5 "$STAGING/" | grep -i "wp_debug\|debug mode\|error_reporting")
  [[ -n "$debug" ]] && echo "  [WEAK] Debug output visible"

  # CORS on staging
  cors=$(curl -skI --max-time 5 --connect-timeout 5 "$STAGING/wp-json/wp/v2/users" -H "Origin: https://evil.com" | grep -i "access-control-allow-credentials: true")
  [[ -n "$cors" ]] && echo "  [WEAK] CORS credential reflection on staging"

  # XMLRPC on staging
  xmlrpc=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 -X POST "$STAGING/xmlrpc.php" \
    -d '<?xml version="1.0"?><methodCall><methodName>demo.sayHello</methodName></methodCall>')
  [[ "$xmlrpc" == "200" ]] && echo "  [WEAK] XMLRPC open on staging"
fi
第 5 步——加盟/多地点子域名枚举

适用于加盟业务或多地点企业:

DOMAIN="$1"
OUTDIR="$OUTDIR/staging/$DOMAIN"

echo "[*] Franchise/location subdomains:"

# Extract location-based subdomains
grep -iE 'franchise|location|store|shop|branch|office|city|state' "$OUTDIR/alive_subs.txt" | while read -r line; do
  sub=$(echo "$line" | awk '{print $1}')
  echo "--- $sub ---"

  # Check if it's a WordPress site
  wp=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$sub/wp-login.php")
  [[ "$wp" =~ ^(200|301|302)$ ]] && echo "  WordPress detected"

  # Check for WPSL (WP Store Locator) data
  wpsl=$(curl --max-time 30 --connect-timeout 10 -sk "$sub/wp-json/wpsl/v1/" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,list) else 'no')" 2>/dev/null)
  [[ "$wpsl" != "no" && "$wpsl" != "0" ]] && echo "  WPSL: $wpsl locations"

  # Check for store-specific data
  users=$(curl --max-time 30 --connect-timeout 10 -sk "$sub/wp-json/wp/v2/users" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,list) else 0)" 2>/dev/null)
  [[ "$users" -gt 0 ]] && echo "  Users: $users"
done

注意事项

  • crt.sh 速率限制。 达到速率限制时,crt.sh 可能返回空 JSON。查询之间间隔 2-3 秒,或直接在 crt.sh/?d= 查询 PostgreSQL 转储。
  • 通配符证书会隐藏子域名。 如果只有 *.example.com 证书,crt.sh 中不会显示各个子域名。可使用 subfinder DNS 暴力枚举作为备用方法。
  • 预发布环境可能需要 VPN。 有些预发布环境限制了 IP 地址。 仅从已获授权的测试源地址进行测试。
  • 生产环境可能也能访问 WordPress install.php。 一些维护不善的生产站点也开放了此页面。这不一定是预发布环境独有的问题。检查页面标题文字中是否有 “Welcome to WordPress”,以确认这是全新安装。
  • 生产环境和预发布环境的 CORS 配置可能不同。 在声称存在安全控制缺口之前,先在两个环境中测试相同的有界端点矩阵。

验证

  • 必须使用 httpx 探测每个预发布子域名,确认其处于在线状态。
  • WordPress install.php 必须返回 HTTP 200,且响应正文包含 “WordPress” 和 “installation”(而非重定向或 SPA)。
  • 必须将预发布环境的弱点与生产环境进行比较,确认存在安全缺口(例如,生产环境有 WAF,而预发布环境没有)。
  • 必须确认通过 crt.sh 发现的内部子域名确实属于目标的基础设施,而非同一证书中无关的域名。
  • 必须将发现的凭据或配置值作为敏感证据处理。仅在获得明确授权且使用获批身份的情况下测试凭据;切勿假定可以在生产环境中尝试预发布环境的凭据。

评论

支持 Markdown