Python For Cybersecurity: Write Your First Security Tools And Scripts

Python for cybersecurity
Python for cybersecurity
By HOC Team  |  Updated: August 2026   Read time: ~28 min

Python is the unofficial language of cybersecurity. Walk into any red team operation, blue team SOC, or bug bounty hunter's workspace and you will find Python scripts: port scanners, banner grabbers, packet sniffers, log analysers, and automation frameworks.

Scapy, Impacket, Volatility, sqlmap, and the Shodan API client are all Python. Security professionals who know Python move faster, build custom tools for specific targets, and automate the repetitive parts of assessments that manual processes cannot keep up with.

This guide is entirely hands-on. You will build ten working security tools from scratch, each explained line by line: a threaded TCP port scanner, a service banner grabber, a subdomain enumerator, a web directory brute-forcer, an SSH brute-forcer with Paramiko, a Scapy packet sniffer, a log analyser, a hash cracker, a CVE lookup tool, and an automated recon framework that ties them together. Every tool is built for authorised use on systems you own or have explicit permission to test.

⚠ Legal warning -- authorised use only Every tool in this guide is legitimate for authorised security testing and illegal without authorisation. Running a port scanner, brute-forcer, or sniffer against systems you do not own or have written permission to test violates the Computer Fraud and Abuse Act (US), Computer Misuse Act (UK), and equivalent laws globally. Practise on your own VMs, HackTheBox, TryHackMe, or Vulnhub. Always obtain written authorisation before testing any system you do not own.
1. Environment setup

All tools require Python 3.10 or later. Use a virtual environment for every security project -- it isolates dependencies, prevents version conflicts, and keeps your system Python clean.

# Verify Python version (need 3.10+) python3 --version # Create and activate a virtual environment python3 -m venv ~/security-toolkit source ~/security-toolkit/bin/activate # Linux/macOS ~\security-toolkit\Scripts\activate # Windows # Install all third-party libraries used in this guide pip install requests paramiko scapy python-nmap colorama dnspython
socket
stdlib
TCP/UDP connections, port scanning, banner grabbing
requests
pip install requests
HTTP requests for web scanning, API calls, directory brute-forcing
paramiko
pip install paramiko
SSH client for brute-forcing and remote command execution
scapy
pip install scapy
Packet capture, crafting, and analysis for network security
dnspython
pip install dnspython
DNS queries for subdomain enumeration and DNS recon
hashlib
stdlib
MD5, SHA1, SHA256 hashing for hash cracking tools
threading
stdlib
Multi-threading to make scanners and brute-forcers fast
colorama
pip install colorama
Coloured terminal output so open ports and hits stand out
2. Tool 1 -- TCP port scanner

A port scanner probes a target host for open TCP ports. The Python socket module provides everything needed: create a socket, attempt a connection, and check whether it succeeds. Threading makes it fast enough to scan 1,000 ports in seconds rather than minutes. This is the foundation of almost every security assessment.

#!/usr/bin/env python3 # port_scanner.py -- Threaded TCP port scanner # Usage: python3 port_scanner.py -t 192.168.1.1 -p 1-1024 -T 100 # Authorised use only on systems you own or have permission to test import socket, threading, argparse, sys from queue import Queue from datetime import datetime COMMON_SERVICES = { 21:"FTP", 22:"SSH", 23:"Telnet", 25:"SMTP", 53:"DNS", 80:"HTTP", 110:"POP3", 143:"IMAP", 443:"HTTPS", 445:"SMB", 1433:"MSSQL", 3306:"MySQL", 3389:"RDP", 5432:"PostgreSQL", 5900:"VNC", 6379:"Redis", 8080:"HTTP-Alt", 27017:"MongoDB" } open_ports = [] port_queue = Queue() lock = threading.Lock() def scan_port(target, timeout): while not port_queue.empty(): port = port_queue.get() try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((target, port)) # 0 = open sock.close() if result == 0: service = COMMON_SERVICES.get(port, "Unknown") with lock: open_ports.append(port) print(f"[+] Port {port:5d}/tcp OPEN {service}") except socket.error: pass finally: port_queue.task_done() def main(): parser = argparse.ArgumentParser(description="Threaded TCP Port Scanner") parser.add_argument("-t", "--target", required=True) parser.add_argument("-p", "--ports", default="1-1024", help="Range e.g. 1-1024 or list e.g. 80,443,8080") parser.add_argument("-T", "--threads", type=int, default=100) parser.add_argument("--timeout", type=float, default=1.0) args = parser.parse_args() try: target_ip = socket.gethostbyname(args.target) except socket.gaierror: print(f"[-] Cannot resolve {args.target}"); sys.exit(1) # Parse port range "1-1024" or comma list "80,443,8080" if "-" in args.ports: start, end = args.ports.split("-") ports = range(int(start), int(end) + 1) else: ports = [int(p) for p in args.ports.split(",")] for p in ports: port_queue.put(p) print(f"[*] Scanning {target_ip} | {args.ports} | {args.threads} threads") print(f"[*] Started: {datetime.now().strftime('%H:%M:%S')}") print("-" * 50) threads = [] for _ in range(min(args.threads, port_queue.qsize())): t = threading.Thread(target=scan_port, args=(target_ip, args.timeout)) t.daemon = True t.start() threads.append(t) port_queue.join() print("-" * 50) print(f"[*] Done: {len(open_ports)} open port(s) -- {sorted(open_ports)}") if __name__ == "__main__": main()
Key concepts: connect_ex, Queue, and threading.Lock connect_ex() returns 0 on success (port open) instead of raising an exception -- cleaner for bulk scanning than try/except on every port. Queue is thread-safe: multiple worker threads can pull ports from it without corrupting the list. threading.Lock() ensures only one thread writes to open_ports at a time, preventing race conditions. daemon=True means threads die automatically when the main script exits -- no hanging processes if you Ctrl+C.
3. Tool 2 -- Banner grabber and service fingerprinter

