// 2 ZERO-DAY · 2 CVE · 4 EXPLOIT IN THE LAST 24H

Python Security Toolkit: Essential Syntax and Patterns

This page assumes you've worked through the first steps in Page 1 — variables, running a .py file, and the idea that Python executes top-to-bottom. Now we need patterns you'll actually use when scripting against Lab Wintermute: pulling data from DVWA at 192.0.2.30, parsing nmap output for 192.0.2.20, handling errors without your script dying mid-scan. Keep this open in a second terminal while you work.


Core Control Flow: Decisions and Repetition

Conditionals — branch based on a test.

# Basic if/elif/else — checking a port state from scan data
port_state = "open" if port_state == "open": print(f"Port is accessible — investigate service")
elif port_state == "filtered": print(f"Firewall or ACL blocking — port may be open behind filter")
else: print(f"Port closed or host unreachable") # Common comparison operators: ==, !=, <, >, <=, >=, 'in' for membership

Why this matters: A port marked filtered is not a clean result — it is an unanswered question. Your script must handle all three states, not just open.

Loops — repeat work across targets or data collections.

# for loop — iterate over a known collection
targets = ["192.0.2.20", "192.0.2.30"]
for ip in targets: print(f"Scheduling scan for {ip}") # range() — generate number sequences, useful for port ranges
for port in range(1, 1025): # 1 to 1024 inclusive # scan logic here pass # while loop — repeat while condition holds, with explicit exit
attempt = 0
max_attempts = 3
while attempt < max_attempts: # retry logic here attempt += 1 # increment to avoid infinite loop

enumerate() — get index and value together, essential for numbered output.

services = ["ssh", "http", "ftp"]
for idx, svc in enumerate(services, start=1): print(f"{idx}. {svc}")
# Output: 1. ssh, 2. http, 3. ftp

Storing and Structuring Data: Lists and Dictionaries

Lists — ordered, mutable sequences. Use for ordered scan results or target queues.

# Creating and modifying
open_ports = [22, 80, 443]
open_ports.append(3306) # add to end
open_ports.insert(0, 21) # insert at position # Accessing by position (zero-indexed)
first_port = open_ports[0] # 21
last_port = open_ports[-1] # 3306 # Slicing — extract subsets
low_ports = open_ports[:3] # first three elements

Dictionaries — key:value pairs. Use for structured host data: IP maps to findings.

# Wintermute scan result storage
host_findings = { "192.0.2.20": { "os": "Linux 2.6", "open_ports": [21, 22, 23, 80], "services": {21: "ftp", 22: "ssh", 23: "telnet", 80: "http"} }, "192.0.2.30": { "os": "Unknown", "open_ports": [80, 443], "services": {80: "http", 443: "https"} }
} # Accessing nested data
metasploitable_services = host_findings["192.0.2.20"]["services"]
print(metasploitable_services[80]) # "http" # Safe access with .get() — returns None instead of crashing if key missing
dvwa_os = host_findings.get("192.0.2.30", {}).get("os")

In plain terms: lists are for "here's a sequence of things," dictionaries are for "here's what I know about this specific thing."


File I/O: Reading and Writing Script Data

# Reading a file of target IPs — one per line
with open("wintermute_targets.txt", "r") as f: targets = f.read().splitlines() # removes trailing newlines # Writing structured output — append mode preserves previous runs
with open("scan_results.txt", "a") as f: f.write(f"192.0.2.20: ports {open_ports}\n") # CSV-style parsing for tool output
line = "192.0.2.20,80,open,Apache httpd 2.2.8"
parts = line.split(",") # ['192.0.2.20', '80', 'open', 'Apache httpd 2.2.8']
ip, port, state, banner = parts # unpack into variables

String Handling: Parsing and Encoding Security Data

Formatting — three approaches, f-strings preferred for readability.

target = "192.0.2.20"
port = 80 # f-string (Python 3.6+) — most readable for security scripts
url = f"http://{target}:{port}/dvwa/login.php" # .format() — older, still seen in inherited code
url = "http://{}:{}/dvwa/login.php".format(target, port) # % formatting — legacy, avoid in new code
url = "http://%s:%s/dvwa/login.php" % (target, port)

Encoding and Decoding — network data arrives as bytes; you need text.

import base64 # Bytes to text — specify encoding explicitly
raw_bytes = b"user:pass"
text = raw_bytes.decode("utf-8") # "user:pass"
back_to_bytes = text.encode("utf-8") # b"user:pass" # Base64 — common in HTTP Basic Auth headers
credentials = "admin:password"
encoded = base64.b64encode(credentials.encode("utf-8"))
# Result: b'YWRtaW46cGFzc3dvcmQ=' decoded = base64.b64decode(encoded).decode("utf-8")
# Back to: "admin:password"

⚠️ Authorized, defensive use only. Encoding transforms data; it does not protect it. Base64 is not encryption — anyone can reverse it. You'll see it in HTTP headers during legitimate traffic analysis in Lab Wintermute.


