HackBlue

Tools · Python and Bash for security automation · 10 / 20

A Python TCP service check for your own hosts: expected ports versus reality

Write a small script that connects to a list of your hosts and ports, compares results with an expected-state file, and reports unexpected open or closed services.

9 min read · practice only with authorization

Expected state

Network exposure drifts: a debug port left open, a database bound to all interfaces, a service that stopped. A simple check that compares reality with an expected-state file catches drift between full scans. Run it only against systems you own or are authorized to test.

The script

A TCP connect with a short timeout tells you whether a port accepts connections. ThreadPoolExecutor keeps it fast for dozens of checks.

Snippet
#!/usr/bin/env python3
"""Compare open TCP ports on owned hosts with an expected state file."""
import json, socket, sys
from concurrent.futures import ThreadPoolExecutor

expected = json.load(open(sys.argv[1]))   # {"10.0.0.5": {"open": [22, 443], "closed": [3306, 6379]}}

def is_open(host, port):
    try:
        with socket.create_connection((host, port), timeout=1.5):
            return True
    except OSError:
        return False

jobs = [(h, p, want) for h, spec in expected.items() for want in ('open', 'closed') for p in spec.get(want, [])]
with ThreadPoolExecutor(max_workers=32) as ex:
    results = list(ex.map(lambda j: (*j, is_open(j[0], j[1])), jobs))
drift = 0
for host, port, want, actual in results:
    ok = actual == (want == 'open')
    drift += not ok
    print(f'{"OK  " if ok else "DRIFT"} {host}:{port} expected {want}, actually {"open" if actual else "closed"}')
sys.exit(1 if drift else 0)

Lab: catch a drift

Write an expected-state file for two lab VMs: SSH open, a web port open, and database and cache ports closed. Run the check. Then start a test service on a closed port, such as python3 -m http.server 6379 on one VM, and rerun to see a DRIFT line and exit code 1. Stop the service and confirm the check is clean again.

Snippet
cat > expected.json <<'EOF'
{"192.168.56.10": {"open": [22, 443], "closed": [3306, 6379, 9200]},
 "192.168.56.11": {"open": [22], "closed": [5432, 8080]}}
EOF
python3 portcheck.py expected.json; echo exit=$?

When to use a real scanner

This check only tests ports you list. For discovering unknown services, use Nmap against your own ranges on a schedule and diff results, as covered in the Nmap hub. Cloud security groups and firewall rules are another source of truth worth comparing against.

Diagram

Expected versus actual

Drift is any mismatch.

opencloseddrift

192.168.56.10:22

expected open
actually open

192.168.56.10:6379

expected closed
actually open

192.168.56.11:22

expected open
actually closed

Check yourself

1. Which systems may this script check?

2. A port expected closed is open. What does the script report?

3. Why use a thread pool?