Cyberlium

Python › Module 1 › Lesson 4

BeginnerModule 1Lesson 4/5

Lab — First Python Script

Write and run a small security-flavored script that reads a file and prints matching lines

25 min+21 XP3 quiz
Module progress4 of 5

Opening

Mini hunter: print matching lines from a log you wrote. The DNA of every analyzer, without touching anyone else’s files.

Lessons 1–3 gave you why Python, the loop/function grammar, and with open plus pathlib. This lab puts them on one page. You will create a fictional sample_auth.log under $HOME/cyberlium-lab, lock it with chmod 600, and run find_failed.py so lines containing Failed password print to the terminal. That substring check is how junior hunts start before SIEM queries exist. The log is fiction: fake users, documentation IPs, no real passwords, no production export from work. You will not open /etc/auth.log, /var/log/secure, or a roommate’s machine “to have real data.” You will not pipe the script at a live SSH service. You will not treat the optional per-IP count as a reason to scan the internet for those addresses — 10.0.0.5 and 203.0.113.10 here are teaching labels (203.0.113.0/24 is documentation space). Evidence of completion is printed lines plus a locked log and script. Next is the module quiz, then sockets — still not this file’s job.

1. What you are building: needle in line, streamed, on a file you own

The mechanism is three bindings and a loop. needle = "Failed password" is a str. path is a pathlib Path under your home lab, not a cwd-relative guess. with path.open("r", encoding="utf-8") as f: for line in f: if needle in line: print(line.strip()). "in" on two strings is substring search: it does not parse SSHD grammar; it does not need regex. strip() drops the trailing newline so your terminal is not double-spaced. If you slurp with read().splitlines() on a 20-line lab file, it still works; keep the for-line form anyway so the muscle matches bigger files later.

Why a file instead of the sample_lines list from Lesson 2? Because defenders inherit text from disk. Creating the log yourself is the ethics control: you know every line is fictional, you know it holds no customer data, you can chmod 600 without locking a system file. Copying a real auth log from a server you administer at work can still be a policy violation — leave work data at work. The lab log includes both failures and one success so you can see the filter drop the Accepted line. If all four lines printed, your needle is wrong or the if is missing.

2. The fictional log: failed lines, one accept, documentation IPs — then mode 600

Three Failed password lines and one Accepted password line are enough to prove the filter. Users root and admin are common bait in fiction; they are not an invitation to attack a live box. Source 10.0.0.5 repeating twice sets up the optional count challenge: same string, two lines, still not a geolocation OSINT hunt. 203.0.113.10 is from TEST-NET-3 (RFC 5737) — a documentation prefix, not a host you ping “to see.” After writing the file, chmod 600 so other local accounts cannot read even fictional failures. On Windows without chmod, use WSL or Git Bash, or restrict the file to your user. Do not chmod 777 to “make Python work.” Permission denied on a 600 file you own usually means you are a different user — fix the owner, do not world-read secrets.

How you create the log does not matter as much as where it lives. A shell here-doc, Path.write_text, or Notepad saved into the lab folder are all fine if the bytes are the four fictional lines and the path is $HOME/cyberlium-lab/sample_auth.log. What fails is grabbing “a real log from the internet” or from a SIEM export so the lab feels grown-up. Real logs contain other people’s names, source IPs, and sometimes secrets. This course uses fiction so you can share a screenshot of output without leaking a workplace. If you already have a log from YOUR own homelab VM that you own end-to-end, you may substitute it privately — still chmod 600, still never gist it, still never add scan steps against those IPs.

3. Wrong vs right: detonating real logs vs a locked fictional sample_auth.log

Worked failure — stealing a production log to feel professional. Right is fiction you wrote, filter, chmod 600, no scan.

  • Wrong

    Copy /var/log/auth.log or a cloud export with real usernames into the repo. Open /etc/shadow as the input path. Skip with and encoding. Run the script against a relative sample_auth.log from a random cwd and hunt the wrong file. Publish the log to a public gist. chmod 644 on a shared PC. Add socket connects to 10.0.0.5 because it appeared in a line. Spray a wordlist at SSH. Any of that fails the lab and the ethics.

  • Right

    Write fictional lines into $HOME/cyberlium-lab/sample_auth.log, chmod 600, point find_failed.py at that Path, print matches only. Optional: count how many printed lines contain 10.0.0.5 — still print, still local. Confirm three failed lines, not the Accepted one. Next lesson is Quiz — Python Basics, then sockets on localhost — not a scan of the IPs in the story.

4. Hands-on: create the log, write the hunter, run, lock, optional count