Banner grabbing connects to an open port and reads the service greeting message, which typically includes the software name and version: Apache/2.4.51, OpenSSH_8.9, vsftpd 3.0.5. Version information maps directly to CVE databases. This is how penetration testers quickly identify exploitable versions without running a full vulnerability scanner.

#!/usr/bin/env python3 # banner_grabber.py -- Grab service banners from open ports # Usage: python3 banner_grabber.py -t 192.168.1.1 -p 21,22,80,443,8080 import socket, ssl, argparse HTTP_PROBE = b"GET / HTTP/1.0\r\nHost: TARGET\r\nUser-Agent: Mozilla/5.0\r\n\r\n" GENERIC_PROBE = b"\r\n" KNOWN_SERVICES = [ "Apache", "nginx", "OpenSSH", "vsftpd", "ProFTPD", "Microsoft-IIS", "Postfix", "Exim", "MySQL", "Redis", "Samba" ] def grab_banner(host, port, timeout=3): banner = "" try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((host, port)) if port in (80, 8080, 8000): sock.send(HTTP_PROBE.replace(b"TARGET", host.encode())) elif port in (443, 8443): ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE sock = ctx.wrap_socket(sock, server_hostname=host) sock.send(HTTP_PROBE.replace(b"TARGET", host.encode())) else: sock.send(GENERIC_PROBE) banner = sock.recv(1024).decode("utf-8", errors="replace").strip() sock.close() except: banner = "" return banner def fingerprint(banner): found = [s for s in KNOWN_SERVICES if s.lower() in banner.lower()] return found if found else ["Unknown"] def main(): parser = argparse.ArgumentParser(description="Banner Grabber") parser.add_argument("-t", "--target", required=True) parser.add_argument("-p", "--ports", default="21,22,25,80,443,3306,3389") args = parser.parse_args() ports = [int(p) for p in args.ports.split(",")] print(f"[*] Grabbing banners from {args.target}\n") for port in ports: banner = grab_banner(args.target, port) if banner: services = fingerprint(banner) print(f"[+] Port {port}: {', '.join(services)}") print(f" {banner.split(chr(10))[0][:120]}") else: print(f"[-] Port {port}: no banner") if __name__ == "__main__": main()
4. Tool 3 -- Subdomain enumerator

Subdomain enumeration discovers hosts like dev.example.com, staging.example.com, and api.example.com that often run older software or expose internal services. DNS brute-forcing sends a DNS query for each candidate subdomain and records those that resolve. Threading makes it fast enough to test 10,000 candidates in under a minute.