Error Handling: Keeping Scripts Alive Mid-Engagement

A script that crashes on the third host of fifty wastes your lab time and leaves gaps in documentation.

import requests # Basic try/except — catch specific exceptions, not all
try: r = requests.get("http://192.0.2.30/dvwa/", timeout=5) print(r.status_code)
except requests.exceptions.ConnectionError: print("Host unreachable — check VM network or snapshot state")
except requests.exceptions.Timeout: print("Request timed out — target may be overloaded or filtered")
except requests.exceptions.RequestException as e: print(f"Unexpected error: {e}") # finally block — runs regardless, useful for cleanup
finally: print("Scan attempt logged for report")

Common pattern: wrap the entire per-target loop body in try/except, so one bad target doesn't kill the batch.


Running External Tools: subprocess

The subprocess module (standard library since Python 2.4, no installation needed) calls system tools and captures output. It replaces older, less safe functions like os.system().

import subprocess # SAFE pattern: list of arguments, shell=False (default)
# Runs nmap against Metasploitable2; returns bytes
result = subprocess.check_output( ["nmap", "-sV", "-p", "22,80", "192.0.2.20"], stderr=subprocess.DEVNULL, # suppress error noise timeout=60 # don't hang indefinitely
) # Decode bytes to string for parsing
output_text = result.decode("utf-8")
print(output_text) # If command fails, CalledProcessError is raised with .returncode and .output

Why this matters: shell=True lets you write nmap -sV 192.0.2.20 as a single string, but it also lets shell metacharacters in variables execute arbitrary commands. In Lab Wintermute that's low risk; in production with user input, it's catastrophic. Build the list-of-arguments habit now.

Parameter Purpose Safe Default
shell=False Execute via OS directly, not shell interpreter ✓ Keep False
stderr Where to send error stream; DEVNULL discards, STDOUT merges Varies by need
timeout Seconds before aborting hung command Always set
universal_newlines / text=True Return string not bytes; text=True is Python 3.7+ Check your Python version

HTTP Requests with the requests Library

Not in standard library — install once per environment:

python -m pip install requests
import requests # GET request to DVWA — basic call
r = requests.get("http://192.0.2.30/dvwa/login.php", timeout=10) # Response inspection
print(r.status_code) # HTTP result: 200, 404, 500, etc.
print(r.headers.get("Server")) # Web server banner, if disclosed # Text vs. binary content
print(r.text[:200]) # Decoded string using r.encoding
print(r.content[:50]) # Raw bytes — check before decoding # Encoding control — force if detection fails
r.encoding = "utf-8" # or "ISO-8859-1" for legacy apps
print(r.text) # JSON parsing — DVWA API endpoints, or tool output
# r.json() raises exception if response isn't valid JSON
try: data = r.json()
except ValueError: data = None # Not JSON, handle accordingly

Quick Reference: Built-ins and Type Conversions

Function Typical Security Use
len(collection) Count open ports, response length anomalies
range(start, stop) Iterate port numbers, generate test sequences
enumerate(iterable, start=1) Numbered output for reports
int(string) Convert port string "80" to integer 80
str(number) Convert port integer to string for concatenation
bytes(string, encoding) Prepare data for network transmission
help(object) Inspect module or function in interactive Python
# help() in practice — when you forget a method
help(requests.get) # shows signature and docstring
help(subprocess.check_output) # verify parameters before running

Putting It Together: Minimal Wintermute Recon Script Skeleton

#!/usr/bin/env python3
import subprocess
import sys TARGET = "192.0.2.20"
PORTS = [21, 22, 23, 25, 80, 443] def scan_port(target, port): """Run minimal TCP probe, return parsed result or None.""" try: # -Pn: skip host discovery (we know it's up in lab) # -p: single port cmd = ["nmap", "-Pn", "-p", str(port), "--open", target] result = subprocess.check_output(cmd, timeout=30) return result.decode("utf-8") except subprocess.CalledProcessError: return None # port closed or filtered except subprocess.TimeoutExpired: return "TIMEOUT" # Main loop with structured storage
findings = {}
for port in PORTS: output = scan_port(TARGET, port) findings[port] = output print(f"Port {port}: {'OPEN' if output else 'closed/filtered'}") # findings dictionary now holds everything for your report

Cross-Reference to Coming Pages

  • Page 5 (Reconnaissance): Expands the nmap wrapper above into full host discovery.
  • Page 6 (Web Applications): Deepens requests usage for DVWA form interaction and session handling.
  • Page 9 (Malware): Encoding patterns here apply to payload analysis — safely, without execution.

Samuel's note from the bench: I keep a wintermute_utils.py in every lab session folder with the scan_port() pattern above, modified per exercise. Reusing your own tested code beats rewriting from scratch — but only if you understand what it does. Copy-paste without comprehension produces scripts that fail silently at 2 AM when your snapshot rollback doesn't work.

Further reading