Skip to main content

Professional web recon

Running nmap to map a web application is like doing surgery with a traffic radar. Nmap discovers port 443 is open — you already know that, there’s a website. To map a corporate monolith’s real attack surface, you need to behave exactly like a legitimate user.
This page covers professional recon without raising alarms: passive recon, silent fingerprinting, type confusion, JA3 and Client Hints evasion. Full methodology with decision tree at /en/methodology.

Why Nmap won’t help here

The server sits behind a WAF, a CDN and an API Gateway. What Nmap touches is the first shield, not the brain.

Layer 0 — Absolute passive recon (zero contact)

You send not a single packet to the client’s server. All info is public.

Wayback Machine

Domain URL history. Obsolete endpoints developers forgot to delete and that no longer pass current WAF rules.
🎯 Gems found here:
  • /api/v1/auth (old version without rate limit)
  • /old/admin/login.php (legacy panel not migrated)
  • .env, .git/config, backup.sql (accidental uploads)

Certificate Transparency

Every issued cert is published in public logs. SANs are a subdomain goldmine.
🎯 What appears here:
  • staging.empresa.com, dev.empresa.com, qa.empresa.com
  • vpn.empresa.com, mail.empresa.com
  • Internal subdomains accidentally leaked (internal-api.empresa.com)

Google / Shodan / Censys dorking

GitHub: abandoned secrets

The first place a modern attacker looks.
Historical gems: developers delete a commit with a secret and assume it’s clean. But the commit stays in history until the branch is rewritten. git log --all --full-history -p -- file.env reveals it.

Layer 1 — Frontend analysis (DevTools, not scanner)

Your dev knowledge makes the difference. The browser does the dirty work, not a scanner the WAF would detect.

React/Angular/Vue source maps

If the dev team pushed *.map to production (they do 30% of the time), you have the original unminified source.

JS bundles: find endpoints, tokens, comments

Even without source maps, minified bundles contain hardcoded API routes, forgotten dev tokens, comments, and feature flags.
🎯 What appears here:
  • Internal endpoints: /api/v2/internal/users, /admin/debug
  • Active feature flags: enable_legacy_auth: true
  • Forgotten dev Stripe/Mailgun/Sendgrid tokens
  • Comments with TODOs and warnings

robots.txt, sitemap.xml, .well-known

The house gifts. Almost always reveal internal routes.

Layer 2 — Proxied browsing (passive Burp)

Configure Burp Suite or OWASP ZAP as intermediary and browse manually. Create account, use the app, fill forms with coherent data. 🎯 Goal in this phase is NOT to attack. It’s to:
  • Map the real directory tree (browser-visible is a fraction).
  • Capture all endpoints and HTTP verbs in use.
  • Dissect session tokens (JWT, cookies).
  • Identify susceptible parameters (numeric IDs, paths, redirects).
The WAF sees this as 100% legitimate traffic, because literally it is.

Minimum stealth Burp config


Layer 3 — Backend fingerprint (without firing WAF)

After passive browsing, you know tokens, endpoints and structure. Time to identify backend language and framework.

Extensions are dead

No modern corporate app exposes file type in URL. Forget about .php or .aspx — clean routing shows /api/v1/auth and nothing else.

Fingerprint by session cookies

Languages have default names. If the team didn’t rename them (usually they didn’t), you have the fingerprint without sending anything.

Fingerprint by headers

Fingerprint by error anatomy (the king technique)

If headers are stripped and cookies obfuscated, force a controlled error. You’re not injecting anything — you want the backend parser to trip.

Type Confusion

API expects a string. Send another type.
🎯 How to read result:

HTTP verb tampering

🎯 Backend output by unsupported verb:
  • 405 Method Not Allowed with JSON body {"error":"..."} → modern well-configured framework.
  • 405 with HTML error → could be Apache/Tomcat default page.
  • 500 with stack trace → framework didn’t expect that verb and vomits.

Structural corruption

JSON with extra comma, missing brace, alternate encoding.

Layer 4 — What modern WAFs see (and how to evade)

The WAF doesn’t only check your IP. It identifies you across multiple synchronized layers. Faking one tells on you.

The IP-block myth

Imagine you change IP via VPN after first block. The WAF blocks you instantly again. Why?
  1. Identical session cookie — The WAF tracks the user, not just the network.
  2. Identical JA3 fingerprint — Your Python/curl has a unique TLS signature. Changing IP doesn’t change this.
  3. Distributed attack pattern — 50 different IPs with same JA3 = coordinated attack, max priority.

JA3/JA4: client crypto fingerprint

The TLS handshake (Client Hello) includes supported algorithm and extension list. Each client has a unique signature.
🛡️ (Offensive) solutions:

Client Hints (sec-ch-ua-*)

Chromium browsers send a sec-ch-ua-* block describing brand and platform. If your User-Agent says Chrome 120 but you don’t send these, the WAF detects asymmetry.

Header structural order

Real browsers assemble HTTP requests in rigid order. Libraries (Python requests, Go net/http) order them alphabetically or randomly. 🛡️ Solution: use tools that respect order (tls-client, curl-impersonate, real browsers).

Geo-blocking and ASN

If the client only allows Spanish IPs, just changing AWS region isn’t enough:
But advanced WAFs also look at ASN: 🛡️ Professional solution: legal residential proxies (Bright Data, Oxylabs). Your traffic comes from real residential routers in Spain.

Passive browser biometrics

The login page loads JavaScript (reCAPTCHA v3, Cloudflare Turnstile, DataDome) that records in real time:
  • Mouse trajectory (human = imprecise logarithmic curves).
  • Typing dynamics (human = irregular ms between keydown and keyup).
  • Hardware events (real key presses, not direct DOM injection).
Your script injects password = "123456" in the DOM in 0 ms, without keyboard events. The JS detects the ghost. 🛡️ Solution:
  • Real browser with playwright-stealth or undetected-chromedriver.
  • Mouse motion simulation with Bézier curves.
  • page.type() with realistic delays instead of page.evaluate(() => input.value = ...).

Layer 5 — When the wall is smooth (no info leak)

When no error reveals tech, the dev team is competent. Change attack plane — don’t insist on fingerprinting.

Timing attacks

Response time is a tell no global exception handler can hide.

Endpoint archaeology

Old non-migrated versions:

Mass Assignment (the goldmine)

Without knowing tech, try injecting extra fields in legit JSON:
If response is 200 OK, check later in /profile if account got new attributes.

Systematic IDOR

Any endpoint taking an ID:
If multiple return 200, IDOR confirmed.

How to harden your own backend against this recon

If you’re defending instead of auditing:

1. Global exception handler

Never let the framework leak the stack trace.

2. Strip revealing headers

3. Rename session cookies

4. Aggressive rate limiting on fingerprint endpoints