#!/usr/bin/env python3 # subdomain_enum.py -- DNS brute-force subdomain enumerator # Usage: python3 subdomain_enum.py -d example.com -w subdomains.txt -T 50 # Wordlist: SecLists/Discovery/DNS/subdomains-top1million-5000.txt import dns.resolver, threading, argparse, sys from queue import Queue found_subdomains = [] queue = Queue() lock = threading.Lock() def check_subdomain(domain, resolver): while not queue.empty(): word = queue.get() subdomain = f"{word}.{domain}" try: answers = resolver.resolve(subdomain, "A") ips = [str(r) for r in answers] with lock: found_subdomains.append(subdomain) print(f"[+] {subdomain:<45} {', '.join(ips)}") except: pass finally: queue.task_done() def main(): parser = argparse.ArgumentParser(description="Subdomain Enumerator") parser.add_argument("-d", "--domain", required=True) parser.add_argument("-w", "--wordlist", required=True) parser.add_argument("-T", "--threads", type=int, default=50) parser.add_argument("--dns", default="8.8.8.8") args = parser.parse_args() resolver = dns.resolver.Resolver() resolver.nameservers = [args.dns] resolver.timeout = 2; resolver.lifetime = 2 try: with open(args.wordlist) as f: words = [l.strip() for l in f if l.strip()] except FileNotFoundError: print(f"[-] Wordlist not found"); sys.exit(1) for w in words: queue.put(w) print(f"[*] Enumerating {args.domain} | {len(words)} words | {args.threads} threads\n") ts = [threading.Thread(target=check_subdomain, args=(args.domain, resolver), daemon=True) for _ in range(min(args.threads, len(words)))] for t in ts: t.start() queue.join() print(f"\n[*] Found {len(found_subdomains)} subdomain(s)") for s in sorted(found_subdomains): print(f" {s}") if __name__ == "__main__": main()
Free wordlists: The SecLists repository (github.com/danielmiessler/SecLists) contains production-quality wordlists for subdomain enumeration, directory brute-forcing, and passwords. The DNS list subdomains-top1million-5000.txt covers the 5,000 most common subdomain names and finds the vast majority of real subdomains in practice.
5. Tool 4 -- Web directory brute-forcer

Directory brute-forcing discovers hidden paths on web servers: admin panels, backup files, configuration pages, and API endpoints not linked from the public site. It sends an HTTP request for each candidate path and records those returning HTTP 200 (found), 301/302 (redirect), or 403 (forbidden -- the path exists but is blocked).

#!/usr/bin/env python3 # dir_brute.py -- Web directory brute-forcer # Usage: python3 dir_brute.py -u http://target.com -w common.txt -T 30 # Wordlist: SecLists/Discovery/Web-Content/common.txt import requests, threading, argparse, sys, urllib3 from queue import Queue urllib3.disable_warnings() found = [] queue = Queue() lock = threading.Lock() STATUS_LABELS = { 200:"[200 OK] ", 301:"[301 REDIR] ", 302:"[302 REDIR] ", 403:"[403 FORBID]", 401:"[401 AUTH] ", 500:"[500 ERROR] " } def scan_path(base_url, extensions, session, timeout): while not queue.empty(): word = queue.get() targets = [word] + [f"{word}.{e}" for e in extensions] for path in targets: url = f"{base_url.rstrip('/')}/{path}" try: r = session.get(url, timeout=timeout, verify=False, allow_redirects=False) if r.status_code in STATUS_LABELS: label = STATUS_LABELS[r.status_code] with lock: found.append(url) print(f"[+] {label} {url} ({len(r.content)} bytes)") except requests.RequestException: pass queue.task_done() def main(): parser = argparse.ArgumentParser(description="Web Directory Brute-forcer") parser.add_argument("-u", "--url", required=True) parser.add_argument("-w", "--wordlist", required=True) parser.add_argument("-T", "--threads", type=int, default=30) parser.add_argument("-x", "--extensions", default="php,html,txt,bak") parser.add_argument("--timeout", type=float, default=5.0) args = parser.parse_args() exts = [e.strip() for e in args.extensions.split(",")] try: with open(args.wordlist) as f: words = [l.strip() for l in f if l.strip() and not l.startswith("#")] except FileNotFoundError: print(f"[-] Wordlist not found"); sys.exit(1) for w in words: queue.put(w) session = requests.Session() session.headers["User-Agent"] = "Mozilla/5.0 (Security Scanner)" print(f"[*] Target: {args.url} | {len(words)} words | exts: {args.extensions}\n") ts = [threading.Thread(target=scan_path, args=(args.url, exts, session, args.timeout), daemon=True) for _ in range(args.threads)] for t in ts: t.start() queue.join() print(f"\n[*] Done. {len(found)} path(s) found.") if __name__ == "__main__": main()
6. Tool 5 -- SSH brute-forcer with Paramiko

Paramiko is a pure-Python SSH client. This tool tests SSH authentication with a list of username/password combinations -- a standard test during authorised penetration tests for weak credential policies. A short delay between attempts avoids triggering fail2ban on test targets.

