Python › Module 2 › Lesson 4
Lab — Build a Port Scanner
Build and run a localhost-only port scanner with copy-paste Python you can extend
Opening
Lab rule: HOST is the string 127.0.0.1 in the file — not argv, not the café, not a pasted cloud IP.
Lessons 1–3 gave you connect_ex, timeout, GET/HEAD ethics, and a scanner design. This lab is the implementation on YOUR machine only. You will copy a short Python script that hardcodes HOST = "127.0.0.1", walks a tiny port list (22, 80, 443, 8000, 3306), uses a sub-second timeout, and prints OPEN versus closed_or_filtered. Then you write the same results to $HOME/cyberlium-lab/localhost-scan.txt and chmod 600. If every port is closed, that is a valid run — many laptops listen on none of those. Optional: in a second terminal, on THIS machine, start python -m http.server 8000 and scan again so 8000 reports OPEN. You will not change HOST to a LAN address to “see more.” You will not nmap the school. You will not scan café Wi-Fi, an ISP, or random internet hosts. Next is the module quiz, then automation. Scope stays loopback until a later course with written authorization says otherwise.
1. What you are measuring: TCP connect from this process to YOUR loopback, five doors only
Each port is one question you already practiced: connect_ex((127.0.0.1, port)) with settimeout. 0 means OPEN from here — a process on this OS accepted TCP. Non-zero means closed_or_filtered in the wait window. Port 22 often closed unless you installed SSH on this box. 80 and 443 closed unless you run a local web server. 3306 closed unless you chose to run MySQL locally. 8000 closed until you start Python’s http.server — that is the intended optional OPEN. The script must not take the host from the command line in this lab. A SAFETY flag you later forget is how yesterday’s “just this once” becomes a scan of a public IP. Hardcode the IPv4 loopback. Print it on the first line of output so the artifact is self-explaining.
Timeout stays small (0.5 seconds is plenty on localhost). A hang means you dropped settimeout; fix the script, do not widen the target. Do not expand PORTS to 1–65535 for a screenshot. Do not add banner grabbing against anything but a listener you started, and even then it is optional — the required artifact is the five-line result file. Do not run this as root to “see more ports”; connect to high ports on loopback does not need uid 0, and root on a shared box is a different lesson’s hazard.
2. Create one OPEN on purpose: python -m http.server 8000 on THIS machine, then scan again
In a second terminal, in a throwaway directory you own, run python -m http.server 8000 (or python3 / py -3). That binds a simple HTTP listener — typically all interfaces or a local one, port 8000. It is YOUR process. Leave it running, run the scanner, confirm 8000 OPEN, then stop the server with Ctrl+C. Do not point http.server at someone else’s files. Do not expose it as a public demo on a VPS for this lab. Loopback scan will see OPEN even if the bind is 0.0.0.0:8000 because you are connecting to 127.0.0.1, which reaches local listeners. If 8000 stays closed, you started the server on a different machine or a different port — debug YOUR terminals, do not pick a LAN host.
You may GET http://127.0.0.1:8000 with requests from lesson 2 if you want to see HTTP 200 from the same listener. That is still your box. You still do not crawl, do not POST logins, do not SQLi. The scanner lab’s success criterion is the text file, not a vulnerability report.
3. The artifact: localhost-scan.txt under cyberlium-lab, mode 600 — evidence, not a trophy of strangers
mkdir -p "$HOME/cyberlium-lab" (Git Bash/WSL/macOS/Linux; on native Windows PowerShell create the folder under your user profile). Write date, whoami, the hardcoded HOST, each port and state, and a one-line ethics note that you did not scan a LAN. chmod 600 so other local accounts do not read it. The file may list OPEN 8000. It must not list a classmate’s IP, a café gateway, passwords, or nmap XML. Empty files fail. World-writable 777 fails the hygiene habit from earlier topics. If chmod is missing, restrict the file in your profile or use WSL.
Extending after the lab (still ethical): add a port to the list that YOU listen on; add a comment; keep HOST frozen. Removing the hardcode to “support any host” is how this script becomes an accident. If a future authorized pentest needs a scanner, you will have paperwork, a scope document, and usually a professional tool — not this five-port homework file aimed at a hotel Wi-Fi.
4. Wrong vs right: rewriting HOST vs running the locked script and locking the notes
Worked failure — treating the lab as a reason to scan a network. Evidence is localhost-scan.txt, not nmap of a /24.
Wrong
HOST = "192.168.1.1" or sys.argv[1] pointed at café/school/ISP/cloud. PORTS = range(1, 65536). nmap -p- because Python is slow. Leave http.server running on a public VPS. chmod 777 the notes. Paste live passwords. Banner-grab SSH on a company jumphost. Skip the output file and screenshot a neighbor’s router UI instead. All of that is out of scope and some of it is illegal. This course does not teach stealth scans or login brute force on OPEN ports.
Right
Save port_scan.py with HOST hardcoded "127.0.0.1", PORTS [22, 80, 443, 8000, 3306], TIMEOUT 0.5, connect_ex, close. Run it. Optionally start python -m http.server 8000 on THIS machine and rerun. Write results to $HOME/cyberlium-lab/localhost-scan.txt and chmod 600. Confirm 8000 OPEN only if you started the server. Next: Quiz — Python Networking.
5. Hands-on: copy port_scan.py, scan loopback, write localhost-scan.txt
Work top to bottom. Create the lab directory first so redirection has a place to land. Paste the Python unchanged. Run python port_scan.py (or python3 / py -3). Then either append the printed lines into localhost-scan.txt or let the script’s file-write section do it. Re-run after http.server if you want an OPEN. Read the file back. Stop the optional server. Do not add live internet targets because the closed ports felt short.
port_scan.py — HOST hardcoded 127.0.0.1, tiny list, timeout
# LOCALHOST LAB ONLY. Do not change HOST. Do not scan café / ISP / school / internet.
import socket
from datetime import datetime, timezone
from pathlib import Path
HOST = "127.0.0.1" # hardcoded loopback — never argv, never a pasted public IP
PORTS = [22, 80, 443, 8000, 3306]
TIMEOUT = 0.5
def is_open(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(TIMEOUT)
try:
return s.connect_ex((host, port)) == 0
finally:
s.close()
lab = Path.home() / "cyberlium-lab"
lab.mkdir(parents=True, exist_ok=True)
out = lab / "localhost-scan.txt"
print(f"Scanning HOST={HOST} ports={PORTS} timeout={TIMEOUT}")
rows = []
for port in PORTS:
state = "OPEN" if is_open(HOST, port) else "closed_or_filtered"
row = f"{port:5} {state}"
print(row)
rows.append(row)
header = [
"=== localhost TCP connect scan ===",
f"date: {datetime.now(timezone.utc).isoformat()}",
f"HOST: {HOST}",
"PORTS: 22 80 443 8000 3306",
f"TIMEOUT: {TIMEOUT}",
"ethics: loopback only — did not scan LAN/cafe/ISP/school/internet",
"",
]
out.write_text("
".join(header + rows) + "
", encoding="utf-8")
print(f"wrote {out}")
print("Then chmod 600 that file (Git Bash/WSL) or restrict it in your Windows profile.")
# NEVER: change HOST to 192.168.x.x, a cafe gateway, an ISP, a school, or a random host
# NEVER: PORTS = list(range(1, 65536))
# NEVER: nmap, masscan, SYN stealth, login brute on OPEN 22Write localhost-scan.txt and chmod 600; optional http.server 8000
# After port_scan.py writes ~/cyberlium-lab/localhost-scan.txt, lock it. # Git Bash / WSL / macOS / Linux: chmod 600 "\$HOME/cyberlium-lab/localhost-scan.txt" # Windows without chmod: WSL/Git Bash, or restrict the file in your profile. # Optional OPEN on 8000 — OTHER terminal, YOUR machine only: # python -m http.server 8000 # Then rerun port_scan.py; 8000 should read OPEN. Ctrl+C stops the server. # NEVER: nmap a classmate, cafe AP, ISP gear, school subnet, or random internet host # NEVER: store passwords or cookies in the notes file
Mission: localhost-scan.txt (mode 600) from a hardcoded 127.0.0.1 scan
1) Save port_scan.py with HOST = "127.0.0.1" (not argv), PORTS 22/80/443/8000/3306, TIMEOUT 0.5, connect_ex. 2) Run it against loopback. Optional: start python -m http.server 8000 in another terminal ON THIS MACHINE so 8000 is OPEN, then scan again and stop the server. 3) Write date, HOST, and per-port results to $HOME/cyberlium-lab/localhost-scan.txt and chmod 600. Do not scan a LAN, café, ISP, school, or random internet host. Do not brute OPEN ports.
Stuck? Ask Cyberlium AI Mentor
If all five ports are closed and that feels like a broken lab, ask for a hint — not a LAN target. Try: "Hint only: why is a closed-only result valid on 127.0.0.1, how does python -m http.server 8000 on MY machine create one OPEN, and why must HOST stay hardcoded?" You still fill localhost-scan.txt. No nmap, no café Wi-Fi, no school /24.
You ran a real connect scanner without leaving loopback: hardcoded 127.0.0.1, five ports, timeout, optional local http.server for one OPEN, results locked in localhost-scan.txt mode 600. That is the whole skill plus the whole legal line. Next — Quiz — Python Networking — ten APPLY items on sockets, requests, and responsible scanning, then Module 3 opens with Automating Security Tasks.
Knowledge Check
APPLY: port_scan.py reports closed_or_filtered on 22, 80, 443, 8000, 3306. A classmate says change HOST to the café gateway. Correct lab move?
Multiple choice
Knowledge Check
APPLY: True or False: is_open() returning True means connect_ex == 0 to 127.0.0.1, and you should then brute SSH if port 22 is OPEN.
True or False
Knowledge Check
APPLY: Why chmod 600 on $HOME/cyberlium-lab/localhost-scan.txt, and what must the file not contain?
Multiple choice