Recon
Map every live host’s services, web surface, and content into a per-host picture you can attack.
Discovery told you what exists. Recon tells you what’s running on it. For each
live host you build a profile: open ports, service versions, web apps,
screenshots, and discovered content. This is the raw material the
hunting phase mines for vulnerabilities.
Organize per host
Recon output is per-host. The convention (Arsenic’s hosts/ layout) keeps each
host’s data isolated, so you can hand a teammate one host folder and they have
everything:
hosts/
└── 203.0.113.10/
└── recon/
├── nmap-quick-tcp.{nmap,gnmap,xml} # full TCP port sweep
├── nmap-tcp.{nmap,gnmap,xml} # version/script scan of open ports
├── nmap-udp.{nmap,gnmap,xml}
├── httpx.txt # live web services
├── ffuf.*.json # content discovery
└── hostnames.txt # vhosts pointing at this IP
The recon pipeline
For each live host, in order:
- Port scanning — find every open TCP (and key UDP) port.
- Service enumeration — version + default-script
scan the open ports to identify what’s listening.
- HTTP probing & screenshots — find web
services across all hosts/ports and eyeball them fast.
- Content discovery — fuzz web roots for hidden paths,
files, and endpoints.
Scan once per IP, remember every name
A single IP frequently hosts many domains (name-based virtual hosting). Port scan
the IP so you don’t scan the same box ten times — but carry the list of
hostnames forward, because the web server may serve completely different apps
depending on the Host: header. The HTTP probing
step is where vhosts matter most.
A note on pacing
Recon is the loudest phase so far — full port scans and fuzzing are
unmistakable. Respect the rate limits from your rules of
engagement:
tune nmap -T, ffuf -rate, and run during permitted windows. When in doubt, go
slower. A knocked-over production service is a bad look and a worse phone call.
Start with Port Scanning.
1 - Port Scanning
Find every open port on each live host — fast, then thorough — without scanning the same box twice.
The goal is the complete set of open ports on each host. The pattern that
balances speed and completeness is two passes: a fast full-range sweep to
find which ports are open, then a detailed scan of only those ports (covered
in Service Enumeration).
Pass 1: fast full-range TCP sweep
Scan all 65,535 TCP ports quickly to find what’s open. I reach for one of two
tools here.
nmap (the classic)
host=203.0.113.10
mkdir -p "hosts/$host/recon"
sudo nmap -p- --open -Pn -n \
--min-rate 1500 --max-retries 1 \
-T4 \
"$host" \
-oA "hosts/$host/recon/nmap-quick-tcp"
# Pull the open ports into a comma list for pass 2
ports=$(awk -F/ '/open/{print $1}' "hosts/$host/recon/nmap-quick-tcp.gnmap" \
| tr '\n' ',' | sed 's/,$//')
-p- — all 65,535 ports.--open — only report open ports.-Pn — skip host discovery (you already know it’s up).-n — no DNS resolution (faster, quieter).--min-rate / --max-retries — speed knobs; raise min-rate on robust
networks, lower it on fragile ones.
naabu (faster for many hosts)
naabu uses a SYN scan and is
noticeably quicker across large host lists. Pipe its results straight into nmap
for versioning:
naabu -host "$host" -p - -silent -o "hosts/$host/recon/naabu-tcp.txt"
ports=$(cut -d: -f2 "hosts/$host/recon/naabu-tcp.txt" | paste -sd,)
masscan is an option for very large IP ranges (it can scan the internet in
minutes), but it trades accuracy for speed and needs careful rate limiting. For
typical engagement-sized scope, nmap --min-rate or naabu is plenty.
Incremental / batched scanning for large scope
When you have many hosts, scanning every port on every host serially takes
forever. Arsenic batches this: scan the most popular ports across all hosts
first (you get fast, high-value coverage), then work through the remaining port
ranges in batches. The idea is to surface the interesting services early instead
of waiting for a full sweep of one host before starting the next.
A simple version — popular ports across everything first:
TOP_PORTS=$(sort -r -k3 /usr/share/nmap/nmap-services | awk '/\/tcp/{print $2}' \
| cut -d/ -f1 | head -n 1000 | paste -sd,)
sudo nmap -sS -p"$TOP_PORTS" --open -Pn -n -T4 \
--min-hostgroup 255 --max-retries 1 \
-iL recon/ips/alive.txt \
-oA recon/nmap-popular-tcp
Then schedule the full -p- sweep per host as time allows.
UDP — don’t skip it entirely
UDP scanning is slow, but skipping it misses SNMP, DNS, SChannel, IKE, TFTP,
NetBIOS and other juicy services. Scan the top UDP ports rather than all of them:
sudo nmap -sU --top-ports 100 --open -Pn -n -T4 \
"$host" -oA "hosts/$host/recon/nmap-udp"
With the open-port list in hand, move to Service
Enumeration to find out what’s actually listening.
2 - Service Enumeration
Identify the exact software and version behind every open port — the input every later step depends on.
Knowing port 8080 is open tells you little. Knowing it’s Apache Tomcat 9.0.30
tells you what default paths to check, what CVEs apply, and what credentials to
try. Service enumeration turns open ports into identified, versioned services.
Version + default-script scan
Run nmap against only the ports you found open in port
scanning, with version detection and the default safe scripts.
This is the deep, accurate scan, so let it take its time:
host=203.0.113.10
ports=$(awk -F/ '/open/{print $1}' "hosts/$host/recon/nmap-quick-tcp.gnmap" \
| tr '\n' ',' | sed 's/,$//')
sudo nmap -p"$ports" -sV -sC -A -Pn -n \
--host-timeout 30m \
"$host" \
-oA "hosts/$host/recon/nmap-tcp"
What the flags do:
-sV — probe for service/version.-sC — run the default NSE script set (banner grab, titles, common checks).-A — aggressive: adds OS detection, traceroute, and more scripts. Drop it if
you need to be quieter; -sV -sC alone is the high-signal core.--host-timeout — don’t let one stubborn host stall the whole run.
The two output formats you’ll use constantly:
.nmap — human-readable; read it..xml — machine-readable; feed it to searchsploit, reporting tools, and
importers.
Walk every host’s .nmap output and pull out:
- Service + version for each port → drives vulnerability
hunting.
- HTTP/HTTPS services (including odd ports like 8000, 8443, 3000) → feed to
HTTP probing.
- TLS cert names → may surface new vhosts/domains (loop back to
discovery).
- Anonymous/guessable access flagged by NSE scripts (FTP anon login, open SMB
shares, exposed RPC).
Targeted NSE for interesting services
When -sC flags something, follow up with service-specific scripts. A few I
reach for a lot:
# SMB — shares, users, known vulns
nmap -p139,445 --script "smb-enum-shares,smb-enum-users,smb-vuln-*" "$host"
# HTTP — titles, methods, common files
nmap -p80,443 --script "http-title,http-methods,http-headers,http-enum" "$host"
# SSL/TLS — protocols, ciphers, weaknesses
nmap -p443 --script "ssl-enum-ciphers,ssl-cert" "$host"
Quick service inventory across all hosts
To get a one-line-per-service overview for triage:
grep -hP '^\d+/(tcp|udp)\s+open' hosts/*/recon/nmap-*.nmap \
| awk '{print $1, $3, $4, $5, $6, $7}' \
| sort | uniq -c | sort -rn
This tells you at a glance “we have 40 web servers, 12 SSH, 6 RDP, 3 Tomcat” —
which decides where to spend the hunting phase.
Next: get eyes on the web surface with HTTP Probing &
Screenshots.
3 - HTTP Probing & Screenshots
Find every live web service across all hosts and ports, then screenshot them to triage the web surface at a glance.
Web is where most findings live. After port scanning you have a pile of open
ports that might be HTTP; this step confirms which ones actually serve web
content — on which scheme and port, with what title and technology — then
screenshots them so you can eyeball hundreds of apps in minutes.
Probe with httpx
I use httpx (ProjectDiscovery)
for this. Feed it every host and every web-ish port; it works out http vs https,
follows redirects, and reports a bunch of metadata.
# Build the candidate list: every hostname + IP you care about
# (httpx will try each on the ports you specify)
httpx -l recon/web-candidates.txt \
-p 80,443,8000,8001,8080,8443,3000,8843,9000 \
-title -status-code -tech-detect -web-server -content-length \
-follow-redirects \
-json -o recon/httpx.json -silent
# Plain list of live URLs for the next steps
jq -r '.url' recon/httpx.json | sort -u | tee recon/live-urls.txt
The flags that matter:
-tech-detect — Wappalyzer-style fingerprinting (CMS, framework, server).
Really useful for the hunting phase.-title -status-code -web-server — fast triage columns.-follow-redirects — catches apps that bounce http→https or to a login.
Virtual hosts matter here. The same IP can serve different apps per Host:
header, so probe by hostname, not just IP, and name-based vhosts get
discovered. If you have many names on one IP, httpx handles the list — just make
sure the hostnames (not only IPs) are in your candidate file.
Screenshot the web surface
Eyeballing screenshots is the fastest way to spot login panels, default install
pages, admin consoles, and abandoned apps across a large estate.
Arsenic originally used aquatone for this. Aquatone is archived now, so I use
one of these instead.
Option A — gowitness (what I use)
gowitness scan file -f recon/live-urls.txt \
--screenshot-path report/static/screenshots \
--write-db # SQLite report you can browse
gowitness report server # browse at http://localhost:7171
Option B — httpx built-in screenshots
If you’d rather not add a tool, httpx can screenshot during the probe:
httpx -l recon/web-candidates.txt -p 80,443,8080,8443 \
-screenshot -srd report/static/screenshots -silent
Option C — aquatone (still works)
If you’re maintaining an existing aquatone-based flow:
cat recon/live-urls.txt \
| aquatone -ports 80,443,3000,8000,8001,8080,8443 \
-out report/static/aquatone
Triage the gallery
Open the report and bucket what you see:
- Login panels → credential testing, default creds, auth bypass.
- Default/install pages → unconfigured apps, often exploitable.
- Admin consoles (Tomcat Manager, Jenkins, phpMyAdmin, Grafana) → high-value
targets; check default creds immediately.
- Errors / stack traces → version disclosure, debug endpoints.
- Parked / blank → deprioritize.
Promising apps go to Content Discovery for deeper fuzzing,
and the whole live-URL list feeds vulnerability hunting.
4 - Content Discovery
Fuzz web roots for hidden directories, files, and endpoints the app doesn’t link to.
Apps expose far more than their navigation shows: /admin, /.git/,
/backup.zip, /api/v1, /.env, old /test.php files. Content discovery
brute-forces paths against a wordlist to find them. Run it against every live web
service from HTTP probing.
Pick a fuzzer
Arsenic supports gobuster, dirb, and ffuf, defaulting to ffuf. The two
I actually use:
- ffuf — fast, flexible, good filtering; my
default.
- feroxbuster — recursive by
default, nice for deep trees.
Wordlists
SecLists is where I pull wordlists
from. A solid general-purpose stack (this mirrors Arsenic’s default web-content
set):
Discovery/Web-Content/common.txt
Discovery/Web-Content/raft-medium-words.txt
Discovery/Web-Content/raft-large-directories.txt
Discovery/Web-Content/quickhits.txt
Discovery/Web-Content/RobotsDisallowed-Top1000.txt
Build a combined, de-duplicated list once:
cat /opt/SecLists/Discovery/Web-Content/{common,raft-medium-words,quickhits}.txt \
| sort -u > recon/wordlist-web-content.txt
Tailor it to the tech you fingerprinted: a Tomcat box gets tomcat.txt, a
Jenkins box gets Jenkins-Hudson.txt, and so on.
Run ffuf
url="https://app.example.com"
host=app.example.com
mkdir -p "hosts/$host/recon"
ffuf -u "$url/FUZZ" \
-w recon/wordlist-web-content.txt \
-ac \
-mc all -fc 404 \
-recursion -recursion-depth 2 \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" \
-of json -o "hosts/$host/recon/ffuf.json"
What the flags do (these match the Arsenic as-ffuf defaults):
-ac — auto-calibration: ffuf learns the “not found” response shape and
filters it automatically. You need this, or you drown in false positives.-mc all -fc 404 — match everything, filter out 404s. Lets you see 401/403
(exists but protected) and 500 (something broke = interesting).-recursion -recursion-depth 2 — dig into discovered directories.-e .php,.bak,.zip,.txt — add extension fuzzing when you know the stack.
Tune signal, not noise
Auto-calibration handles most of the junk, but apps that return 200 for
everything need manual filtering. Inspect the size/word/line distribution and
filter the dominant bucket:
# How many results per status code?
jq '.results[].status' hosts/app.example.com/recon/ffuf.json | sort | uniq -c
# Filter by response size if a wildcard 200 is flooding results
ffuf -u "$url/FUZZ" -w wordlist.txt -fs 1234 # filter that exact size
Arsenic’s as-prune-ffuf does exactly this after the fact — trimming the
dominant status/size bucket out of a bloated results file so what’s left is
signal.
What to chase
From the results, prioritize:
- Auth panels & admin paths (
/admin, /manager, /wp-admin). - Source/secrets leakage (
/.git/, /.env, /config.php.bak, /backup/). - APIs (
/api, /swagger, /graphql) — often under-protected. - Anything
403 — it exists and someone tried to hide it.
Discovered endpoints and the technologies you fingerprinted both feed the
Vulnerability Hunting phase.