⚠ For authorised use only -- brute-forcing systems without permission is illegal This tool is for testing SSH servers you own or have explicit written permission to test. Brute-forcing production servers without authorisation is a criminal offence.
#!/usr/bin/env python3 # ssh_brute.py -- SSH brute-forcer using Paramiko # Usage: python3 ssh_brute.py -t 192.168.1.100 -u root -P passwords.txt # Authorised testing only import paramiko, argparse, sys, time, socket paramiko.util.log_to_file("/dev/null") # Suppress paramiko debug output def try_ssh(host, port, username, password, timeout=5): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect(host, port=port, username=username, password=password, timeout=timeout, allow_agent=False, look_for_keys=False) client.close() return True except paramiko.AuthenticationException: return False # Wrong password -- expected, keep going except (paramiko.SSHException, socket.error): return None # Connection issue -- skip this attempt def main(): parser = argparse.ArgumentParser(description="SSH Brute-forcer (authorised use only)") parser.add_argument("-t", "--target", required=True) parser.add_argument("-p", "--port", type=int, default=22) parser.add_argument("-u", "--username", required=True) parser.add_argument("-P", "--passlist", required=True) parser.add_argument("--delay", type=float, default=0.3, help="Seconds between attempts (default: 0.3)") args = parser.parse_args() try: usernames = open(args.username).read().splitlines() except: usernames = [args.username] try: passwords = open(args.passlist).read().splitlines() except FileNotFoundError: print(f"[-] Password list not found"); sys.exit(1) total = len(usernames) * len(passwords) print(f"[*] Target: {args.target}:{args.port} | {total} combinations\n") for username in usernames: for password in passwords: print(f" Trying {username}:{password}", end="\r") result = try_ssh(args.target, args.port, username, password) if result is True: print(f"\n[+] SUCCESS! {username}:{password}") print(f" Connect: ssh {username}@{args.target}") sys.exit(0) time.sleep(args.delay) print(f"\n[-] No valid credentials found in {total} attempts.") if __name__ == "__main__": main()
7. Tool 6 -- Packet sniffer with Scapy

Scapy is Python's packet manipulation library -- it can capture, parse, craft, and send network packets. This sniffer captures live traffic, displays protocol, source, destination, and payload summaries, and decodes DNS queries and HTTP requests. Requires root/administrator privileges and must only be used on networks you are authorised to monitor.

#!/usr/bin/env python3 # packet_sniffer.py -- Network packet sniffer using Scapy # Usage: sudo python3 packet_sniffer.py -i eth0 -c 100 -f "tcp port 80" # Requires root. Authorised networks only. from scapy.all import sniff, IP, TCP, UDP, ICMP, DNS, Raw, ARP import argparse from datetime import datetime pkt_count = 0 def process_packet(packet): global pkt_count pkt_count += 1 ts = datetime.now().strftime("%H:%M:%S.%f")[:-3] if packet.haslayer(ARP): a = packet[ARP] op = "request" if a.op == 1 else "reply" print(f"[{ts}] ARP {op}: {a.psrc} -> {a.pdst}") return if not packet.haslayer(IP): return ip = packet[IP]; info = ""; proto = "?" if packet.haslayer(TCP): tcp = packet[TCP]; proto = "TCP" flags = tcp.sprintf("%flags%") info = f"{ip.src}:{tcp.sport} -> {ip.dst}:{tcp.dport} [{flags}]" if packet.haslayer(Raw): pay = packet[Raw].load.decode("utf-8", errors="replace") if pay.startswith(("GET ", "POST ", "HTTP/")): info += f" | {pay.split(chr(10))[0].strip()[:80]}" elif packet.haslayer(UDP): udp = packet[UDP]; proto = "UDP" info = f"{ip.src}:{udp.sport} -> {ip.dst}:{udp.dport}" if packet.haslayer(DNS) and packet[DNS].qr == 0: info += f" DNS? {packet[DNS].qd.qname.decode()}" elif packet.haslayer(ICMP): proto = "ICMP" t = {0:"reply", 8:"request", 3:"unreachable"}.get(packet[ICMP].type, "?") info = f"{ip.src} -> {ip.dst} ({t})" if info: print(f"[{ts}] {proto:<5} {info}") def main(): parser = argparse.ArgumentParser(description="Packet Sniffer (requires root)") parser.add_argument("-i", "--iface", default="eth0") parser.add_argument("-c", "--count", type=int, default=0, help="Packets to capture (0=unlimited)") parser.add_argument("-f", "--filter", default="", help='BPF filter e.g. "tcp port 80"') args = parser.parse_args() print(f"[*] Sniffing on {args.iface} | filter: '{args.filter or 'none'}' | Ctrl+C to stop\n") try: sniff(iface=args.iface, filter=args.filter, prn=process_packet, count=args.count, store=False) except KeyboardInterrupt: print(f"\n[*] Stopped. {pkt_count} packets processed.") if __name__ == "__main__": main()
8. Tool 7 -- Log analyser and failed login detector