Work top to bottom in the code block. If python is missing, use python3. If the script prints zero lines, check the needle spelling and that you saved the log in the same lab directory the Path expects — Path.home() / "cyberlium-lab" / "sample_auth.log", not “wherever the IDE’s working directory is.” If it prints four lines, the if needle in line guard is missing. The optional challenge is a second accumulator: failures_from = 0, if "10.0.0.5" in line and needle in line: increment. Do not then nmap that address. Do not look it up on Shodan. It is a string in a file you wrote.

find_failed.py should import pathlib, refuse to run if the log is missing (SystemExit with the expected path), and avoid a cwd-relative open("sample_auth.log") that silently reads a different file when you launch from the IDE. Print matches as you go so you can see the hunter work; a final count line starting with # is a comment to humans, not more log data. Keep live passwords out of the file even as “examples.” A fictional Failed password line does not need a real hash or a real passphrase. If you add extra lines for practice, keep them fictional and keep the needle test honest — do not force all lines to match just to see output.

Command guide

sample_auth.log + find_failed.py — fictional log, chmod 600, print matches

DEFENSIVE lab. YOUR machine. $HOME/cyberlium-lab only. Sample lines are FICTIONAL. Do not copy production logs. NEVER open /etc/shadow, /var/log/auth.log from a system you do not own, or a roommate's files. NEVER scan the IPs in the story.

Command — copy this

mkdir -p "$HOME/cyberlium-lab"
cd "$HOME/cyberlium-lab"

Command — copy this

cat > sample_auth.log << 'EOF'
sshd: Failed password for root from 10.0.0.5 port 51234
sshd: Accepted password for alice from 10.0.0.9 port 51111
sshd: Failed password for admin from 10.0.0.5 port 51235
sshd: Failed password for root from 203.0.113.10 port 44022
EOF

Command — copy this

chmod 600 sample_auth.log

Command — copy this

cat > find_failed.py << 'EOF'
from pathlib import Path

lab = Path.home() / "cyberlium-lab"
path = lab / "sample_auth.log"
needle = "Failed password"

if not path.is_file():
    raise SystemExit(f"missing {path} — create the fictional log first")

hits = 0
from_10 = 0
with path.open("r", encoding="utf-8") as f:
    for line in f:
        if needle in line:
            print(line.strip())
            hits += 1
            if "10.0.0.5" in line:
                from_10 += 1

print(f"# matched {hits} line(s); optional 10.0.0.5 count={from_10}")
# NEVER: path = Path("/var/log/auth.log") or Path("/etc/shadow")
# NEVER: socket.connect to IPs in the log
# NEVER: paste live passwords into sample_auth.log
EOF

Command — copy this

python find_failed.py || python3 find_failed.py
chmod 600 find_failed.py sample_auth.log

Windows without chmod: WSL/Git Bash, or restrict the files in your profile.

Expected: three Failed password lines printed; Accepted line omitted. Optional count: two lines mention 10.0.0.5 — still not a scan target.

Mission: find_failed.py on fictional sample_auth.log (mode 600)

1) Create $HOME/cyberlium-lab/sample_auth.log with fictional failed/accepted lines (no production logs, no live passwords) and chmod 600. 2) Write find_failed.py that streams the file with with open / pathlib and prints lines where needle in line. 3) Run it and confirm three Failed password lines (not the Accepted one). Optional: count failures that mention 10.0.0.5 — do not scan that IP. chmod 600 the script too.

Stuck? Ask Cyberlium AI Mentor

If zero lines print, or all four print, ask for a hint — not a path to /var/log. Try: "Hint only: why must needle in line drop the Accepted line, why Path.home() / cyberlium-lab / sample_auth.log beats a relative open, and why chmod 600 on a fictional log still matters?" You still write the files. No production logs. No connects to 10.0.0.5.

You ran the hunter pattern on a log you authored: context manager, utf-8, substring filter, locked files. Three fictional failures printed; the accept did not. The optional IP count was string tally, not reconnaissance. Next — Quiz — Python Basics — ten APPLY items on why Python, types and loops, files, this lab, and the ethics line before Module 2 opens Socket Programming on localhost.

Knowledge Check

1

APPLY: find_failed.py uses if needle in line on your sample_auth.log. What does that check, and what must you not use as path?

Multiple choice

Knowledge Check

2

APPLY: You chmod 600 sample_auth.log after writing fictional Failed password lines. Why, and is this the same pattern used in real log hunting?

Multiple choice

Knowledge Check

3

APPLY: True or False: Printing matching lines from a fictional sample_auth.log you created is unauthorized access, so you should instead copy /var/log/secure from a server at work into a public gist to finish the lab.

True or False

← Previous

Answer all 3 knowledge checks to continue. (0/3 answered)