Foundations of Ethical Security Testing with Python: A Beginner's Laboratory Guide
Chapter 5 of 11 · updated Jul 08, 2026
Reconnaissance and Network Mapping: Understanding the Attack Surface
Reconnaissance and Network Mapping: Understanding the Attack Surface
Let me tell you how I actually run this phase in Lab Wintermute. After four pages of setup and fundamentals, you finally get to touch the target — but "touch" is the operative word, and its legal weight is something I hammer into every student. Reconnaissance (recon for short: the information-gathering phase before any directed attack) splits into two modes that sit on opposite sides of a bright ethical line. Passive recon means observing information the target already publishes — DNS records, WHOIS data, job postings mentioning technologies, even photos of server racks on social media. You never send a packet to their infrastructure. Active recon means your packets actually reach the target's interfaces: pings, port scans, direct connections. In our lab, the scope agreement explicitly authorizes active recon against 192.0.2.20 and 192.0.2.30. In the real world, active recon without written authorization is legally indistinguishable from attack preparation in most jurisdictions. I keep a signed scope document in every lab folder; you should too.
⚠️ Authorized, defensive use only. Every technique below is framed for authorized security assessment. Never direct these against systems you do not own or have explicit written permission to test.
The TCP Three-Way Handshake: Why Scanners Work
Before we write Python that pokes at ports, you need to see what actually crosses the wire. TCP, defined in RFC 793 from September 1981, was built for reliable process-to-process communication across interconnected networks. The mechanism that makes this reliability possible is also what makes port scanning possible: the three-way handshake.
Here's the sequence. Your machine sends a SYN packet (synchronize, with a random starting sequence number). If the target port is open and listening, it replies SYN-ACK (synchronize-acknowledge, with its own sequence number plus an acknowledgment of yours). Your machine then sends ACK, and the connection is established. If the port is closed, RFC 793 says the target must respond with RST (reset). If a firewall drops the packet, you get silence — no RST, no SYN-ACK, just timeout.
In plain terms: the handshake is like knocking on a door. Open door? Someone answers. Locked door? Someone yells "go away." Door hidden behind a soundproof wall? Nobody answers, and you stand there looking foolish until you give up.
This behavior is why our simple TCP connect scanner works. Python's socket.socket(socket.AF_INET, socket.SOCK_STREAM) creates a TCP socket, and sock.connect((target_ip, target_port)) implicitly triggers the full three-way handshake. We don't craft raw SYN packets here — that's a more advanced technique not covered in our sources — but we leverage what the operating system's TCP stack does automatically.
Why this matters: A "stealth" SYN scan (sending only SYN, never completing the handshake) requires raw socket privileges and specific Nmap flags. Our Python scanner uses full connect scanning, which is slower and noisier but requires no special permissions and teaches the underlying mechanics transparently. In Lab Wintermute, we accept the noise because we're learning, not evading detection.
Building the Scanner: Python Against 192.0.2.20
From Page 2, you already have Python installed and understand variables, loops, and basic socket operations. Now we assemble the pieces into our first active recon tool. We're scanning Metasploitable2 at 192.0.2.20, ports 1-1024 — the "well-known ports" range where standard services traditionally listen. (Specific port-to-service mappings aren't in our sources; you'll learn common assignments like 22/SSH, 80/HTTP, 445/SMB through practice and supplementary references.)
The scanner logic: iterate ports, attempt connection, record result, close socket. The timeout is critical — without it, a filtered port hangs your script. Note that settimeout() behavior varies across Python implementations; our sources note that in some embedded contexts like CircuitPython 8.2.2, TCP connection timeout may be fixed regardless of your setting. Standard CPython on Kali Linux behaves as expected, but always test.
#!/usr/bin/env python3
# tcp_connect_scanner.py — Lab Wintermute authorized scan only
import socket TARGET = "192.0.2.20"
START_PORT = 1
END_PORT = 1024
TIMEOUT_SECONDS = 1 # aggressive for lab; adjust for reliability def scan_port(target_ip, port): """Attempt TCP connection, return state string.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(TIMEOUT_SECONDS) try: sock.connect((target_ip, port)) # If we reach here, three-way handshake succeeded return "open" except socket.timeout: return "filtered" # no response, likely firewall drop except ConnectionRefusedError: return "closed" # target sent RST per RFC 793 finally: sock.close() def main(): print(f"[*] Starting TCP connect scan: {TARGET} ports {START_PORT}-{END_PORT}") results = {"open": [], "closed": [], "filtered": []} for port in range(START_PORT, END_PORT + 1): state = scan_port(TARGET, port) results[state].append(port) if state == "open": print(f"[+] Port {port}/tcp open") print(f"\n[*] Scan complete") print(f" Open: {len(results['open'])} | Closed: {len(results['closed'])} | Filtered: {len(results['filtered'])}") if __name__ == "__main__": main()
Run it from your Kali workstation (192.0.2.10). Expect minutes, not seconds — full connect scans are slow. The output will show open ports; closed and filtered counts aggregate at the end.
Why this matters: A port marked filtered is not a clean result — it is an unanswered question. In production assessments, filtered ports demand follow-up: different scan types, source port manipulation, or simply documenting the uncertainty. Never report "filtered" as "secure"; report it as "unresolved."
Service Banners and Version Detection
An open port tells you where to talk; the banner often tells you what you're talking to. Many services — SSH daemons, web servers, FTP servers — send a text greeting upon connection. This is version detection's first pass, and it's critical for defensive work: knowing what software runs where lets you map against known vulnerabilities.
Python banner grabbing extends our scanner:
def grab_banner(sock, port): """Attempt to read service banner after successful connect.""" try: # Some services send banner immediately; others require protocol-specific probe banner = sock.recv(1024).decode('utf-8', errors='replace').strip() return banner except socket.timeout: return "[no banner received]"
In practice, banner grabbing is finicky. Protocols differ: SSH sends a version string unprompted; HTTP requires you to send a request first; others are binary. For Lab Wintermute, manual inspection with nc (netcat) often teaches more than automation:
# Connect to SSH banner — banner typically sent immediately
nc -v 192.0.2.20 22 # HTTP requires speaking the protocol
echo -e "HEAD / HTTP/1.0\r\n\r\n" | nc 192.0.2.20 80
The banner feeds vulnerability research: product name and version lead to CVE lookups, vendor advisories, and patch status verification. This is defensive gold — you're building an accurate asset inventory, not just an attack map.
Nmap Integration: Calling the Standard from Python
Our Python scanner teaches mechanics, but Nmap is the industry standard for good reason. Its version detection engine, script library, and output formats are battle-tested. Nmap's license is a custom license based on (but not compatible with) GPLv2; for our purposes, we use the binary distribution on Kali Linux.
Nmap supports XML output via -oX, which we can parse programmatically. The command nmap -oX - target sends XML to stdout (dash as filename), perfect for piping into Python. The DTD defining the XML structure lives at https://svn.nmap.org/nmap/docs/nmap.dtd.
# Representative scan: OS detection, version detection, scripts, traceroute
# -A enables aggressive detection; -T4 sets timing template (faster than default)
nmap -A -T4 -oX wintermute_192.0.2.20.xml 192.0.2.20 # For stdout XML piping to Python
nmap -oX - 192.0.2.20
Parsing in Python uses the standard library — no external dependency needed:
#!/usr/bin/env python3
# parse_nmap_xml.py
import xml.etree.ElementTree as ET
import sys def parse_nmap_xml(xml_source): """Extract host, port, state, service info from Nmap XML.""" tree = ET.parse(xml_source) root = tree.getroot() for host in root.findall('host'): address = host.find('address').get('addr') ports = host.find('ports') if ports is None: continue for port in ports.findall('port'): portid = port.get('portid') state = port.find('state').get('state') service = port.find('service') svc_name = service.get('name', 'unknown') if service is not None else 'unknown' product = service.get('product', '') if service is not None else '' version = service.get('version', '') if service is not None else '' print(f"{address}:{portid} {state} {svc_name} {product} {version}".strip()) if __name__ == "__main__": parse_nmap_xml(sys.argv[1])
Why this matters: Structured output transforms recon from artisanal craft into repeatable process. Your Lab Wintermute report will include parsed Nmap data; your future employer will expect it.
Nmap's XML includes product, version, and extrainfo attributes from version detection, with confidence levels: level 3 for table-lookup guesses (common port associations), level 10 for confirmed version detection. Treat lower-confidence results as leads, not verdicts.
Defensive Perspective: Seeing the Scan from the Other Side
Every offensive technique maps to a defensive control. Here's how the target side experiences our activity, and how defenders respond.
| Offensive Action | Target-Side Observable | Defensive Control |
|---|---|---|
| TCP connect scan (ports 1-1024) | Rapid sequential connection attempts from single source | Network IDS/IPS: Snort (open-source network intrusion detection system) identifies covert port scans by pattern — threshold of SYNs without completing handshakes, or full connects to many ports. Snort examines each packet for suspicious irregularities. |
| Banner grabbing | Valid application connections, possibly logged | Service hardening: Disable banners or return generic strings; configure application logging of all connections |
| Version detection (Nmap -sV) | Probes to open ports with unusual patterns | Honeypots: Decoy systems that emulate services, log interaction, waste attacker time; not in our sources but widely deployed |
| Scan from single IP | Obvious source attribution | Network segmentation: Isolate critical assets; scan from one subnet shouldn't reach another without passing controlled chokepoints |
| Repeated reconnaissance | Persistent pattern over time | Correlation: SIEM (Security Information and Event Management, log aggregation and analysis platform) rules flag multiple recon indicators from same source |
Specific logging notes: our Python scanner's full connects appear as standard TCP connections in system logs — /var/log/auth.log for SSH, web server access logs for HTTP. Nmap's service probes may trigger application-level logging of malformed or unexpected requests. On Metasploitable2, examine /var/log/syslog after your scan; you'll see the noise you generated.
Port knocking — dynamically opening firewall ports based on a secret sequence of connection attempts — isn't mentioned in our sources but represents an additional layer: ports appear closed until the correct "knock" sequence arrives, making simple scans misleading.
Lab Wintermute Checklist: Reconnaissance Phase
Before proceeding to Page 6's web application mapping, verify:
- [ ] Python scanner executed against 192.0.2.20 with timeout handling
- [ ] Nmap XML output generated and parsed successfully
- [ ] Service banners manually verified for at least 3 open ports
- [ ] Metasploitable2 logs reviewed to identify scan artifacts
- [ ] Documented: open ports, services, versions, and confidence levels
- [ ] Defensive controls identified for each observed exposure
Snapshot your VMs before continuing. Reconnaissance is the foundation; everything that follows in Pages 6-10 builds on this map. A sloppy scan means blind exploitation attempts — inefficient, noisy, and poor tradecraft even in authorized labs.