Log analysis is core blue team work. This tool parses Linux auth logs (/var/log/auth.log), extracts failed SSH login attempts, groups them by source IP, and flags brute-force activity. The most dangerous finding: an IP that failed many times and then succeeded, indicating a likely compromise.

#!/usr/bin/env python3 # log_analyser.py -- Parse auth logs and detect brute-force attacks # Usage: python3 log_analyser.py -f /var/log/auth.log -t 10 --report import re, argparse, sys from collections import Counter, defaultdict from datetime import datetime SSH_FAIL = re.compile(r"(\w+\s+\d+\s[\d:]+).*Failed password for (?:invalid user )?(\S+) from ([\d.]+)") SSH_OK = re.compile(r"(\w+\s+\d+\s[\d:]+).*Accepted password for (\S+) from ([\d.]+)") def analyse(filepath, threshold): failed = Counter(); success = Counter() usernames = defaultdict(set); timeline = defaultdict(list) total = 0 try: with open(filepath, errors="replace") as f: for line in f: total += 1 m = SSH_FAIL.search(line) if m: ts, user, ip = m.group(1), m.group(2), m.group(3) failed[ip] += 1; usernames[ip].add(user); timeline[ip].append(ts) continue m = SSH_OK.search(line) if m: success[m.group(3)] += 1 except FileNotFoundError: print(f"[-] Log file not found: {filepath}"); sys.exit(1) return failed, success, usernames, timeline, total def main(): parser = argparse.ArgumentParser(description="Auth Log Analyser") parser.add_argument("-f", "--file", required=True) parser.add_argument("-t", "--threshold", type=int, default=10) parser.add_argument("--top", type=int, default=20) parser.add_argument("--report", action="store_true") args = parser.parse_args() failed, success, usernames, timeline, total = analyse(args.file, args.threshold) lines = [ f"Log Analyser Report -- {datetime.now().strftime('%Y-%m-%d %H:%M')}", f"File: {args.file} | {total:,} lines | {len(failed)} attacker IPs", f"Total failed attempts: {sum(failed.values()):,}", "", f"{'IP':<18} {'Fails':>7} {'Users':>8} Last seen", "-"*65 ] for ip, cnt in failed.most_common(args.top): flag = " *** BRUTE FORCE ***" if cnt >= args.threshold else "" last = timeline[ip][-1] if timeline[ip] else "" lines.append(f"{ip:<18} {cnt:>7,} {len(usernames[ip]):>8} {last}{flag}") # IPs that failed many times then succeeded = possible compromise compromised = [ip for ip in success if failed.get(ip, 0) >= args.threshold] if compromised: lines.append("\n[!] HIGH RISK: brute-force followed by successful login:") for ip in compromised: lines.append(f" {ip}: {failed[ip]} fails then {success[ip]} success(es)") output = "\n".join(lines) print(output) if args.report: fname = f"log_report_{datetime.now().strftime('%Y%m%d_%H%M')}.txt" open(fname, "w").write(output) print(f"\n[*] Report saved: {fname}") if __name__ == "__main__": main()
9. Tool 8 -- Hash identifier and dictionary cracker

Hash cracking tests password strength by checking whether a hash matches a known wordlist. The identifier detects hash type from length and character set before cracking. Used in penetration tests to assess password policy strength and in CTF challenges.

#!/usr/bin/env python3 # hash_cracker.py -- Hash identifier and dictionary cracker # Usage: python3 hash_cracker.py -H 5f4dcc3b5aa765d61d8327deb882cf99 -w rockyou.txt import hashlib, argparse, sys, re from datetime import datetime HASH_SIGS = [ (32, r"^[a-f0-9]+$", ["md5"]), (40, r"^[a-f0-9]+$", ["sha1"]), (64, r"^[a-f0-9]+$", ["sha256"]), (96, r"^[a-f0-9]+$", ["sha384"]), (128, r"^[a-f0-9]+$", ["sha512"]), ] def identify_hash(h): h = h.strip().lower() for length, pattern, algos in HASH_SIGS: if len(h) == length and re.match(pattern, h): return algos return ["unknown"] def crack(target_hash, wordlist_path, algorithms): target = target_hash.strip().lower() start = datetime.now(); attempts = 0 try: with open(wordlist_path, errors="replace") as f: for line in f: pw = line.strip() if not pw: continue attempts += 1 if attempts % 100000 == 0: print(f" {attempts:,} attempts...", end="\r") for algo in algorithms: try: h = hashlib.new(algo) h.update(pw.encode("utf-8", errors="replace")) if h.hexdigest() == target: elapsed = (datetime.now()-start).total_seconds() print(f"\n[+] CRACKED in {elapsed:.1f}s ({attempts:,} attempts)") print(f" Algorithm: {algo} Password: {pw}") return pw except ValueError: pass except FileNotFoundError: print(f"[-] Wordlist not found"); sys.exit(1) elapsed = (datetime.now()-start).total_seconds() print(f"\n[-] Not cracked. {attempts:,} passwords tried in {elapsed:.1f}s.") def main(): parser = argparse.ArgumentParser(description="Hash Identifier and Cracker") parser.add_argument("-H", "--hash", required=True) parser.add_argument("-w", "--wordlist", required=True) parser.add_argument("-a", "--algo", default="") args = parser.parse_args() algos = [args.algo] if args.algo else identify_hash(args.hash) print(f"[*] Hash: {args.hash} Detected: {', '.join(algos)}\n") crack(args.hash, args.wordlist, algos) if __name__ == "__main__": main()
10. Tool 9 -- CVE lookup tool

