Unit 1: Python for defenders · Lesson 3 of 6 · 22 min

Loops, conditions and errors

Walk the whole inventory, keep the risky hosts and read the errors Python gives you.

mission

You are
the engineer running this week's exposure review
Situation
Telnet sends passwords in clear text and remote desktop is a favourite target. Your manager wants every host that still exposes either one.
Goal
Write a script that loops over the inventory and flags telnet and remote desktop.
Hand in
A list of hosts with telnet (23) or remote desktop (3389) open.

A loop, one line at a time

Click a stage to see what flows out of it
web02 web ubuntu-24.04 22,80,443,3389
db02 db ubuntu-24.04 22,5432,8443
print01 print ubuntu-24.04 23,80,9100

3 lines. data/inventory.txt, before any filter

Each line goes through the same steps. Only the hosts that pass the condition reach the output.
01

Write the script

Indentation is the syntax: everything indented under for runs once per line.

learner@py-lab
cat > scripts/exposure.py <<'EOF'
RISKY = {23: "telnet", 3389: "remote desktop"}

for line in open("data/inventory.txt"):
    name, role, system, ports = line.split()
    ports = [int(p) for p in ports.split(",")]
    for port, service in RISKY.items():
        if port in ports:
            print(f"{name:<10} {service}")
EOF
02

Run it

learner@py-lab
python3 scripts/exposure.py
web02      remote desktop
db01       telnet
vpn01      telnet
dc01       remote desktop
dc02       telnet
dc02       remote desktop
dc03       remote desktop
file02     telnet
file03     remote desktop
print01    telnet
print02    telnet
print03    telnet
legacy01   telnet

you should see One line per host and risky service. A host with both appears twice.

03

Count instead of listing

Managers want numbers first. Pipe the output into standard tools rather than rewriting the script.

learner@py-lab
python3 scripts/exposure.py | grep -c telnet
8
python3 scripts/exposure.py | grep -c 'remote desktop'
5
04

Meet your first traceback

Real data is messy. Add a broken line to a copy of the inventory and see how Python reports it.

learner@py-lab
cp data/inventory.txt /tmp/inv.txt && echo 'printer-old 9100' >> /tmp/inv.txt
sed 's#data/inventory.txt#/tmp/inv.txt#' scripts/exposure.py > /tmp/exposure.py && python3 /tmp/exposure.py | tail -n 7
Traceback (most recent call last):
  File "/tmp/exposure.py", line 4, in <module>
    name, role, system, ports = line.split()
    ^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 4, got 2)
dc03       remote desktop
file02     telnet
file03     remote desktop
print01    telnet
print02    telnet
print03    telnet
legacy01   telnet

you should see Read tracebacks from the bottom: the last line names the error, the line above shows where.

defender tip A crash halfway through a run still printed earlier findings. In automation that partial output is easy to mistake for a full result.

05

Handle bad lines on purpose

Catch the specific error, report the bad line and keep going.

learner@py-lab
cat > /tmp/exposure2.py <<'EOF'
RISKY = {23: "telnet", 3389: "remote desktop"}

for n, line in enumerate(open("/tmp/inv.txt"), 1):
    try:
        name, role, system, ports = line.split()
    except ValueError:
        print(f"skipped line {n}: {line.strip()!r}")
        continue
    ports = [int(p) for p in ports.split(",")]
    for port, service in RISKY.items():
        if port in ports:
            print(f"{name:<10} {service}")
EOF
python3 /tmp/exposure2.py | tail -n 3
print03    telnet
legacy01   telnet
skipped line 18: 'printer-old 9100'

you should see The script finishes and tells you which line it skipped.

Silent
try:
    name, role, system, ports = line.split()
except Exception:
    pass

Swallows every error. A changed log format now looks like a quiet day.

Honest
try:
    name, role, system, ports = line.split()
except ValueError:
    print(f"skipped line {n}")
    continue

Catches only the expected error and says what it skipped.

What a defender takes away

  1. 1A loop plus a condition is the core of most security scripts.
  2. 2Read tracebacks bottom up: error type, then the line that raised it.
  3. 3Never hide errors. Report skipped input so a quiet result means something.

Questions

0 of 3 solved

Answer each one from what you find on your lab machine.

  1. Show hint

    Pipe the script into grep -c.

  2. Show hint

    Look for a name that appears twice.

  3. Show hint

    The last line of the traceback.