PRACTICE LAB 4

Python Security Automation Toolkit

Develop an automated Python security command-line toolkit incorporating socket-based port scanner check engines, log analyzer regex parsers, and text logs reports exporters.

Environment
Python Terminal Environment
Difficulty
Beginner (Level 3)
Course Module
Programming for Security
Deliverables
Python CLI script and Scanned Logs
1. System Architecture & Workflow

The diagram below shows the architecture of our Python Security Toolkit. The toolkit connects to target IP hosts using TCP sockets to determine which ports are open (port scanner module), and also reads authentication log files using regex patterns to flag suspicious IPs with repeated login failures (log analyzer module). Both modules are controlled through a single CLI menu interface.

Architecture Diagram
2. Part 1: Step-by-Step Action Items & Key Execution Steps
STEP 1

Create the Project Directory and Python Script File

First, you need to create a dedicated folder for this project and an empty Python file inside it.

  1. Open your Kali Linux VM inside VirtualBox. Wait for the desktop to fully load.
  2. Click on the Terminal Emulator icon in the top taskbar (black rectangular screen icon). A terminal window opens.
  3. Create a new project directory by typing the following command and pressing Enter:
$ mkdir -p ~/security-scripts
This command creates a new directory called security-scripts inside your home folder. The -p flag means "create parent directories if needed" and prevents errors if the directory already exists.
PartWhat It Does
mkdirMake Directory — creates a new folder
-pParents — creates parent directories as needed and doesn't error if the directory already exists
~/security-scripts~ is a shortcut for your home directory (e.g., /home/kali). So this creates /home/kali/security-scripts
  1. Navigate into the new directory:
$ cd ~/security-scripts
This changes your current working directory to the security-scripts folder so all subsequent commands and file operations happen inside this project folder.
  1. Create a new empty Python file called toolkit.py:
$ touch toolkit.py
The touch command creates a new empty file. If the file already exists, it updates its modification timestamp without changing its contents.
  1. Create a sample log file that simulates SSH login failures for testing:
$ nano auth.log
This opens the nano text editor and creates a new file called auth.log. You will paste sample log data into this file.
  1. The nano editor opens with an empty file. Type (or paste) the following sample log lines:
Copy
Jun 15 10:22:31 server sshd[1234]: Failed password for root from 192.168.1.50 port 22 ssh2
Jun 15 10:22:35 server sshd[1235]: Failed password for admin from 10.0.0.99 port 22 ssh2
Jun 15 10:23:01 server sshd[1236]: Failed password for root from 192.168.1.50 port 22 ssh2
Jun 15 10:23:15 server sshd[1237]: Accepted password for user1 from 172.16.0.10 port 22 ssh2
Jun 15 10:24:02 server sshd[1238]: Failed password for root from 192.168.1.50 port 22 ssh2
Jun 15 10:24:30 server sshd[1239]: Failed password for admin from 10.0.0.99 port 22 ssh2
Jun 15 10:25:11 server sshd[1240]: Failed password for root from 192.168.1.50 port 22 ssh2
Jun 15 10:26:05 server sshd[1241]: Failed password for admin from 10.0.0.99 port 22 ssh2
Jun 15 10:26:45 server sshd[1242]: Failed password for admin from 10.0.0.99 port 22 ssh2
  1. After pasting the log data, press Ctrl + O to save the file.
  2. Press Enter to confirm the filename.
  3. Press Ctrl + X to exit nano and return to the terminal.
What you should see: You're back at the terminal prompt. If you run ls, you should see both toolkit.py and auth.log listed.
STEP 2

Build the Port Scanner Module — Line by Line

The port scanner connects to a target IP address on specified ports using TCP sockets. If the connection succeeds, the port is OPEN (a service is listening). If it fails or times out, the port is CLOSED.

  1. Open the toolkit.py file in nano:
$ nano toolkit.py
Opens the empty toolkit.py file in the nano text editor so you can start writing the Python code.

Now let's understand each line of the port scanner before typing it:

Code Breakdown: Port Scanner Function
# toolkit.py - Python Security Automation Toolkit
Line 1 — Comment: A comment describing the file. The # symbol tells Python to ignore everything after it on this line. This helps other developers (and your future self) understand what the file does.
import argparse
Line 2 — Import argparse: Imports Python's built-in argparse module, which provides tools for parsing command-line arguments. This lets users run the script with flags like --scan and --ports instead of hardcoding values. Think of it as creating a menu system for your terminal command.
import socket
Line 3 — Import socket: Imports Python's socket module, which provides low-level networking operations. A "socket" is like a virtual plug that connects two computers over a network. We use it to attempt TCP connections to target ports — if the connection succeeds, the port is open.
import re
Line 4 — Import re: Imports Python's re (Regular Expressions) module. Regular expressions are powerful text-matching patterns. We use them to search through log files and find lines matching specific patterns (like "Failed password from [IP address]").
from collections import Counter
Line 5 — Import Counter: Imports the Counter class from Python's collections module. Counter is a special dictionary that automatically counts how many times each item appears in a list. We use it to count how many failed login attempts each IP address has.
def scan_ports(target_ip, ports):
Line 6 — Function Definition: Creates a new function called scan_ports that accepts two parameters: target_ip (the IP address to scan, like "192.168.56.20") and ports (a list of port numbers to check, like [21, 22, 80, 445]).
print(f"[*] Scanning target {target_ip}...")
Line 7 — Status Message: Prints an informational message showing which IP address is being scanned. The f"..." is an f-string that inserts the value of target_ip into the message. The [*] prefix is a security tool convention meaning "informational".
for port in ports:
Line 8 — For Loop: Starts a loop that iterates through each port number in the ports list. For example, if ports is [21, 22, 80, 445], this loop runs 4 times — once for each port.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Line 9 — Create Socket: Creates a new network socket object. socket.AF_INET means we're using IPv4 addresses (like 192.168.x.x). socket.SOCK_STREAM means we're using TCP protocol (reliable, connection-oriented). Together, this creates a TCP/IPv4 socket — the same type your browser uses to connect to websites.
s.settimeout(1.0)
Line 10 — Set Timeout: Sets the maximum time (in seconds) to wait for a connection attempt. If the target doesn't respond within 1 second, the connection attempt is aborted and the port is considered closed. Without this, the script could hang for minutes waiting for unresponsive ports.
result = s.connect_ex((target_ip, port))
Line 11 — Attempt Connection: Tries to connect to the target IP on the specified port. connect_ex() is different from connect() — instead of throwing an error on failure, it returns a numeric error code. 0 means success (port is open), any other number means failure (port is closed or filtered). The target is passed as a tuple (ip, port).
if result == 0:
Line 12 — Check Result: Checks if the connection succeeded. A return value of 0 means the TCP three-way handshake completed successfully — the port is open and a service is listening on it.
print(f"[+] Port {port}: OPEN")
Line 13 — Report Open Port: Prints a success message showing which port is open. The [+] prefix means "positive finding". This output tells you a service is actively listening on that port number.
s.close()
Line 14 — Close Socket: Closes the socket connection to free up system resources. Every opened socket should be closed after use — leaving sockets open can exhaust your system's available connections.
Code Breakdown: Log Parser Function
def parse_logs(log_path, threshold):
Line 15 — Function Definition: Creates the parse_logs function with two parameters: log_path (the file path to the log file, like "auth.log") and threshold (the minimum number of failures before triggering an alert, like 3).
print(f"[*] Parsing log file: {log_path}...")
Line 16 — Status Message: Prints which log file is being analyzed.
fail_pattern = re.compile(r"Failed password for.*from (\d+\.\d+\.\d+\.\d+)")
Line 17 — Compile Regex Pattern: Creates a pre-compiled regular expression pattern that matches log lines containing "Failed password for" followed by an IP address. The (\d+\.\d+\.\d+\.\d+) part captures the IP address: \d+ matches one or more digits, \. matches a literal dot. The parentheses () create a "capture group" so we can extract just the IP address from each matching line.
failed_ips = []
Line 18 — Empty List: Creates an empty list to store all IP addresses found in failed login attempts. As we scan each line of the log file, matching IPs will be appended to this list.
with open(log_path, "r") as f:
Line 19 — Open File: Opens the log file in read mode ("r"). The with keyword ensures the file is automatically closed when we're done, even if an error occurs. The file object is assigned to variable f.
for line in f:
Line 20 — Read Lines: Loops through each line in the log file, one at a time. This is memory-efficient because it doesn't load the entire file into memory at once.
match = fail_pattern.search(line)
Line 21 — Search Pattern: Applies the regex pattern to the current line. If the line contains "Failed password for...from [IP]", match will be a match object containing the captured IP. If the line doesn't match (like a successful login), match will be None.
if match:
Line 22 — Check Match: Checks if the regex found a match on this line. In Python, None is "falsy" and match objects are "truthy", so this only executes if the line contained a failed login attempt.
failed_ips.append(match.group(1))
Line 23 — Extract IP: match.group(1) extracts the text captured by the first set of parentheses in our regex pattern — which is the IP address. .append() adds this IP to the end of our failed_ips list.
counts = Counter(failed_ips)
Line 24 — Count IPs: Creates a Counter object that automatically tallies how many times each IP address appears in the list. For example, if "192.168.1.50" appears 4 times, counts["192.168.1.50"] will be 4.
for ip, count in counts.items():
Line 25 — Iterate Counts: Loops through each IP address and its failure count. .items() returns pairs of (key, value), so ip gets the address and count gets the number of failures.
if count >= threshold:
Line 26 — Threshold Check: Checks if this IP's failure count meets or exceeds the alert threshold. For example, if threshold is 3 and this IP failed 4 times, the condition is true and an alert is triggered.
print(f"[ALERT] IP {ip} reached {count} login failures!")
Line 27 — Alert Output: Prints a security alert showing the offending IP address and how many failed login attempts it made. The [ALERT] prefix signals a critical finding that requires attention.
Code Breakdown: CLI Menu Interface
if __name__ == "__main__":
Line 28 — Main Guard: This checks if the script is being run directly (not imported). __name__ is a special Python variable that equals "__main__" when you run the file with python toolkit.py.
parser = argparse.ArgumentParser(description="Security Toolkit")
Line 29 — Create Argument Parser: Creates an ArgumentParser object that will handle command-line arguments. The description parameter sets the help text shown when users run python toolkit.py --help.
parser.add_argument("--scan", help="Target IP to scan")
Line 30 — Add --scan Flag: Registers a command-line flag called --scan that accepts a target IP address. When the user runs python toolkit.py --scan 192.168.56.20, the value "192.168.56.20" is stored in args.scan.
parser.add_argument("--ports", help="Comma-separated ports")
Line 31 — Add --ports Flag: Registers a --ports flag for specifying which ports to scan, as a comma-separated list (e.g., "21,22,80,445").
parser.add_argument("--analyze", help="Path to log file")
Line 32 — Add --analyze Flag: Registers an --analyze flag for specifying the log file path to parse for failed logins.
parser.add_argument("--threshold", type=int, default=3, help="Failure threshold")
Line 33 — Add --threshold Flag: Registers a --threshold flag with type=int (converts input to an integer) and default=3 (uses 3 if the user doesn't specify a value).
args = parser.parse_args()
Line 34 — Parse Arguments: Processes the actual command-line input and stores all the flag values in the args object. After this, you can access values like args.scan, args.ports, args.analyze, and args.threshold.
if args.scan and args.ports:
Line 35 — Check Scan Mode: Checks if the user provided both --scan and --ports flags. Both are required for port scanning — you need a target IP AND a list of ports to check.
port_list = [int(p) for p in args.ports.split(",")]
Line 36 — Parse Port List: Converts the comma-separated port string (like "21,22,80,445") into a list of integers ([21, 22, 80, 445]). .split(",") splits the string at each comma, and int(p) converts each piece from a string to a number. This is a "list comprehension" — Python's compact way of creating lists.
scan_ports(args.scan, port_list)
Line 37 — Call Scanner: Calls the scan_ports function with the target IP and parsed port list.
elif args.analyze:
Line 38 — Check Analyze Mode: If the user didn't provide scan flags but did provide --analyze, switch to log analysis mode instead.
parse_logs(args.analyze, args.threshold)
Line 39 — Call Parser: Calls the parse_logs function with the log file path and the failure threshold value.
else:
Line 40 — Else Default: If the user didn't provide any valid flags, show the help menu.
parser.print_help()
Line 41 — Show Help: Prints the automatically-generated help text showing all available command-line flags and their descriptions, so the user knows how to use the toolkit.
✓ Complete Combined Script: Now that you understand every line, here is the full script combined. Paste all the code below into your toolkit.py file.
4. Part 2: Complete Deliverable Script
  1. Open toolkit.py in nano: nano toolkit.py
  2. Delete any existing content (if any) by pressing Ctrl + K repeatedly until the file is empty.
  3. Copy the complete script below and right-click → Paste it into nano:
Copy
# toolkit.py - Python Security Automation Toolkit
import argparse
import socket
import re
from collections import Counter

def scan_ports(target_ip, ports):
    print(f"[*] Scanning target {target_ip}...")
    for port in ports:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1.0)
        result = s.connect_ex((target_ip, port))
        if result == 0:
            print(f"[+] Port {port}: OPEN")
        s.close()