This tool queries the NIST National Vulnerability Database (NVD) API to look up CVE details: CVSS score, description, affected software, and references. Useful during a penetration test when you have identified a software version and want to check known vulnerabilities without leaving the terminal.

#!/usr/bin/env python3 # cve_lookup.py -- Query the NIST NVD API for CVE details # Usage: python3 cve_lookup.py -c CVE-2021-44228 # python3 cve_lookup.py -k "apache log4j" --top 5 import requests, argparse, sys NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0" def lookup_cve(cve_id): try: r = requests.get(NVD_API, params={"cveId": cve_id}, timeout=15) r.raise_for_status() data = r.json() if data.get("totalResults", 0) == 0: print(f"[-] CVE not found: {cve_id}"); return None return data["vulnerabilities"][0]["cve"] except requests.RequestException as e: print(f"[-] API error: {e}"); sys.exit(1) def search_cves(keyword, top=10): try: r = requests.get(NVD_API, params={"keywordSearch": keyword, "resultsPerPage": top}, timeout=15) r.raise_for_status() return [v["cve"] for v in r.json().get("vulnerabilities", [])] except requests.RequestException as e: print(f"[-] API error: {e}"); sys.exit(1) def get_cvss(cve): m = cve.get("metrics", {}) if "cvssMetricV31" in m: d = m["cvssMetricV31"][0]["cvssData"] return d.get("baseScore"), d.get("baseSeverity"), "v3.1" elif "cvssMetricV2" in m: d = m["cvssMetricV2"][0]["cvssData"] return d.get("baseScore"), "N/A", "v2" return "N/A", "N/A", "N/A" def print_cve(cve): score, severity, ver = get_cvss(cve) desc = cve.get("descriptions", [{}])[0].get("value", "No description") published = cve.get("published", "")[:10] refs = [r["url"] for r in cve.get("references", [])[:3]] print("\n" + "="*70) print(f"CVE: {cve['id']}") print(f"Published: {published}") print(f"CVSS Score: {score} ({severity}) [{ver}]") print(f"Description: {desc[:300]}{'...' if len(desc)>300 else ''}") if refs: print("References:") for ref in refs: print(f" {ref}") print("="*70) def main(): parser = argparse.ArgumentParser(description="CVE Lookup Tool (NIST NVD API)") g = parser.add_mutually_exclusive_group(required=True) g.add_argument("-c", "--cve", help="CVE ID e.g. CVE-2021-44228") g.add_argument("-k", "--keyword", help="Search keyword") parser.add_argument("--top", type=int, default=5) args = parser.parse_args() if args.cve: cve = lookup_cve(args.cve.upper()) if cve: print_cve(cve) else: print(f"[*] Searching NVD: '{args.keyword}' (top {args.top})") for cve in search_cves(args.keyword, args.top): print_cve(cve) if __name__ == "__main__": main()
11. Tool 10 -- Automated recon framework

This framework ties the previous tools together into one script. Given a target, it runs port scanning, banner grabbing, subdomain enumeration, and web header analysis in sequence, saving all results to a timestamped report directory as both plain text and JSON. This is the kind of script penetration testers run at the start of every engagement.

