Learning Goals
3 minBy the end of this lesson you can:
- Explain what a port scan determines and how (connect vs. SYN scans).
- Interpret port states: open, closed, filtered.
- State clearly what's legal and what's a crime — and why.
- Frame scanning as a defensive tool: auditing your own attack surface.
Warm-Up · Checking Which Doors Are Open
5 minRecall from Lesson 3: a port is a numbered door, and a service listening on it is an open door. A port scan walks down the building checking which doors are open. To a defender that's a self-audit ("why is door 6379 open?"); to an attacker it's the first step of an intrusion — which is exactly why scanning others is treated so seriously by law.
Port scanning is active reconnaissance: unlike Lesson 6's passive recon, it sends packets to the target. That single fact changes everything legally. Scanning a host you don't own or lack written permission for is unauthorized access in many jurisdictions — even if you change nothing. So this lesson is concept + ethics first; next lesson you build a scanner that only ever points at your own lab.
New Concept · How Scanning Works & The Law
14 minWhat a scan determines
For each port, a scan figures out its state by how the target responds to the TCP handshake (Lesson 3):
OPEN a service is listening → handshake completes (SYN-ACK) CLOSED nothing listening → connection refused (RST) FILTERED a firewall drops the packet silently → no response (timeout)
An open port means a service is reachable there; combined with banner-grabbing (Lesson 7) it can reveal what service and version — which tells an attacker what to target, and tells a defender what to lock down or patch.
Scan types
- TCP connect scan — complete the full handshake (what
socket.connectdoes). Simple, no special privileges, but noisy (logged by the target). This is all we'll build. - SYN ("half-open") scan — send SYN, get SYN-ACK, then never complete; stealthier and faster. Needs raw-socket/root privileges and tools like nmap. We'll discuss but not weaponise it.
- UDP scan — harder (no handshake); slow and unreliable.
⚠️ The legal line — read this twice
Port scanning sends packets to the target, so it's "access" in the eyes of laws like the US Computer Fraud and Abuse Act, the UK Computer Misuse Act, and Malaysia's Computer Crimes Act 1997. People have been prosecuted for scanning systems they had no permission to touch — even without breaking in or changing anything. Intent and curiosity are not defences. The penalty for getting this wrong is real: fines and prison.
Where scanning is legal
✓ your own machine (127.0.0.1) and your own LAN devices ✓ VMs/containers you created, on an isolated lab network ✓ a signed penetration-test engagement (explicit, scoped, in writing) ✓ a bug-bounty program — ONLY within its published scope and rules ✓ purpose-built practice targets (e.g. scanme.nmap.org, which EXPLICITLY permits scanning; CTF/lab ranges like HackTheBox, TryHackMe) ✗ anything else — a stranger's server, a company you don't work for, "just to see" — is unauthorized and potentially criminal.
The defender's use: self-audit
The most valuable scan you'll ever run is against your own systems. It answers: which ports are exposed? Are any that shouldn't be (a database, a debug server, an old service)? Does the reality match my intended attack surface (Lesson 2)? Regular self-scans catch misconfigurations (Lesson 34) before attackers do. That's the framing for everything you build next lesson.
Being a good citizen even when authorised
- Rate-limit — a fast scan can look like an attack and stress the target.
- Scope tightly — only the ports/hosts agreed; never "wander."
- Log what you do — a record of authorised activity protects you.
- Report responsibly — findings go to whoever can fix them, privately.
Worked Example · Interpret Scan Results (localhost)
12 minGoal: a single-port probe against 127.0.0.1 that classifies the state (open/closed/filtered) — the building block of next lesson's scanner, framed as reading your own machine.
import socket def port_state(host: str, port: int, timeout: float = 1.0) -> str: """Classify a TCP port. LOCALHOST / authorised hosts only.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(timeout) result = s.connect_ex((host, port)) # 0 = connected if result == 0: return "open" elif result in (111, 61, 10061): # ECONNREFUSED (varies by OS) return "closed" # something actively refused else: return "filtered/closed" # timeout/drop → maybe firewalled HOST = "127.0.0.1" # your own machine — the only target this lesson for port, label in [(3000, "dev server"), (5432, "PostgreSQL"), (22, "SSH"), (9, "discard")]: print(f"{port:5} {label:12} → {port_state(HOST, port)}")
3000 dev server → open 5432 PostgreSQL → closed 22 SSH → closed 9 discard → filtered/closed
Read the result
The state classification turns raw connection results into the security picture from the concept section: open = a reachable service (audit it — should it be exposed?), closed = refused (nothing listening), filtered = no answer (often a firewall dropping packets). Run defensively against your own machine, this immediately surfaces questions like "is my dev database open?" Note the hard-coded HOST = "127.0.0.1" — the ethics aren't optional, they're built into the code. Next lesson turns this into a full, responsible scanner.
Try It Yourself
13 minRun port_state against your own machine for a few ports while a dev server is running. Identify one open, one closed, and (if you have a firewall) one filtered. Explain what each means defensively.
For each, decide legal or crime and name the rule: (a) scanning scanme.nmap.org (which explicitly invites it); (b) scanning your employer's server without being on the security team; (c) scanning your own NAS; (d) scanning IPs "at random to learn."
Check
(a) legal — explicit permission. (b) crime — no authorization, even as an employee. (c) legal — you own it. (d) crime — no authorization for any of them. Curiosity ≠ permission.
Write assert_authorized(host) that refuses to proceed unless the host is in an explicit allow-list (your localhost/lab IPs, or scanme.nmap.org). Make it raise with a clear message otherwise. You'll bolt this onto next lesson's scanner so it physically can't target unauthorized hosts.
Hint
ALLOWED = {"127.0.0.1", "localhost", "scanme.nmap.org", "192.168.56.10"} # my lab VM def assert_authorized(host: str) -> None: if host not in ALLOWED: raise PermissionError( f"Refusing to scan '{host}': not in the authorised allow-list. " f"Only scan hosts you own or have WRITTEN permission for.")
Mini-Challenge · Draft a Scan Authorization Form
8 minProfessionals never scan without written authorization. Draft a one-page "scan authorization" template a client would sign before a pentest: target IPs/ranges, allowed ports, time window, rate limits, out-of-scope systems, point of contact, and a stop condition. This is the document that makes scanning legal.
Show an example template
SCAN AUTHORIZATION (must be signed before any active scanning)
Client / system owner: ____________________
Authorised targets: e.g. 203.0.113.0/28 (ONLY these)
Out of scope: everything else, incl. 203.0.113.5 (prod DB)
Allowed actions: TCP connect scan, service/version detection
NO exploitation, NO DoS, NO data exfiltration
Time window: 2026-06-01 02:00–04:00 (low-traffic)
Rate limit: <= 50 packets/sec
Point of contact: name + phone, reachable during the window
Stop condition: halt immediately if any service is disrupted
or if asked by the contact
Authorised by (sign): ____________________ Date: __________Non-negotiables: explicit in-scope + out-of-scope, allowed actions, time window, rate limit, contact, and a stop condition.
Recap
3 minA port scan sends packets to a target to learn which ports are open (service listening), closed (refused), or filtered (firewalled/dropped) — it's active recon, fundamentally different from passive (Lesson 6). Because it touches the target, scanning a host you don't own or lack written authorization for is potentially a crime — intent and "no harm done" are not defences. Legal scanning is your own systems, scoped engagements, bug-bounty in scope, and purpose-built practice targets like scanme.nmap.org. The best use is defensive: self-audit your attack surface. Next lesson you build a scanner with an authorization allow-list baked in.
Vocabulary Card
- port scan
- Probing a host to determine which ports are open/closed/filtered.
- active recon
- Reconnaissance that sends packets to the target (vs. passive).
- port state
- open (listening), closed (refused), or filtered (dropped/firewalled).
- scan authorization
- The written, scoped permission that makes active scanning legal.
Homework
4 minWrite assert_authorized with your allow-list and the state-classifier, and run a self-audit of your own machine: list open ports, what's listening, and whether each should be open. Draft your scan authorization template. Write a paragraph, in your own words, explaining to a friend why scanning a random internet host "just to learn" is illegal and what to do instead (practice targets, your own lab).
Sample · why "just to learn" isn't a defence
Scanning sends packets to someone else's computer without their permission. Laws like the CFAA / Computer Misuse Act / Computer Crimes Act treat that as unauthorized access — the same category as trying door handles on a stranger's house. It doesn't matter that I didn't break in or take anything; "access without authorization" is the offence, and "I was curious / learning" has been rejected by courts. People have been charged for exactly this. What to do instead: scan my OWN machines and lab VMs (I own them), and use targets that EXPLICITLY invite scanning — scanme.nmap.org, or training ranges like TryHackMe/HackTheBox. Same learning, zero legal risk. If I ever want to test someone else's system, I get it in writing first (a signed scan authorization).
Non-negotiables: working allow-list + self-audit, a scan-authorization draft, and a clear lay explanation of the legal line and the legal alternatives.