Archive

SYS-320 Security Automation Coursework

Historical Champlain College coursework focused on weekly Python and PowerShell scripting for system automation, log parsing, and defensive security practice.

  • Python
  • PowerShell
  • Linux
  • Windows
  • Log Analysis
On this page

Coursework snapshot

Coursework scope
Individual SYS-320 weekly scripting work using supplied logs, feeds, systems and investigation scenarios.
Work completed
I wrote Python and PowerShell scripts for syslog and web-log parsing, URLHaus processing, configurable Windows event searches, metadata extraction, remote hunting and evidence collection.
Automation focus
  • Python log parsing
  • PowerShell automation
  • YAML configuration
  • Threat-intelligence data
  • Windows events
  • Remote evidence collection
What it demonstrates
I progressed from one-off parsing exercises towards configurable analyst workflows, while the parser defects, incomplete collectors and lab-only defaults keep the work firmly in its 2022 coursework context.

Weekly security automation practice

I completed SYS-320 at Champlain College as a sequence of systems-automation and security-scripting exercises. The work progressed from small log searches into configurable parsing, remote collection, threat hunting, PowerShell administration and generated control output.

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:

  1. open a log, CSV file, remote host, or Windows object
  2. isolate the fields relevant to the question
  3. normalise repeated values
  4. produce output that could be checked by an analyst
  5. turn the one-off task into a reusable function or command-line tool

The code shows that progression. 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. The work also includes unfinished branches, incorrect assumptions, and experiments that I would now implement differently.

The examples below focus on the steps that best show that progression, including a later local re-run of the Week05 event parser against its supplied process-event data. They do not present the old scripts as a maintained toolkit or a modern detection benchmark.

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
  • su attempts
  • 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 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, keeping 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 last_online value.

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 homework file records the corrections made to a provided 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 last_online field.

The implementation makes that transformation explicit:

with open(filename) as f:
    contents = csv.reader(f, delimiter=',')

    for _ in range(9):
        next(contents)

    for eachLine in contents:
        x = re.findall(r"" + searchTerms + "", eachLine[2])
        for _ in x:
            the_url = eachLine[2].replace("http", "hxxp")
            the_src = eachLine[4]

One original notebook result, with the historical indicator removed, had this shape:

URL: hxxp://<historical-indicator>/.win32.exe
Info: 2022-01-25 01:XX:XX

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. The current Python csv documentation also recommends opening CSV files with newline='' and provides DictReader for mapping values by header rather than numeric position. 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 | process | path | PID | username

I later re-ran the script against the supplied process-event directory. A sanitised result looked like this:

C:\Windows\System32\mshta.exe javascript:... | <host-id> | mshta.exe | C:\Windows\System32\mshta.exe | 5652 | NT AUTHORITY\SYSTEM

That re-run establishes that the parser can still load the supplied YAML and emit its intended record shape. It does not establish that every configured expression is evaluated correctly.

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 the intended analyst-facing shape 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 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 corresponding notebook followed the scanner result with process and file context. Kraken labelled both ls and snapd as foundGoBinary; I then used lsof to inspect the ls process. With lab account and process identifiers removed, the correlated output included:

WARN DETECTION! Malicious process detected as foundGoBinary process=ls

COMMAND  PID    USER        FD   TYPE  NAME
ls       <pid>  <lab-user>  5u   IPv6  TCP *:http-alt (LISTEN)

The follow-up also placed that executable under /usr/local/bin and showed open handles for a hidden log and PID file in the supplied scenario. This supports the claim that the workflow moved from a tool hit to process and file context. It does not prove that every Kraken label was correct: the simultaneous snapd result shows why the detection category needed analyst validation.

The 2022 script 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. Paramiko’s current SSHClient documentation confirms that rejecting unknown host keys remains the default policy; AutoAddPolicy explicitly opts out of that check.

PowerShell and Windows automation

The second half of the course 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. It 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 exercise generated 886 decoded rule lines in this form:

iptables -A INPUT -s <indicator> -j DROP

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. The generated iptables.sh is UTF-16 encoded, and the homework wrote iptables.bash before referring to iptables.sh in its remote command. The exercise rendered rule text but did not validate successful application of the Linux policy. 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 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 use it here to explain how scriptable administrative features can combine 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 scripts also contain 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.

Current PowerShell retains security features relevant to that exercise, including module logging, script-block logging and AMSI integration, as described in Microsoft’s current PowerShell security guidance. That does not make the 2022 simulation a current detection test: its paths, Windows configuration and teaching scenario were fixed, and no modern repeat has been performed.

Limitations and current relevance

Testing boundary

The notebooks recorded results from the early log searches, URLHaus processing, SSH collection and threat-hunting stages; the PowerShell exercise generated its rule text; and I later re-ran the Week05 parser against the supplied data. The incomplete bodyfile collector and the filename, encoding and control-flow defects remain failures rather than being presented as successful results.

I did not perform a modern rerun of the Week12 and Week13 simulation. It transforms files, changes Defender preferences and depends on an environment-specific transfer path, so this page discusses its design and detection implications without claiming a current runtime result.

Historical code, not a current baseline

The logs, URLHaus export and blocklists date from 2022. Their addresses, domains, status values and detection labels are historical observations and must not be used as current reputation or firewall intelligence. The scripts also depend on fixed schemas, absolute paths, broad regular expressions and commands from their original teaching environments.

What remains current is the engineering lesson: retain provenance, parse structured data with schema-aware tools, label the rule behind each match, test empty and malformed cases, verify remote identities, restrict permissions, and distinguish generated output from an applied control. Those principles are more durable than any one historical indicator or command.

Public-detail boundary

The code excerpts are sanitised. Historical lab addresses, other students’ names, personal email addresses, a fixed teaching key and unnecessary operational detail from the file-transformation exercise are deliberately omitted.

From scripts to analyst workflows

The same operation became 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 maps clearly to SOC and incident-response work:

  • 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 appeared in the coursework. They make the progression more useful: I can identify where a script trusts its input, where it loses context, and where a successful-looking output does not prove that the underlying operation was correct.

Across the weekly work 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 provides an early record of the habits that matter in later 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.