#!/usr/bin/env python3 # recon.py -- Automated recon framework # Usage: python3 recon.py -t example.com -w subdomains.txt # Saves output to ./recon-{target}-{timestamp}/ import socket, threading, requests, argparse, sys, json, urllib3 import dns.resolver from queue import Queue from datetime import datetime from pathlib import Path urllib3.disable_warnings() def log(msg, f=None): print(msg) if f: f.write(msg + "\n") def port_scan(host, ports, threads=150, timeout=1.0): open_ports = []; q = Queue(); lock = threading.Lock() for p in ports: q.put(p) def worker(): while not q.empty(): port = q.get() try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) if s.connect_ex((host, port)) == 0: with lock: open_ports.append(port) s.close() except: pass finally: q.task_done() ts = [threading.Thread(target=worker, daemon=True) for _ in range(min(threads, len(ports)))] for t in ts: t.start() q.join() return sorted(open_ports) def grab_banner(host, port, timeout=3): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout); s.connect((host, port)) probe = (f"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n".encode() if port in (80, 8080) else b"\r\n") s.send(probe) banner = s.recv(512).decode("utf-8", errors="replace").strip() s.close() return banner.split("\n")[0][:100] except: return "" def enum_subdomains(domain, wordlist, threads=50): if not wordlist or not Path(wordlist).exists(): return [] found = []; q = Queue(); lock = threading.Lock() resolver = dns.resolver.Resolver() resolver.nameservers = ["8.8.8.8"]; resolver.timeout = 2 with open(wordlist) as f: for w in f: q.put(w.strip()) def worker(): while not q.empty(): w = q.get(); sub = f"{w}.{domain}" try: ips = [str(r) for r in resolver.resolve(sub, "A")] with lock: found.append({"subdomain": sub, "ips": ips}) except: pass finally: q.task_done() ts = [threading.Thread(target=worker, daemon=True) for _ in range(threads)] for t in ts: t.start() q.join(); return found def web_headers(url): required = ["X-Content-Type-Options", "X-Frame-Options", "Content-Security-Policy", "Strict-Transport-Security"] try: r = requests.get(url, timeout=8, verify=False) issues = [f"Missing: {h}" for h in required if h not in r.headers] return r.status_code, r.headers.get("Server", "[hidden]"), issues except: return "err", "", [] def main(): parser = argparse.ArgumentParser(description="Automated Recon Framework") parser.add_argument("-t", "--target", required=True) parser.add_argument("-w", "--wordlist", default="") parser.add_argument("-p", "--ports", default="1-1024") args = parser.parse_args() ts = datetime.now().strftime("%Y%m%d_%H%M%S") out_dir = Path(f"recon-{args.target}-{ts}"); out_dir.mkdir(exist_ok=True) results = {} with open(out_dir / "report.txt", "w") as rep: log(f"RECON REPORT: {args.target} {datetime.now().strftime('%Y-%m-%d %H:%M')}", rep) log("="*60, rep) try: ip = socket.gethostbyname(args.target) log(f"Target IP: {ip}\n", rep) except: log(f"Cannot resolve {args.target}", rep); sys.exit(1) # Port scan log(f"[1/4] PORT SCAN ({args.ports})", rep) if "-" in args.ports: s, e = args.ports.split("-") port_list = list(range(int(s), int(e)+1)) else: port_list = [int(p) for p in args.ports.split(",")] open_ports = port_scan(ip, port_list) results["open_ports"] = open_ports for p in open_ports: log(f" [+] Port {p}/tcp OPEN", rep) log(f" Total: {len(open_ports)} open port(s)\n", rep) # Banner grab log(f"[2/4] BANNER GRABBING", rep) banners = {} for port in open_ports: b = grab_banner(ip, port) banners[port] = b if b: log(f" Port {port}: {b}", rep) results["banners"] = banners; log("", rep) # Subdomain enum log(f"[3/4] SUBDOMAIN ENUMERATION", rep) subs = enum_subdomains(args.target, args.wordlist) results["subdomains"] = subs for s in subs: log(f" [+] {s['subdomain']:<40} {', '.join(s['ips'])}", rep) log(f" Found: {len(subs)}\n", rep) # Web headers log(f"[4/4] WEB SECURITY HEADERS", rep) for scheme in ("http", "https"): url = f"{scheme}://{args.target}" status, server, issues = web_headers(url) log(f" {url} Status: {status} Server: {server}", rep) for issue in issues: log(f" [-] {issue}", rep) json_path = out_dir / "results.json" json_path.write_text(json.dumps(results, indent=2)) log(f"\n[*] Report: {out_dir}/report.txt JSON: {json_path}", rep) if __name__ == "__main__": main()
Extending the framework The framework is deliberately modular -- each phase is a standalone function. Add new modules by writing a function and calling it in main(): a Shodan API lookup, a robots.txt parser, a certificate transparency log checker (crt.sh API for extra subdomain discovery), or a technology fingerprinter. JSON output makes it trivial to pipe results into dashboards or further scripts.
10
working security tools built in this guide -- from port scanner to full recon framework
#1
Python is the most used language for security tooling, scripting, and automation in 2026
6
stdlib modules used -- socket, threading, hashlib, re, argparse, ssl -- no install required
0
lines of code in this guide require unlawful use -- all tools built for authorised testing only