def parse_logs(log_path, threshold):
    print(f"[*] Parsing log file: {log_path}...")
    fail_pattern = re.compile(r"Failed password for.*from (\d+\.\d+\.\d+\.\d+)")
    failed_ips = []
    with open(log_path, "r") as f:
        for line in f:
            match = fail_pattern.search(line)
            if match:
                failed_ips.append(match.group(1))
    
    counts = Counter(failed_ips)
    for ip, count in counts.items():
        if count >= threshold:
            print(f"[ALERT] IP {ip} reached {count} login failures!")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Security Toolkit")
    parser.add_argument("--scan", help="Target IP to scan")
    parser.add_argument("--ports", help="Comma-separated ports")
    parser.add_argument("--analyze", help="Path to log file")
    parser.add_argument("--threshold", type=int, default=3, help="Failure threshold")
    args = parser.parse_args()

    if args.scan and args.ports:
        port_list = [int(p) for p in args.ports.split(",")]
        scan_ports(args.scan, port_list)
    elif args.analyze:
        parse_logs(args.analyze, args.threshold)
    else:
        parser.print_help()
  1. Press Ctrl + O to save, then Enter to confirm, then Ctrl + X to exit nano.
  2. Now test the port scanner by scanning your own machine:
$ python toolkit.py --scan 127.0.0.1 --ports 22,80,443,8080
This runs the port scanner against your local machine (127.0.0.1 = localhost) on common ports: 22 (SSH), 80 (HTTP), 443 (HTTPS), 8080 (Alt HTTP). Any services running on these ports will be reported as OPEN.
Expected Output:
[*] Scanning target 127.0.0.1...
[+] Port 22: OPEN (if SSH is running)
  1. Now test the log analyzer against the sample auth.log file:
$ python toolkit.py --analyze auth.log --threshold 3
This reads the auth.log file, searches for failed login patterns using regex, counts the failures per IP, and alerts on any IP with 3 or more failures.
FlagWhat It Does
--analyze auth.logSpecifies the log file to parse
--threshold 3Only alert if an IP has 3+ failed attempts
Expected Output:
[*] Parsing log file: auth.log...
[ALERT] IP 192.168.1.50 reached 4 login failures!
[ALERT] IP 10.0.0.99 reached 4 login failures!
3. Automation Architecture

The diagram below shows the automated execution pipeline: from accepting IP and port inputs via CLI, to opening socket connections, parsing log files with regex, triggering threshold-based alerts, and exporting text reports.

Automation Flow Diagram
5. Deliverables Summary

Created Files / Templates

  • ~/security-scripts/toolkit.py — Complete Python security toolkit with port scanner and log analyzer
  • ~/security-scripts/auth.log — Sample authentication log file for testing

Verification Artifacts

  • Terminal output showing open ports discovered by the scanner
  • Terminal output showing [ALERT] messages for IPs exceeding the failure threshold
6. Closing Explanation: Why We Did This & What It Accomplishes

Architectural Intent & Operational Impact

Why We Did This

What This Accomplishes