← All field notes
Web ReconQuick reference

Web Recon Pipeline

An httpx, Katana, and Nuclei workflow that keeps live URLs, crawl output, and reviewed scanner passes separate.

This pipeline keeps each stage’s input separate from its richer or noisier output:

approved hosts
    │
    ▼
  httpx ───────────────> live_urls.txt ──────────┬─> fingerprint and screenshots
                                                 ├─> origins.txt ─> broad HTTP templates
                                                 └─> Katana ──────> endpoints.txt
                                                                   reviewed endpoint-specific tests

The important split is between endpoints.txt, which retains complete crawled URLs, and origins.txt, which contains one scheme://host:port value per web service.

File Contents Next consumer
targets.txt Approved hostnames, addresses, or CIDRs httpx port probing
live_urls.txt Responsive HTTP or HTTPS origins Fingerprinting, screenshots, and Katana
katana_urls.txt Complete URLs from the standard crawl Endpoint merge
katana_headless_urls.txt Navigated and XHR URLs from selected browser-rendered targets Endpoint merge
endpoints.txt Deduplicated complete URLs Manual review and endpoint-specific templates
origins.txt Deduplicated scheme://host:port values Broad Nuclei HTTP template directories

Run the workflow in a dedicated assessment directory. These files can contain internal names, query values, tokens, and scanner findings.

Start with the installed versions

Check the binaries and template set before relying on copied flags:

HTTPX_BIN="$(command -v httpx-toolkit || command -v httpx)"
printf 'httpx: %s\n' "${HTTPX_BIN:-not found}"
command -v katana
command -v nuclei
command -v unfurl
command -v jq

Do not continue until each command resolves the intended binary. Then record the versions used for the assessment:

"$HTTPX_BIN" -version
katana -version
nuclei -version
nuclei -tv

Kali often names the ProjectDiscovery binary httpx-toolkit; other installations commonly use httpx. Resolving it first avoids accidentally calling the unrelated Python HTTP client.

Update Nuclei templates before an assessment begins, then keep that template version fixed while testing so the results remain reproducible:

nuclei -ut
nuclei -tv

Prepare the approved input

Use one approved hostname, address, or CIDR per line. Remove blank lines and comments, deduplicate the list, and create owner-only working files on POSIX systems:

umask 077

awk 'NF && $1 !~ /^#/ { print $1 }' '<TARGETS_FILE>' \
  | sort -u \
  > targets.txt

wc -l targets.txt

Review targets.txt before probing. A clean input list is the scope boundary for every later stage; a URL discovered by crawling is not permission to leave the approved hosts.

Probe common web ports

This is the wider port list I use when the normal 80,443,8080,8443 pass is not enough. It is useful on both external and internal target lists, but the input still needs to contain only approved hosts.

"$HTTPX_BIN" -l targets.txt \
  -p 80,81,300,443,591,593,832,981,1010,1311,2082,2087,2095,2096,2480,3000,3128,3333,4243,4567,4711,4712,4993,5000,5104,5108,5800,6543,7000,7396,7474,8000,8001,8008,8014,8042,8069,8080,8081,8088,8090,8091,8118,8123,8172,8222,8243,8280,8281,8333,8443,8500,8834,8880,8888,8983,9000,9043,9060,9080,9090,9091,9200,9443,9800,9981,12443,16080,18091,18092,20720,28017 \
  -silent \
  -rl 50 \
  -t 25 \
  -o live_urls.txt

Keep the first output clean because Katana expects URLs, not the decorated status and title output. Run the richer fingerprint view separately:

"$HTTPX_BIN" -l live_urls.txt \
  -sc -title -td -server -ip \
  -rl 50 -t 25 \
  -o httpx_fingerprint.txt

Screenshots are also a separate pass. That keeps Chromium away from ports that did not return a web response:

"$HTTPX_BIN" -l live_urls.txt -ss -rl 20 -t 10 -o screenshot_results.txt

Crawl the live URLs

Start without a browser. Katana’s default scope follows the root domain; -fs fqdn keeps each crawl on the exact host supplied in the input.

katana -list live_urls.txt \
  -d 5 \
  -fs fqdn \
  -jc \
  -kf robotstxt,sitemapxml \
  -ef css,png,jpg,jpeg,svg,gif,ico,woff,woff2,eot,ttf \
  -c 10 -p 5 -rl 40 \
  -silent \
  -o katana_urls.txt

For applications that depend heavily on browser-rendered JavaScript, create headless_targets.txt as a reviewed subset of live_urls.txt, with one complete URL per line. Repeat the crawl only against that smaller set:

katana -list headless_targets.txt \
  -d 5 \
  -fs fqdn \
  -headless \
  -xhr \
  -ef css,png,jpg,jpeg,svg,gif,ico,woff,woff2,eot,ttf \
  -c 5 -p 2 -rl 20 \
  -silent \
  -jsonl \
  -eof raw,body \
  -o katana_headless.jsonl

-xhr records browser requests inside each JSONL result’s response.xhr_requests array; it does not add those URLs to normal text output. Extract both navigated pages and XHR endpoints before merging the crawl results:

jq -r '
  (.request.endpoint? // empty),
  (.response.xhr_requests[]?.endpoint? // empty)
' katana_headless.jsonl \
  | sort -u \
  > katana_headless_urls.txt

Do not add -no-sandbox by habit. It is intended for environments such as a container running as root where Chromium cannot otherwise start.

Preserve complete URLs

Nuclei needs complete URLs. Extracting only the path with unfurl paths loses the scheme and host, so a later nuclei -l run would no longer know where to send the request.

sort -u katana_urls.txt katana_headless_urls.txt > endpoints.txt

unfurl -u format '%s://%d%:%P' < live_urls.txt > origins.txt

If only the standard crawl was used:

sort -u katana_urls.txt > endpoints.txt
unfurl -u format '%s://%d%:%P' < live_urls.txt > origins.txt

Check the reduction before scanning:

wc -l targets.txt live_urls.txt endpoints.txt origins.txt
head -n 3 endpoints.txt
head -n 3 origins.txt

Every line in endpoints.txt should still have a scheme and host. Every line in origins.txt should end at the authority, without an application path.

Run a reviewed HTTP template set

The current Nuclei flag is -tags, not -as-tags. Broad template directories should run once per origin, not once for every path Katana found. Many templates append their own fixed path to {{BaseURL}}; using a crawled path as the base can create requests such as /api/users/login and repeat that mistake across the entire crawl.

Use origins.txt for the broad pass and keep endpoints.txt for templates intentionally designed to test a supplied endpoint. First list the templates selected by the directories and filters:

nuclei -tl \
  -t http/cves/ \
  -t http/exposed-panels/ \
  -t http/exposures/ \
  -t http/misconfiguration/ \
  -etags intrusive,dos,bruteforce,fuzz \
  -s low,medium,high,critical \
  > nuclei-http-templates.txt

Review that list and inspect unfamiliar or high-impact templates before execution. Tag exclusions reduce the set; they do not prove that every remaining request is safe for the target.

Display one selected template when its behavior needs closer review:

nuclei -td -t '<TEMPLATE_PATH>'

Run the reviewed set:

nuclei -l origins.txt \
  -t http/cves/ \
  -t http/exposed-panels/ \
  -t http/exposures/ \
  -t http/misconfiguration/ \
  -etags intrusive,dos,bruteforce,fuzz \
  -ni \
  -s low,medium,high,critical \
  -c 10 -rl 40 \
  -stats -jsonl -or \
  -o nuclei-http.jsonl

-ni disables Interactsh and excludes OAST-based templates. -or avoids placing full request and response bodies in JSONL. Findings can still contain matched or extracted values, so protect the output and treat each result as a lead to validate manually rather than a confirmed finding.

Do not substitute endpoints.txt into this command merely because it contains more lines. More inputs here usually mean duplicated or malformed template paths, not better coverage.

DNS, TLS, and network templates take hostnames or IPs rather than crawled paths, so keep that pass separate:

nuclei -l '<HOSTS_FILE>' \
  -t dns/ -t ssl/ -t network/ \
  -etags intrusive,dos,bruteforce,fuzz \
  -ni \
  -s low,medium,high,critical \
  -c 10 -rl 40 \
  -stats -jsonl -or \
  -o nuclei-hosts.jsonl

Check downloaded JavaScript separately

Most HTTP templates are designed for pages and API routes. A smaller token-exposure pass is more useful for JavaScript responses:

grep -Ei '\.(m?js)([?#].*)?$' endpoints.txt | sort -u > javascript_urls.txt

nuclei -l javascript_urls.txt \
  -t http/exposures/tokens/ \
  -ni \
  -c 5 -rl 20 \
  -jsonl -or \
  -o javascript_exposures.jsonl

Treat this output as sensitive because a successful extractor may print a live credential. Store it with the same care as other assessment secrets.

Keep heavier testing separate

Headless templates and DAST/fuzzing templates are not part of the broad pass. They create more traffic and can submit payloads to application inputs. Run them only against an explicitly approved endpoint list, with a target-specific scope expression and a reviewed template set.

Do not fold token-spray into this workflow. It performs authentication attempts and can create lockouts or noisy account activity; it needs separate authorization and a deliberately selected identity set, not a generic target list.

Source references