⚡ Build on this toolkit -- next steps

  1. Set up a home lab to practise safely. Download Metasploitable2 or VulnHub VMs -- intentionally vulnerable machines designed for legal security practice. Run every tool in this guide against them. TryHackMe and HackTheBox provide browser-based labs if you prefer not to run local VMs. Never run these tools against systems you do not own.
  2. Add argparse to every script from line one. Every tool in this guide uses argparse -- it is the single habit that converts a single-use script into a reusable tool. The -h flag documents the tool for your future self and teammates. Add it first, not as an afterthought.
  3. Learn Scapy deeply -- it is the foundation of network security tooling. The packet sniffer in Tool 6 is the entry point. Scapy can also craft and send packets (SYN scans, ARP spoofing detection, custom protocol fuzzing), making it the Swiss Army knife of network security scripting. The official Scapy documentation and the book "Black Hat Python" both cover Scapy in detail.
  4. Study the source code of professional security tools. Nmap, Metasploit, Burp Suite, and Wireshark are the tools you will use daily as a security professional. Reading how professionals structure security tools is as educational as writing your own. Metasploit tutorial | Network segmentation | Active Directory security | Vulnerability management
Frequently asked questions
Is Python good for cybersecurity?

Python is the dominant language for cybersecurity scripting and tooling. The standard library covers low-level networking (socket, ssl, struct), and the ecosystem provides everything else: Scapy for packets, Paramiko for SSH, Requests for HTTP, dnspython for DNS. Major security tools written in Python include Scapy, Impacket, Volatility, sqlmap, and most of the OWASP testing toolkit. For security automation, log analysis, API security testing, and custom tool development, Python is the professional's first choice.

What Python libraries do I need for cybersecurity?

The core Python security toolkit: socket and ssl (stdlib) for network connections; threading and queue (stdlib) for concurrent scanning; hashlib (stdlib) for cryptographic hashing; re (stdlib) for log parsing; argparse (stdlib) for command-line interfaces. Third-party: requests for HTTP; scapy for packet capture and manipulation; paramiko for SSH; dnspython for DNS queries. Install with: pip install requests scapy paramiko dnspython colorama

How do I write a port scanner in Python?

Use socket.connect_ex() to test TCP ports -- it returns 0 if the port is open, non-zero if closed. Set a short timeout with socket.settimeout(). Use Python threading with a Queue to scan multiple ports simultaneously -- 100 threads reduces a 1,000-port scan from ~16 minutes to under 10 seconds. See Tool 1 in this guide for a complete, annotated, threaded implementation with command-line argument support and service name lookup.

What is Scapy and how is it used in security?

Scapy is a Python library for packet manipulation -- it can capture live traffic, craft and send custom packets, and parse every protocol layer. Security use cases include network sniffing and traffic analysis, ARP spoofing detection, SYN scan implementation, custom protocol testing, network forensics, and fuzzing services with malformed packets. Scapy requires root or administrator privileges because it operates at the raw socket level. Install with pip install scapy.

Can I use Python for ethical hacking and penetration testing?

Yes -- Python is the primary scripting language for ethical hacking. Penetration testers use it for custom scanners, exploit modification, post-exploitation automation, log analysis, and rapid prototyping of attack chains. The entire Impacket library (SMB, NTLM, Kerberos tooling) is Python. Learning Python makes you a faster penetration tester because you can build exactly the tool you need for a specific target rather than being limited to what existing tools support.

Is it illegal to run a port scanner or brute-forcer?

Running these tools against systems you do not own or have explicit written permission to test is illegal under the Computer Fraud and Abuse Act (US), Computer Misuse Act (UK), and equivalent laws globally. Authorised use -- against your own systems, in a lab environment (Metasploitable, HackTheBox, TryHackMe), or with a signed penetration testing authorisation letter -- is legal and professionally standard. Always obtain written authorisation before testing any system. Professional penetration testers carry a signed Rules of Engagement document for every engagement specifying exactly which systems and techniques are authorised.

About the author Written by the HOC Team at Hackers Online Club -- a cybersecurity community trusted by ethical hackers, penetration testers, security engineers, and developers since 2010. 15+ years of practical cybersecurity tutorials, security tool development guides, and Python scripting resources. Learn more about HOC

Join Our Club

Enter your Email address to receive notifications | Join over Million Followers

Previous Article
API Security Testing

API Security Testing Tutorial: How to Test REST APIs For Vulnerabilities

Related Posts