PRACTICE LAB 3

Linux & Windows Hardening

Enforce local system account credentials policies, disable unneeded system daemons, modify Unix file and directory permissions, audit system logs, configure Windows Local Group Policy Editor baselines, and deploy automated hardening auditing tools.

Environment
Kali Linux VM & Windows VM
Difficulty
Beginner (Level 3)
Course Module
Systems Administration & OS Hardening
Deliverables
Bash & PowerShell Audit Scripts
1. System Architecture & Workflow

The diagram below highlights the security domains for OS Hardening. Mitigating risk on Linux hosts focuses on strict permission bits (like masking /etc/shadow), auditing open ports, and disabling insecure SSH login paths, while Windows hardening relies on Group Policy objects to control password complexities, audit parameters, and firewall rules.

OS Hardening Controls Domains
2. Part 1: Step-by-Step Action Items & Key Execution Steps
STEP 1

Configure Windows Password Policy in Group Policy Editor

Launch the Local Group Policy Editor in Windows and enforce strict password parameters to resist brute-force exploitation.

  1. Log in to your Windows VM using an administrator account.
  2. On your keyboard, press the Windows Key + R to display the Run dialog.
  3. Type gpedit.msc in the Open text box and press Enter. This launches the Local Group Policy Editor GUI.
  4. In the left folder tree, navigate through the folders:
    • Computer Configuration
    • Double-click Windows Settings
    • Double-click Security Settings
    • Double-click Account Policies
    • Select the Password Policy folder.
  5. In the right-hand details pane, double-click the policy named Minimum password length.
  6. In the Properties dialog box, change the character limit value to 12 characters.
  7. Click Apply, then click OK.
  8. Double-click the policy named Password must meet complexity requirements.
  9. Select the Enabled option, then click Apply and OK. This forces accounts to use mixed-case letters, digits, and special characters.
STEP 2

Configure SSH Server Access Controls on Linux

Edit the OpenSSH service configuration files on Linux to restrict root sessions and enforce key-based access authentication.

  1. Log in to your Kali Linux VM.
  2. Open a terminal window.
  3. Execute the commands below to backup and edit the OpenSSH configurations.
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
This command copies the active OpenSSH configuration to sshd_config.bak to serve as a backup fallback if a mistake is made during editing.

4. Open the configuration file in the nano editor under administrator privilege:

