Active reconnaissance — Hands-on lab
You are going to redo the walkthrough on your own. Three containers, a clean methodology, a report you would hand to a client. This is the skeleton of reconnaissance that will follow you through your entire career.
Plan for: 90 min to 2 h.
Deliverable: ~/labs/rapport/reconnaissance.md (2 pages) and every artifact inside ~/labs/scans/ + ~/labs/captures/.
Module 01 introduced the single-container Docker lab. Here we move to three containers on the same private network: a Kali attacker and two very different targets, to force you to vary your scans. The compose file does the work:
cd labs/docker/module-04-recon-active
docker compose up -d
Thirty seconds later, you have a 10.20.30.0/24 network with three machines on it. Just like scanning a real internal segment.
What you have to hand in
By the end of the lab, in ~/labs/ inside the attacker container (persisted to your host):
scans/decouverte.txt— output of the initial ARP scan.scans/12-ports.nmap+scans/20-ports.nmap— full TCP scans per target.scans/12-versions.nmap+scans/20-versions.nmap— version scans.scans/20-nse.nmap— NSE scripts focused on expected vulnerabilities.scans/20-udp.nmap+scans/20-snmp.txt— UDP recon and SNMP inventory.scans/20-smb.txt— SMB enumeration (shares, users, policy).scans/12-web.txt— web reconnaissance (whatweb, gobuster, curl).captures/scan-syn.pcap— atcpdumptrace of a SYN scan.scans/scapy-verification.txt— the Scapy script's output.rapport/reconnaissance.md— two pages, imposed structure.
Prerequisites
You should have finished module 01: Docker Desktop up and running, the inskillsec/kali-lab image already built. Otherwise, follow the module 01 lab for the first build (roughly ten minutes).
Quick check:
docker images inskillsec/kali-lab
docker info | grep -i "operating"
If inskillsec/kali-lab is missing, the compose file will build it on first run — wait it out.
Step 1 — Bring the lab up (2 min)
cd labs/docker/module-04-recon-active
docker compose up -d
Three containers start: the attacker (10.20.30.5), the rich Linux target (10.20.30.20) and the clean web target (10.20.30.12).
Health check:
cd ..
./verifier-lab.sh module-04-recon-active # bash / macOS / Linux / WSL
.\verifier-lab.ps1 module-04-recon-active # PowerShell
You should see three running services. If m04-cible-linux sticks at starting, wait another thirty seconds: Metasploitable 2 starts a lot of services (Apache, Samba, MySQL, PostgreSQL, distccd, SNMP…) before it is ready.
Enter the attacker:
cd module-04-recon-active
docker compose exec attaquant bash
Create the output structure:
mkdir -p ~/labs/{scans,captures,rapport}
cd ~/labs
Definition — why this lab needs cap_add: NET_ADMIN, NET_RAW
A regular Docker container runs with a restricted set of Linux capabilities. It cannot modify its network interfaces, cannot forge raw packets (a SYN without ACK, say), cannot run tcpdump. That is fine for a web server, but deadly for an offensive lab.
Two capabilities are essential here. NET_RAW authorizes opening raw sockets, the kind Nmap uses for -sS (SYN scan), the kind Scapy uses to build a packet by hand, the kind arp-scan and tcpdump use to read Ethernet frames directly. NET_ADMIN authorizes route manipulation and interface changes — useful for a few NSE scripts and for arp-scan on some kernels.
The module 04 compose adds them only to the attacker:
cap_add:
- NET_ADMIN
- NET_RAW
Targets do not get them — they have no reason to scan anything. This is least privilege applied at the container level.
Step 2 — Layer 2 discovery (5 min)
The starting point of any internal reconnaissance: who is talking on this segment?
sudo arp-scan --interface=eth0 --localnet | tee scans/decouverte.txt
You see three lines (or four, counting the Docker gateway 10.20.30.1). Note the two target IPs:
export CIBLE_LINUX=10.20.30.20
export CIBLE_WEB=10.20.30.12
An alternative discovery, without ARP:
sudo nmap -sn 10.20.30.0/24 -oN scans/decouverte-nmap.txt
Both methods should return the same number of hosts. A gap would flag a problem with your lab.
Definition — why ARP is the most reliable discovery internally
On a local segment, every host has to know its neighbors' MAC addresses to send them a packet. That mechanism, ARP, cannot be filtered: even a machine with ICMP blocked, TCP filtered, and every port closed must answer an ARP request to stay reachable from its gateway. It is a layer 2 protocol, below anything an application firewall can see.
That is why any serious attacker on the segment starts with ARP. A ping sweep (nmap -sn) works on many networks, but on a LAN with hardened stations refusing ICMP, ARP is the only reliable way to count live hosts. Building the reflex from week 1 keeps you from missing half of a network on a real mission.
Watch out: ARP only works on the same segment. If your attacker is routed to another VLAN, ARP no longer reports anything. In that case, you either pivot, or you fall back on network discoveries (ICMP, TCP SYN on probable ports).
Step 3 — TCP ports (10 min)
Scan all TCP ports on each target. In Docker the link is perfect, we can push the rate:
sudo nmap -sS -Pn -p- --min-rate 4000 $CIBLE_LINUX -oN scans/20-ports.nmap
sudo nmap -sS -Pn -p- --min-rate 4000 $CIBLE_WEB -oN scans/12-ports.nmap
What to check:
10.20.30.20opens about twenty ports (21,22,23,25,53,80,111,139,445,512-514,1099,1524,2049,2121,3306,3632,5432,5900,6000,6667,8009,8180…). The profile of a server left unmaintained for years.10.20.30.12only opens port80. A recent server, well kept, one job. The profile of half the modern web servers.
Note in rapport/ the exact number of open ports per target. A target with more than twenty open ports is almost always either a deliberate lab or a forgotten production server. That is a strong signal, right in the executive summary.
Step 4 — Versions (10 min)
Grab the open-port lists and run -sV only on those ports:
sudo nmap -sV -p 80 $CIBLE_WEB -oN scans/12-versions.nmap
sudo nmap -sV -p 21,22,23,25,53,80,111,139,445,512-514,1099,1524,2049,3306,3632,5432,5900,6000,6667,8009,8180 \
$CIBLE_LINUX -oN scans/20-versions.nmap
Look at what Nmap uncovers:
grep -E "vsftpd|OpenSSH|Samba|MySQL|Apache|Postgres|distcc" scans/20-versions.nmap
grep -E "nginx|X-Powered" scans/12-versions.nmap
You should read:
vsftpd 2.3.4(the old friend from module 01)Samba smbd 3.X - 4.XMySQL 5.0.51aApache/2.2.8distccd v1nginx 1.27.x(with aServerheader not hidden)
Deliverable: in rapport/, for each target, note the 5 most interesting services with their exact version. That is your material for the "Results" section of the report.
Step 5 — Targeted NSE scripts (10 min)
NSE scripts (Nmap Scripting Engine) turn Nmap from a port scanner into a vulnerability-confirmation tool. We only run scripts that make sense given the version scan — never --script vuln blindly, because that can break services.
On the Linux target:
sudo nmap --script "ftp-vsftpd-backdoor,smb-vuln-cve2009-3103,smb-enum-shares,smb-os-discovery,http-vuln-*" \
-p 21,80,139,445 $CIBLE_LINUX -oN scans/20-nse.nmap
On the web target:
sudo nmap --script "http-headers,http-title,http-methods,http-robots.txt,http-enum" \
-p 80 $CIBLE_WEB -oN scans/12-nse.nmap
Deliverable: in rapport/, the list of CVEs confirmed by NSE with the affected IP and CVE reference. On the Linux target, you should find the vsftpd 2.3.4 backdoor (CVE-2011-2523) and probably Samba usermap (CVE-2007-2447).
Definition — why never nmap --script vuln in a wide sweep
--script vuln bundles roughly 150 NSE scripts, some of which are destructive. A few test a vulnerability by deliberately triggering the dangerous behavior — a buffer overflow that crashes the service, an SQL query that locks a table, a malformed send that puts a router in a loop.
In a lab, no problem. On a client's production, a well-placed --script vuln can knock a database offline for three hours. You have a contract that allows you to test, you do not have a contract that allows you to break.
The rule: run NSE by precise category, based on what the version scan revealed. You see SMB? Targeted smb-vuln-*. You see HTTP? http-headers, http-methods. You see FTP? ftp-anon, ftp-vsftpd-backdoor. Every script named in your command, the rationale in the report. It is also what lets the client replay your scans to check their fixes.
Step 6 — UDP and SNMP (10 min)
UDP is slower and often skipped. Precisely why you should go for it.
sudo nmap -sU --top-ports 25 $CIBLE_LINUX -oN scans/20-udp.nmap
If SNMP (161/udp) is open:
snmpwalk -v 2c -c public $CIBLE_LINUX sysDescr
snmpwalk -v 2c -c public $CIBLE_LINUX hrSWRunName > scans/20-snmp.txt
snmpwalk -v 2c -c public $CIBLE_LINUX iso.3.6.1.2.1.4.20 >> scans/20-snmp.txt
Try other common communities:
onesixtyone -c /usr/share/wordlists/onesixtyone/dict.txt $CIBLE_LINUX -o scans/20-community-strings.txt
Deliverable: the number of SNMP communities found and a short summary of the inventory (OS, processes, interfaces) in rapport/.
Definition — SNMP, the forgotten treasury of corporate networks
SNMP (Simple Network Management Protocol) dates from 1988. It is used to monitor and remotely administer switches, routers, printers, servers — anything running an agent. Version 1 uses a community string, a shared password sent in the clear, almost always left at its default: public read-only, private read-write.
What makes SNMP wonderful for an attacker: read in the clear, it hands over the full software and hardware inventory of the machine. OS version, process list, network addresses, ARP tables, routing tables, connected users, mounted shares. On a real network, an snmpwalk on a well-configured server beats whoami; ip addr; ps aux; ss -tunap combined.
Version 3 fixes it all — authentication, encryption, access control. But in 2026, most switches and printers still run SNMPv1/v2c with public intact. It is an almost perfect indicator of a network's maturity: an environment where SNMP is never reachable under public has been audited. Everywhere else, it is your richest entry point.
Step 7 — Deep SMB enumeration (15 min)
Port 445 is your best friend internally. On Metasploitable 2, it is wide open and leaks nearly everything a domain can leak.
# Banner + signing
nxc smb $CIBLE_LINUX
# Shares over a null session (no credentials)
nxc smb $CIBLE_LINUX -u '' -p '' --shares > scans/20-shares.txt
# Users via RID cycling
enum4linux-ng -R $CIBLE_LINUX -oJ scans/20-rid.json
# Password policy
enum4linux-ng -P $CIBLE_LINUX > scans/20-policy.txt
# Everything into one readable file
enum4linux $CIBLE_LINUX > scans/20-smb.txt 2>&1
Deliverable: in rapport/, note:
- whether
signing: False(allows NTLM relaying) - the list of shares accessible over null session (typically
tmpwritable on Metasploitable 2) - the number of enumerated users
- the password policy (minimum length, lockout)
Step 8 — Web reconnaissance (15 min)
The web target has no exploitable vulnerability. But it leaks a lot as soon as you scratch. That is the case for half of the internal web servers you will scan on assignment.
# Application fingerprint
whatweb -a 3 http://$CIBLE_WEB > scans/12-web.txt
echo "---" >> scans/12-web.txt
# Headers and status
curl -s -I http://$CIBLE_WEB/ >> scans/12-web.txt
echo "---" >> scans/12-web.txt
# robots.txt and sitemap
curl -s http://$CIBLE_WEB/robots.txt >> scans/12-web.txt
echo "---" >> scans/12-web.txt
curl -s http://$CIBLE_WEB/sitemap.xml >> scans/12-web.txt
echo "---" >> scans/12-web.txt
# Path brute force (small targeted list)
gobuster dir -u http://$CIBLE_WEB \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-q -o scans/12-gobuster.txt
# Poke at interesting paths
for p in "/admin/" "/api/v1/" "/backup/" "/.env.example" "/package.json"; do
echo "=== $p ==="
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://$CIBLE_WEB$p
done > scans/12-paths.txt
# Retrieve .env.example and package.json
curl -s http://$CIBLE_WEB/.env.example > scans/12-env-example.txt
curl -s http://$CIBLE_WEB/package.json > scans/12-package.txt
Look at what .env.example leaks:
cat scans/12-env-example.txt
You find: the SMB server name (dev-server), the MySQL port, a read-only user (intranet_ro). Exactly the kind of artifact left behind in public repos by accident, and that hands an attacker their first list of accounts to try.
And package.json:
cat scans/12-package.txt
It exposes a precise application version and every dependency with its version. With that, you can hunt known CVEs on each dependency via nvd.nist.gov — without ever touching the target again.
Definition — silent "information disclosures", the leaks that break nothing
OWASP does not rate information disclosure in the Top 10 as critical — because on its own it compromises nothing. No shell, no exfiltrated password, no exposed customer data. A .env.example on a server is not a breach, it is a legitimate file.
But in the real life of an attacker, these leaks are the fuel for everything else. The name of an internal server quoted in a config example becomes an IP to target. A dependency list with versions becomes a set of candidate CVEs. An admin email left in an HTML comment becomes a phishing target. An nginx banner in 1.14.0 becomes a reconnaissance ticket to try that branch's CVEs.
That is why a serious audit spends time on those findings, even when they lead nowhere in isolation. The client report lists them under "information exposure", with the implicit recommendation: remove those files or hide those headers, not because they are directly exploitable, but because they save the attacker the twenty hours they would have spent guessing.
Step 9 — tcpdump capture (10 min)
No Wireshark inside Docker (no GUI). We capture with tcpdump, then open the PCAP on the host with Wireshark.
In one terminal inside the attacker container:
sudo tcpdump -i eth0 -w captures/scan-syn.pcap host $CIBLE_LINUX and tcp
In a second terminal inside the attacker container (docker compose exec attaquant bash from your host):
sudo nmap -sS -p 22,80,445,3389 10.20.30.20
Back in the first terminal, Ctrl+C on tcpdump.
Check the capture:
tcpdump -r captures/scan-syn.pcap -c 20
capinfos captures/scan-syn.pcap # if available
On your host, the file lives at labs/docker/module-04-recon-active/attaquant-home/captures/scan-syn.pcap. Open it with your host's Wireshark.
Apply the filter:
tcp.flags.syn == 1 and tcp.flags.ack == 0 and ip.dst == 10.20.30.20
Count the packets, check that the source is 10.20.30.5 (the attacker).
Deliverable: the PCAP + a Wireshark screenshot showing the filter.
Step 10 — Scapy exercise (5 min)
Write scapy_check.py in ~/labs/:
#!/usr/bin/env python3
from scapy.all import IP, TCP, sr1
CIBLES = {
"10.20.30.20": [21, 22, 23, 80, 139, 445, 3306],
"10.20.30.12": [80, 443, 22],
}
for ip, ports in CIBLES.items():
print(f"\n=== {ip} ===")
for p in ports:
r = sr1(IP(dst=ip) / TCP(dport=p, flags="S"), timeout=2, verbose=0)
if r is None:
print(f" {p}/tcp filtered")
elif r.haslayer(TCP):
flags = int(r[TCP].flags)
if flags == 0x12:
print(f" {p}/tcp open (SYN/ACK)")
elif flags & 0x04:
print(f" {p}/tcp closed (RST)")
else:
print(f" {p}/tcp ? (flags={hex(flags)})")
Run it:
sudo python3 scapy_check.py | tee scans/scapy-verification.txt
Deliverable: the output must match what Nmap said in step 3. A gap = a problem to understand — wrong interface, silent filtering, missing capability.
Step 11 — Write the report (25 min)
Open rapport/reconnaissance.md. Two pages. No more.
# Reconnaissance report — Week 4 lab (date)
## 1. Executive summary (5 lines max)
Mapped two hosts on the 10.20.30.0/24 segment. Two opposite profiles:
a very chatty legacy Linux server, and a well-configured nginx intranet
that still leaks its software inventory.
Priorities for the next phase: 1) vsftpd 2.3.4, 2) Samba usermap,
3) enumeration of MySQL accounts with no password.
## 2. Scope and method
- Network: 10.20.30.0/24, Docker bridge network, isolated.
- Attacker: Kali 10.20.30.5 (inskillsec/kali-lab container).
- Method: ARP → ping sweep → full SYN scan → targeted -sV → NSE by
category → UDP top-25 → SMB enumeration → web reconnaissance →
Scapy verification.
## 3. Results — 10.20.30.20 (Linux target, "dev-server")
- OS: Ubuntu 8.04 (SNMP + Apache banner).
- Notable TCP ports: 21, 22, 80, 139, 445, 3306, 3632, 5432, 5900, 8180…
- Vulnerabilities confirmed by NSE:
- CVE-2011-2523 (vsftpd 2.3.4 backdoor)
- CVE-2007-2447 (Samba usermap script)
- Accounts enumerated via null session: root, msfadmin, user, service…
- SNMP community "public": full inventory readable.
- /tmp share writable over null session.
- Priority 1 for exploitation: vsftpd backdoor (easy, quiet).
## 4. Results — 10.20.30.12 (web target, "intranet")
- Server: nginx 1.27.x, chatty Server header.
- Application: Intranet-App 2.3.1 (Node.js).
- No CVE directly confirmed.
- Information exposures:
- /.env.example — leaks SMB server name and MySQL ro account.
- /package.json — exposes 8 dependencies with versions.
- /admin/ and /api/v1/ exist but protected (401/403).
- robots.txt lists 17 internal paths, not all of them present,
but revealing the intended architecture.
- Priority for exploitation: hunt CVEs on the 8 dependencies from
package.json.
## 5. Plan for modules 5 to 8
1. Exploit vsftpd 2.3.4 (already used in module 01).
2. Exploit Samba usermap script (Metasploit).
3. Extract hashes of every local account.
4. Reuse extracted credentials against the intranet.
## 6. Artifacts
- scans/12-ports.nmap, scans/20-ports.nmap: full TCP scans.
- scans/12-versions.nmap, scans/20-versions.nmap: version scans.
- scans/12-nse.nmap, scans/20-nse.nmap: targeted NSE.
- scans/20-udp.nmap, scans/20-snmp.txt: UDP recon.
- scans/20-smb.txt, scans/20-rid.json, scans/20-policy.txt: SMB.
- scans/12-web.txt, scans/12-gobuster.txt, scans/12-env-example.txt: web.
- captures/scan-syn.pcap: tcpdump capture.
- scans/scapy-verification.txt: independent Scapy verification.
Self-evaluation checklist
-
scans/decouverte.txtcontains two target IPs. - Two
*-ports.nmapfiles — full TCP scans. - Two
*-versions.nmapfiles — version detection. - Targeted NSE scan per target (never
--script vulnblindly). - UDP top-25 scan on the Linux target.
-
snmpwalkoutput saved if SNMP is open. - SMB enumeration: banner + shares + users + policy.
- Web reconnaissance: whatweb + gobuster +
.env.example+package.json. -
tcpdumpcapture of a SYN scan, PCAP openable in your host's Wireshark. - Scapy script executed, output saved, consistent with Nmap.
- Two-page report following the model above.
- No command was sent outside the
10.20.30.0/24subnet.
What usually breaks
| Symptom | Likely cause | Fix |
|---|---|---|
arp-scan: permission denied | NET_RAW capability not granted | Check cap_add: in the compose, docker compose down then up -d. |
nmap -sS refused | Same — no raw socket | Same. |
tcpdump shows nothing | Wrong interface | ip -brief address inside the container to find the Docker interface. |
-sV marks unknown everywhere | Target not fully up | Wait 30 s, docker compose ps must print healthy for the target. |
| SNMP does not answer | Metasploitable 2 still warming up | Wait 30 s more, or docker compose restart cible-linux. |
enum4linux-ng very slow | Normal — many RPC calls | Be patient, it is never fast. |
| gobuster: connection refused | cible-web restarting | docker compose logs cible-web, check nginx syntax. |
| Wireshark won't open the PCAP | Host path, not container path | Grab the file at labs/docker/module-04-recon-active/attaquant-home/captures/. |
What you take away from this lab
- A scan methodology reproducible to the letter in 90 minutes, usable on any internal segment.
- A reconnaissance report you can drop, as is, into the annex of a client pentest.
- Understanding the noise cost of every Nmap option.
- The habit of capturing your own traffic — the first step toward red-team evasion.
- An ordered list of vulnerabilities to exploit in modules 5, 7, 8, 9.
- Concrete proof that a site with no exploitable vulnerability can still leak a shocking amount of information — a lesson to hammer home to your clients.
Next: the module quiz. Then we walk into the flesh of pentesting — finding and exploiting vulnerabilities.