Weekly security automation practice
This repository contains my Champlain College SYS-320 systems automation and security scripting coursework. It is organised as weekly classwork and homework from Week01 through Week13.
The course used repeated scripting tasks rather than one final application. Each week introduced another data source or administrative problem, then required a Python or PowerShell script to collect, parse, transform, or report it.
The recurring workflow was:
- open a log, CSV file, remote host, or Windows object
- isolate the fields relevant to the question
- normalise repeated values
- produce output that could be checked by an analyst
- turn the one-off task into a reusable function or command-line tool
The repository records that progression in the code itself. The first exercises assume one known log file and fixed field positions. Later scripts accept command-line arguments, walk complete directory trees, load search definitions from YAML, connect to remote systems, and generate files for other administrative tools. Some submissions are complete and usable within their teaching environment; others preserve unfinished branches, incorrect assumptions, or experiments that I would now implement differently.
Parsing Linux and Apache logs
The first Python exercises worked with Linux authentication and service logs. I wrote functions that opened each file, applied regular expressions, collected matches, removed duplicates, and sorted the result.
Separate scripts searched for:
- SSH authentication activity
suattempts- FTP connections
- usernames and source addresses
- terms supplied by the calling script
The Week01 implementation split this into several short modules. A generic _syslog() function accepted a filename and regular expression, read the file line by line, and accumulated each match. sshUsers.py then split the matching authentication records to extract usernames, while userIP.py used a separate grep-style helper to locate a user and select the address from the resulting fields.
This was deliberately narrow parsing against a known sample. It showed how quickly an analyst can extract a useful answer from a log, but it also exposed the fragility of treating a space-delimited record as though every field will always be in the same position. A different syslog prefix, an extra field, or multiple matching lines could change the output. A maintained parser would identify named fields, retain the complete source record, and report malformed input instead of silently indexing the wrong value.
The second week moved to Apache access and proxy logs. I split matching lines into fields and returned the client address, timestamp, request, status, and related values in a compact form. Class and homework notebooks were used to test the functions against different query strings and request patterns.
Search terms were moved into searchTerms.yaml, separating the patterns from the Python functions. The Apache and proxy formats needed different output fields, so the script used dedicated search_bytes() and search_proxy() paths over a shared log-search function. Results were sorted and, where appropriate, converted to a set before printing.
Week04 extended the same idea into a command-line parser. It accepted a directory, service, term, and YAML file, recursively enumerated the files beneath that directory, ran the appropriate parser against each one, and printed a file-by-file result block. The script therefore moved beyond answering one notebook question: it modelled a small analysis utility with input validation, recursive discovery, configuration, format-specific handling, and explicit “No Results” output.
The surviving Week04 code also contains a useful control-flow defect. Its else belongs to the second service test, so a web search can enter both the web-specific and generic branches. The log function also appends matches outside the inner keyword loop, retaining only the final expression’s result in some cases. Those faults are visible examples of why parser tests need to cover multiple services, multiple expressions, empty matches, and malformed records rather than only the expected classroom sample.
Working with URLHaus data
The URLHaus exercise used Python’s csv module to process a supplied threat-intelligence dataset. The function skipped the metadata rows, searched the URL column with a caller-supplied regular expression, and printed the matching URL with its source information.
Before printing, the script changed http to hxxp. That prevented terminals and documentation tools from automatically turning a recorded malicious URL into a clickable link.
The exercise also required correcting a supplied broken script. I replaced a hard-coded filename with the function argument, used csv.reader() with the expected delimiter, skipped the nine header and metadata rows, removed an unnecessary loop, searched the URL field rather than the whole row, and corrected the separator formatting. The output paired the defanged URL with the feed’s source-information field.
The parser assumed the teaching dataset retained the same column order and number of introductory rows. Schema-aware handling would validate the header names before selecting fields and would preserve additional context such as status or discovery date. Even in its compact form, the exercise established a pattern I reused later: ingest an external dataset, make the indicators safer to handle, filter them with a caller-selected expression, and retain enough context for a human to interpret the result.
Configurable Windows event searches
By Week05 I was working with exported Windows process-event data. The main forensics.py script used argparse to accept:
- a directory containing CSV files
- a programme or technique category
- search terms
- a YAML configuration file
It walked the directory tree, passed each CSV file to a helper library, and printed normalised results in the format:
arguments | hostname | path | PID | username
The tool grouped its searches into registry activity, script execution, and PowerShell. The YAML configuration held expressions for artefacts such as authentication-package registry values, mshta, VBScript and JavaScript execution, regsvr32, PowerShell module extensions, invocation expressions, web-client use, execution-policy terms, credential strings, and process-dump references. The point was not to declare every match malicious. It was to turn broad process telemetry into a smaller set of records for review.
Before listing matches, the program printed a description of the selected activity class. A registry search explained why authentication-related values could warrant inspection; the script category described interpreters and script hosts; the PowerShell category established that normal administrative capability can also appear in suspicious execution. This gave the output an investigative context instead of presenting an unexplained list of strings.
The helper split matching CSV rows and selected a fixed set of columns. That produced consistent analyst-facing output for the supplied dataset, but it did not use Python’s CSV parser and could therefore mishandle quoted commas. It also applied regular expressions directly from YAML and retained only the final expression’s matches in one loop. A stronger version would parse headers by name, compile and test each configured expression, label every hit with the rule that produced it, and distinguish an indicator match from a confirmed security event.
File metadata and bodyfile work
The forensic exercises introduced bodyfiles and timeline-oriented metadata. A Python script connected to a Linux host over SSH, opened an SFTP session, enumerated files under /usr/bin, and collected:
- inode
- mode
- user and group identifiers
- file size
- access time
- modification time
- metadata-change time
The intended record placed the path, inode, mode, user and group identifiers, size, and four time values into a pipe-delimited bodyfile-style line. That structure is designed to feed timeline tooling rather than remain as terminal output. It linked ordinary filesystem metadata to a forensic workflow: collect consistently, preserve the source path, then sort or correlate the resulting times elsewhere.
The surviving script is incomplete at the point where the remote file list should be converted into records. Its statFile() function calls local os.stat() rather than sftp.stat(), the collection loop has no body, and the results list is written as though it were already a string. It still records the intended data model, remote path, password-prompt flow, and command-line interface.
A complete implementation would request metadata through SFTP, preserve one record per remote path, normalise timestamps, handle symbolic links deliberately, write to a new evidence file, and close both SFTP and SSH sessions in a finally block. Host-key verification and collection logging would also be required before treating the result as forensic evidence.
Remote threat-hunting exercise
Another Python exercise used Paramiko to transfer a supplied Linux hunting binary to a remote lab host, execute it against system binary directories, and save the returned output locally.
The script handled:
- target, username, and port arguments
- password input through
getpass - SSH and SFTP sessions
- remote execution
- exit-status and error capture
- local result storage
The script prompted for the password with getpass, opened SSH and SFTP channels, copied the supplied kraken binary to the lab account, executed it across common binary directories, waited for an exit status, joined standard output, and wrote the result to results.txt. Standard error was retained when the remote command failed.
The archived version accepted a previously unknown SSH host key automatically and changed the transferred file to a broadly permissive mode. It also used a fixed remote path and did not remove the binary after collection. Those choices suited a disposable teaching environment, but a maintained collection tool would verify the host key, restrict permissions, use a unique temporary path, validate the transferred hash, record command and host metadata, and remove the remote artefact through a controlled cleanup path.
PowerShell and Windows automation
The second half of the repository moved into PowerShell. An early exercise enumerated running processes and system services, selected a smaller set of properties, exported them to CSV, filtered for running services, and used Test-Path to confirm that the output file had been created. This brought the same collect-transform-export pattern into Windows administration.
The script requested process name, identifier, and executable path, then service status, name, display name, and binary-path information. The archived version contains a mistyped automatic variable in the running-service filter and exports different service views to the same filename. Both are straightforward defects, but they show why an automation task needs output naming, validation, and a test against the resulting records rather than only a file-existence check.
One script downloaded two public blocklists, extracted IPv4 addresses with a regular expression, sorted and deduplicated them, then generated an iptables rule file. The pipeline was:
download -> parse -> extract -> normalise -> deduplicate -> render
The PowerShell version checked whether each feed file already existed before downloading it. Select-String extracted address-shaped values from both files; the matches were sorted, deduplicated, and written to bad-ips.txt; a final transformation wrapped each value in an iptables rule. The homework variant could instead render inbound and outbound Windows Firewall commands, and its Linux branch offered to copy the generated script to a selected server over SCP for remote execution.
The generated files were exercise outputs, not a complete firewall-management system. The address expression accepted any four one-to-three-digit octets, so it did not validate the IPv4 range. Existing feed files could also become stale because they were skipped rather than refreshed. A production workflow would validate the feed and every address, attach source and observation time, expire indicators, stage changes, test for legitimate overlap, provide rollback, and keep the generated policy under review.
Other PowerShell work included:
- collecting a named file into a report-numbered evidence directory
- enumerating files by type
- processing CSV input
- querying Windows event and process information
- generating scripts from supplied indicator data
- using remote administration commands across Windows systems
The file-collection script accepted a mandatory integer report number and source path, created a directory such as rpt42, and copied the requested file into it. It was a compact command-line utility built around PowerShell parameter binding. A forensic collection workflow would add source and destination hashes, UTC timestamps, operator and host details, error handling, collision-safe naming, chain-of-custody metadata, and read-only preservation.
Another exercise recursively listed selected document formats and exported their full paths to CSV before processing them. The surviving class script stops at an incomplete command, while the homework developed the idea into a controlled simulation of suspicious file-processing behaviour. That later work combined file discovery, generated PowerShell, cryptographic file transformation, process creation, and filesystem artefacts. I retain it here as evidence of analysing how scriptable administrative features can be combined into behaviour that defenders need to detect, not as reusable operational tooling.
From a monitoring perspective, those exercises connect to script-block logging, process creation, command-line capture, Defender configuration changes, unusual executable copies or renames, bulk file access, archive creation, extension changes, remote transfer, and recovery-note artefacts. The source also contains hard-coded paths, a fixed teaching key, broad assumptions, and incomplete checks. Those are useful forensic signals in the coursework itself and clear reasons not to treat it as deployable code.
From scripts to analyst workflows
The repository shows the same operation becoming more structured over the semester. The first scripts searched one known file. Later work accepted command-line arguments, walked directory trees, loaded configuration, connected to remote systems, processed multiple formats, and generated output for another tool.
That progression supports SOC and incident-response work directly:
- logs become searchable fields
- threat feeds become normalised indicators
- remote hosts become collection targets
- process events become technique-focused results
- file metadata becomes timeline input
- repeated terminal steps become documented scripts
The weekly format also made faults visible. Fixed column positions, unvalidated regular expressions, permissive SSH settings, hard-coded paths, assignment used where comparison was intended, and incomplete loops all appear in the archive. Reviewing them now is part of the evidence: I can identify where a script trusts its input, where it loses provenance, and where a successful-looking output does not prove that the underlying operation was correct.
Across the thirteen weeks I repeatedly moved raw operational data into a form that could support investigation or administration. That included authentication logs, web records, threat-intelligence CSVs, Windows process events, remote filesystem metadata, command output, blocklists, services, and collected files. It established the scripting habits I later applied to SOC and incident-response work: define the question, preserve context, automate the repetitive handling, validate the result, and leave an output another analyst can inspect.