$ sudo nano /etc/ssh/sshd_config
This command opens the sshd_config file in nano to modify default remote access paths.
PartWhat It Does
nanoA simple, beginner-friendly terminal text editor for creating and editing files
  1. Inside the editor window, locate the line that reads: #PermitRootLogin prohibit-password or PermitRootLogin yes.
  2. Remove the starting comment symbol (#) if present, and change the value to: PermitRootLogin no. This prevents direct brute-forcing attempts on the administrative account.
  3. Find the line that reads #PubkeyAuthentication yes, and uncomment it by deleting the # character.
  4. Press Ctrl + O and then press Enter to save the configuration changes. Press Ctrl + X to exit the nano editor.
  5. Execute the command below to reload the daemon settings:
$ sudo systemctl restart ssh
This command restarts the SSH server service to apply the PermitRootLogin restrictions to active listeners.
PartWhat It Does
systemctlSystem Control — manages systemd services (start, stop, enable, disable, check status)
restartStops and then starts the service (applies configuration changes)
STEP 3

Audit and Fix File System Permissions on Linux

Identify files that are world-writable and restrict access permissions on sensitive credential storage locations.

  1. In the Kali terminal, search for any world-writable files in public directories by running:
$ find /home/student -perm -o+w -type f 2>/dev/null
This command scans the user's home directory for files where others possess write access, helping to identify unauthorized modification paths.

2. Audit the permissions of the shadow passwords file using the command below:

$ ls -l /etc/shadow
This command displays read/write permissions for the hashed credentials database. The mask must restrict access strictly to root users.

3. If permissions allow others to read or modify the file, enforce the secure mask by executing:

$ sudo chmod 600 /etc/shadow
This command applies read/write permissions to the owner (root) and removes all read/write/execute rights for groups and other system accounts.
PartWhat It Does
chmodChange Mode — modifies file permissions (read, write, execute)
600Owner can read and write; no access for group or others (secure for private keys)
3. Automation Architecture

The diagram below highlights the scheduled auditing lifecycle. System tasks run daily via cron on Linux or Task Scheduler on Windows, running checking scripts, identifying configurations that deviate from baselines, logging warnings, and locking inactive user nodes.

Scheduled Auditing Lifecycle
4. Part 2: Complete Deliverable Assets & Production Templates

To automate OS hardening audits across your deployment infrastructure, you will write two separate automation scripts: one bash script for your Linux VM and one PowerShell script for your Windows VM.

Linux Hardening Audit Script

Save the script below inside your Kali Linux VM as /home/student/Scripts/audit_linux.sh, make it executable, and run the audit scanner:

$ chmod +x /home/student/Scripts/audit_linux.sh && /home/student/Scripts/audit_linux.sh
This command updates file execution properties and starts the Linux vulnerability audit script.

Code Breakdown — Line by Line

Copy
Line 1: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
#!/usr/bin/env bash
Line 2: This is a comment that describes what the code does: "!/usr/bin/env bash". Comments start with # and are ignored by Python.
# audit_linux.sh - Automated Linux Hardening Auditor
Line 3: This is a comment that describes what the code does: "audit_linux.sh - Automated Linux Hardening Auditor". Comments start with # and are ignored by Python.
set -euo pipefail
Line 4: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
REPORT_LOG="./linux_hardening_report.log"
Line 5: Creates a variable called REPORT_LOG and assigns a value to it. Variables store data for use later in the program.
echo "=== Linux Hardening Audit Report ===" > "${REPORT_LOG}"
Line 6: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
echo "Execution Time: $(date)" >> "${REPORT_LOG}"
Line 7: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
echo "------------------------------------" >> "${REPORT_LOG}"
Line 8: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
# 1. Check for Shadow File Permissions
Line 9: This is a comment that describes what the code does: "1. Check for Shadow File Permissions". Comments start with # and are ignored by Python.
SHADOW_PERMS=$(stat -c "%a" /etc/shadow)
Line 10: Creates a variable called SHADOW_PERMS and assigns a value to it. Variables store data for use later in the program.
if [ "${SHADOW_PERMS}" -eq 600 ] || [ "${SHADOW_PERMS}" -eq 000 ]; then
Line 11: A conditional check — the code inside this block only runs if the condition evaluates to True.
echo "[OK] Shadow file permissions are secure: ${SHADOW_PERMS}" >> "${REPORT_LOG}"
Line 12: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
else
Line 13: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
echo "[FAIL] Shadow file permissions are weak: ${SHADOW_PERMS} (Expected 600)" >> "${REPORT_LOG}"
Line 14: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
fi
Line 15: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
# 2. Verify SSH Root Login Status
Line 16: This is a comment that describes what the code does: "2. Verify SSH Root Login Status". Comments start with # and are ignored by Python.
if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
Line 17: A conditional check — the code inside this block only runs if the condition evaluates to True.
echo "[OK] SSH Remote Root Login is disabled." >> "${REPORT_LOG}"
Line 18: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
else
Line 19: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
echo "[FAIL] SSH configuration allows Remote Root Login." >> "${REPORT_LOG}"
Line 20: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
✓ Complete Combined Script: All lines explained above are combined into the full script shown below. Copy and paste the entire script into your file.
Copy
#!/usr/bin/env bash
# audit_linux.sh - Automated Linux Hardening Auditor

set -euo pipefail

REPORT_LOG="./linux_hardening_report.log"

echo "=== Linux Hardening Audit Report ===" > "${REPORT_LOG}"
echo "Execution Time: $(date)" >> "${REPORT_LOG}"
echo "------------------------------------" >> "${REPORT_LOG}"

# 1. Check for Shadow File Permissions
SHADOW_PERMS=$(stat -c "%a" /etc/shadow)
if [ "${SHADOW_PERMS}" -eq 600 ] || [ "${SHADOW_PERMS}" -eq 000 ]; then
  echo "[OK] Shadow file permissions are secure: ${SHADOW_PERMS}" >> "${REPORT_LOG}"
else
  echo "[FAIL] Shadow file permissions are weak: ${SHADOW_PERMS} (Expected 600)" >> "${REPORT_LOG}"
fi

# 2. Verify SSH Root Login Status
if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
  echo "[OK] SSH Remote Root Login is disabled." >> "${REPORT_LOG}"
else
  echo "[FAIL] SSH configuration allows Remote Root Login." >> "${REPORT_LOG}"
fi

# 3. Check for Insecure Listening Daemons
if ss -tulpn 2>/dev/null | grep -E -q ":(21|23) "; then
  echo "[FAIL] Insecure active daemons detected (FTP port 21 / Telnet port 23)" >> "${REPORT_LOG}"
else
  echo "[OK] No insecure legacy listener daemons active on FTP/Telnet ports." >> "${REPORT_LOG}"
fi

echo "[*] Hardening Audit complete. Output written to ${REPORT_LOG}."
cat "${REPORT_LOG}"

Windows Hardening Audit Script

Save the script below inside your Windows VM as C:\Scripts\AuditHardening.ps1 and run it inside an Administrator PowerShell prompt window:

PS> C:\Scripts\AuditHardening.ps1
This runs the Windows security compliance verification script, parsing service states and registry locks.

Code Breakdown — Line by Line

Copy
Line 1: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
# AuditHardening.ps1 - Automated Windows Hardening Compliance Inspector
Line 2: This is a comment that describes what the code does: "AuditHardening.ps1 - Automated Windows Hardening Compliance Inspector". Comments start with # and are ignored by Python.
set-strictmode -version 2.0
Line 3: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$ReportPath = ".\WindowsHardeningReport.txt"
Line 4: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$Report = @()
Line 5: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$Report += "=== Windows System Hardening Report ==="
Line 6: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$Report += "Execution Time: $(Get-Date)"
Line 7: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$Report += "---------------------------------------"
Line 8: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
# 1. Audit Password Complexity Registry Settings
Line 9: This is a comment that describes what the code does: "1. Audit Password Complexity Registry Settings". Comments start with # and are ignored by Python.
$ComplexityPath = "HKLM:\SAM\SAM\Domains\Account"
Line 10: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
if (Test-Path $ComplexityPath) {
Line 11: A conditional check — the code inside this block only runs if the condition evaluates to True.
# SAM folder is protected; read standard SecEdit metrics instead
Line 12: This is a comment that describes what the code does: "SAM folder is protected; read standard SecEdit metrics instead". Comments start with # and are ignored by Python.
$Report += "[INFO] Checking local account database baselines..."
Line 13: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
}
Line 14: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
# 2. Check for Insecure Listening Services (e.g. Remote Registry, Telnet if any)
Line 15: This is a comment that describes what the code does: "2. Check for Insecure Listening Services (e.g. Remote Registry, Telnet if any)". Comments start with # and are ignored by Python.
$UnsecureServices = @("RemoteRegistry", "TlntSvr", "SessionEnv")
Line 16: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
foreach ($service in $UnsecureServices) {
Line 17: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
$srv = Get-Service -Name $service -ErrorAction SilentlyContinue
Line 18: This line performs an operation as part of the script logic. It contributes to the overall functionality of the program.
if ($srv) {
Line 19: A conditional check — the code inside this block only runs if the condition evaluates to True.
if ($srv.Status -eq "Running") {
Line 20: A conditional check — the code inside this block only runs if the condition evaluates to True.
✓ Complete Combined Script: All lines explained above are combined into the full script shown below. Copy and paste the entire script into your file.
Copy
# AuditHardening.ps1 - Automated Windows Hardening Compliance Inspector
set-strictmode -version 2.0

$ReportPath = ".\WindowsHardeningReport.txt"
$Report = @()
$Report += "=== Windows System Hardening Report ==="
$Report += "Execution Time: $(Get-Date)"
$Report += "---------------------------------------"

# 1. Audit Password Complexity Registry Settings
$ComplexityPath = "HKLM:\SAM\SAM\Domains\Account"
if (Test-Path $ComplexityPath) {
  # SAM folder is protected; read standard SecEdit metrics instead
  $Report += "[INFO] Checking local account database baselines..."
}

# 2. Check for Insecure Listening Services (e.g. Remote Registry, Telnet if any)
$UnsecureServices = @("RemoteRegistry", "TlntSvr", "SessionEnv")
foreach ($service in $UnsecureServices) {
  $srv = Get-Service -Name $service -ErrorAction SilentlyContinue
  if ($srv) {
    if ($srv.Status -eq "Running") {
      $Report += "[FAIL] Insecure service running: $($srv.DisplayName)"
    } else {
      $Report += "[OK] Insecure service $($service) is disabled or stopped."
    }
  } else {
    $Report += "[OK] Service $($service) is not present on system."
  }
}

# 3. Verify Local Firewall Profile Status
$FirewallProfiles = Get-NetFirewallProfile -Profile Domain,Private,Public
foreach ($profile in $FirewallProfiles) {
  if ($profile.Enabled -eq $True) {
    $Report += "[OK] Firewall Profile $($profile.Name) is Enabled."
  } else {
    $Report += "[FAIL] Firewall Profile $($profile.Name) is Disabled!"
  }
}

# Output Report
$Report | Out-File -FilePath $ReportPath -Encoding utf8
$Report
Write-Host "Hardening report compiled: $ReportPath" -ForegroundColor Green
5. Deliverables Summary

Students must produce and submit the following artifacts to verify completion of this training lab.

Created Files / Configs

  • Linux script: /home/student/Scripts/audit_linux.sh
  • Windows script: C:\Scripts\AuditHardening.ps1
  • Saved audit log outputs: linux_hardening_report.log and WindowsHardeningReport.txt

Verification Artifacts / Execution Proof

  • Windows screenshot showing GPO password length rules set to 12.
  • Linux screenshot verifying PermitRootLogin no in /etc/ssh/sshd_config.
  • Command line output from the validation script showing no insecure legacy services running.
6. Closing Explanation: Why We Did This & What It Accomplishes

Architectural Intent & Operational Impact

Why We Did This

What This Accomplishes