Lab 03 β Linux Security Automation with Bash
Mission Information
Section titled βMission InformationβDifficulty: Intermediate
Estimated Time: 90β120 minutes
Primary Language: Bash
Security Domain: Linux Security / SOC / System Hardening / Incident Triage
Environment: Local Linux lab
Automation Type: Defensive Security Assessment
Mission
Section titled βMissionβYour task is to build a Bash-based Linux security assessment script for a Linux system that you own or are explicitly authorized to administer.
The script will collect and summarize:
SYSTEM INFORMATION
CURRENT USER CONTEXT
LOCAL USERS
GROUPS
SUDO / PRIVILEGED ACCESS CONTEXT
FILE PERMISSIONS
RUNNING PROCESSES
RUNNING SERVICES
LISTENING PORTS
DISK USAGE
MEMORY USAGE
AUTHENTICATION EVENTS
SELECTED SECURITY CONFIGURATIONThe final result will be:
LINUX HOST βBASH SCRIPT βCOLLECT βFILTER βSUMMARIZE βREPORTWhy This Lab Matters
Section titled βWhy This Lab MattersβLinux appears everywhere in cybersecurity.
You may find it running:
WEB SERVERS
CLOUD SERVERS
CONTAINERS
SECURITY APPLIANCES
LOGGING SYSTEMS
SIEM INFRASTRUCTURE
DATABASE SERVERS
APPLICATION SERVERS
KUBERNETES NODESA security professional therefore needs to quickly answer questions such as:
What system am I looking at?
Who is logged in?
Which users exist?
Which accounts have privileged access?
Which processes are running?
Which services are enabled?
Which ports are listening?
Is disk space healthy?
What authentication failures occurred?
Are there risky file permissions?Bash is one of the fastest ways to automate those checks.
Learning Objectives
Section titled βLearning ObjectivesβBy completing this lab, you should be able to:
BUILD A BASH SCRIPT
USE VARIABLES
CREATE FUNCTIONS
USE CONDITIONS
USE LOOPS
HANDLE FILE PATHS SAFELY
USE grep
USE awk
USE sort
USE uniq
USE find
USE ps
USE ss
USE systemctl
USE journalctl
CREATE REPORT FILES
LOG SCRIPT EXECUTION
HANDLE ERRORS
BUILD A REUSABLE LINUX SECURITY WORKFLOWFinal Architecture
Section titled βFinal Architectureβ LINUX HOST β ββββββββββββββββ β BASH SCRIPT β ββββββββ¬ββββββββ β ββββββββββββββΌβββββββββββββ β β β SYSTEM USERS PROCESSES β β β SERVICES PERMISSIONS NETWORK β β β ββββββββββββββΌβββββββββββββ β AUTHENTICATION LOGS β SECURITY REPORTAuthorization and Safety
Section titled βAuthorization and SafetyβRun this lab only on:
YOUR OWN LINUX VM
TRAINING LAB
AUTHORIZED SERVER
SANDBOX ENVIRONMENTThis lab is designed for:
INVENTORY
VISIBILITY
AUDITING
DEFENSIVE REVIEWIt does not require exploitation, persistence, credential theft, or disabling security controls.
01 β Prepare the Linux Lab
Section titled β01 β Prepare the Linux LabβYou can use:
Ubuntu
Debian
Rocky Linux
AlmaLinux
CentOS Stream
FedoraExamples in this lab assume a modern Linux distribution using:
systemdSome commands may vary slightly between distributions.
02 β Verify Bash
Section titled β02 β Verify BashβRun:
bash --versionYou should see something similar to:
GNU bash03 β Create the Lab Workspace
Section titled β03 β Create the Lab WorkspaceβCreate:
mkdir -p linux-security-automation/{reports,logs,src}cd linux-security-automationVerify:
treeIf tree is unavailable:
find . -maxdepth 2 -type dExpected:
linux-security-automation/|+-- logs/|+-- reports/|+-- src/04 β Create the Main Script
Section titled β04 β Create the Main ScriptβCreate:
src/linux-security-audit.shOpen it using your preferred editor.
Example:
nano src/linux-security-audit.sh05 β Add the Shebang
Section titled β05 β Add the ShebangβStart with:
#!/usr/bin/env bashThis tells the system to execute the file using Bash.
06 β Enable Safer Bash Behavior
Section titled β06 β Enable Safer Bash BehaviorβAdd:
set -euo pipefailThis enables:
-eStop on many command failures
-uTreat unset variables as errors
-o pipefailDetect failures inside pipelines07 β Understand set -euo pipefail
Section titled β07 β Understand set -euo pipefailβWithout careful error handling:
COMMAND FAILS βSCRIPT CONTINUES βREPORT MAY BE WRONGWith safer handling:
COMMAND FAILS βERROR BECOMES VISIBLE βSCRIPT CAN STOP OR HANDLE IT08 β Define Base Directories
Section titled β08 β Define Base DirectoriesβAdd:
SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="$( cd "${SCRIPT_DIR}/.." && pwd)"
REPORT_DIR="${BASE_DIR}/reports"LOG_DIR="${BASE_DIR}/logs"09 β Create Output Directories
Section titled β09 β Create Output DirectoriesβAdd:
mkdir -p "$REPORT_DIR"mkdir -p "$LOG_DIR"10 β Create a Run Timestamp
Section titled β10 β Create a Run TimestampβAdd:
RUN_TIMESTAMP="$( date +"%Y%m%d-%H%M%S")"11 β Define Output Files
Section titled β11 β Define Output FilesβAdd:
REPORT_FILE="${REPORT_DIR}/linux-security-report-${RUN_TIMESTAMP}.txt"
LOG_FILE="${LOG_DIR}/linux-security-audit-${RUN_TIMESTAMP}.log"12 β Create a Logging Function
Section titled β12 β Create a Logging FunctionβAdd:
log_message() { local level="$1" local message="$2"
printf '%s [%s] %s\n' \ "$(date +"%Y-%m-%dT%H:%M:%S%z")" \ "$level" \ "$message" \ | tee -a "$LOG_FILE"}13 β Test Logging
Section titled β13 β Test LoggingβAdd temporarily:
log_message "INFO" "Test message"Run:
bash src/linux-security-audit.shYou should see:
[INFO] Test messageand a log file should appear in:
logs/14 β Remove the Temporary Test Line
Section titled β14 β Remove the Temporary Test LineβAfter confirming logging works, remove:
log_message "INFO" "Test message"15 β Create a Report Section Function
Section titled β15 β Create a Report Section FunctionβAdd:
write_section() { local title="$1"
{ echo echo "============================================================" echo "$title" echo "============================================================" } >> "$REPORT_FILE"}16 β Create the Report Header
Section titled β16 β Create the Report HeaderβAdd:
initialize_report() { { echo "Linux Security Assessment Report" echo echo "Generated: $(date)" echo "Hostname: $(hostname)" echo "Script: linux-security-audit.sh" } > "$REPORT_FILE"}17 β Build the Main Workflow
Section titled β17 β Build the Main WorkflowβCreate:
main() { log_message "INFO" "Linux security assessment started"
initialize_report
log_message "INFO" "Report initialized"
log_message "INFO" "Linux security assessment completed"
echo echo "Report:" echo "$REPORT_FILE"}Then add:
main "$@"18 β Make the Script Executable
Section titled β18 β Make the Script ExecutableβRun:
chmod +x src/linux-security-audit.shThen:
./src/linux-security-audit.sh19 β System Information Function
Section titled β19 β System Information FunctionβAdd:
collect_system_information() { write_section "01 β System Information"
{ echo "Hostname:" hostname
echo echo "Kernel:" uname -r
echo echo "Architecture:" uname -m
echo echo "Operating System:" if [[ -f /etc/os-release ]]; then cat /etc/os-release else echo "OS information unavailable" fi
echo echo "Uptime:" uptime } >> "$REPORT_FILE"}20 β Add System Collection to main()
Section titled β20 β Add System Collection to main()βAdd:
collect_system_informationRun the script again.
Review:
reports/21 β Why System Context Matters
Section titled β21 β Why System Context MattersβBefore analyzing a Linux server, determine:
HOSTNAME
OPERATING SYSTEM
KERNEL
ARCHITECTURE
UPTIMEThese values help establish:
WHAT SYSTEM IS THIS?
HOW LONG HAS IT BEEN RUNNING?
WHAT PLATFORM-SPECIFIC COMMANDS APPLY?22 β Current User Context
Section titled β22 β Current User ContextβCreate:
collect_current_user() { write_section "02 β Current User Context"
{ echo "Current user:" whoami
echo echo "User ID and groups:" id
echo echo "Current shell:" echo "${SHELL:-unknown}"
echo echo "Logged-in users:" who || true } >> "$REPORT_FILE"}23 β Why Current Context Matters
Section titled β23 β Why Current Context MattersβAlways know:
WHO AM I?
WHAT GROUPS DO I BELONG TO?
WHAT PRIVILEGES DOES THIS SESSION HAVE?before interpreting results.
24 β Local User Inventory
Section titled β24 β Local User InventoryβCreate:
collect_local_users() { write_section "03 β Local User Inventory"
awk -F: ' { printf "user=%s uid=%s gid=%s shell=%s\n", $1, $3, $4, $7 } ' /etc/passwd >> "$REPORT_FILE"}25 β Understand /etc/passwd
Section titled β25 β Understand /etc/passwdβTypical format:
username:x:uid:gid:comment:home:shellExample:
analyst:x:1001:1001:Analyst:/home/analyst:/bin/bashThe file does not normally contain plaintext passwords.
26 β Human User Review
Section titled β26 β Human User ReviewβUID conventions vary, but regular interactive users often have higher UID values.
For many distributions you may inspect:
awk -F: '$3 >= 1000 {print $1, $3, $7}' /etc/passwdDo not treat this as universal across every Linux environment.
27 β Identify Accounts with Login Shells
Section titled β27 β Identify Accounts with Login ShellsβCreate an additional report block:
awk -F: '$7 !~ /(nologin|false)$/ { print $1, $3, $7}' /etc/passwdThis can help identify accounts capable of interactive login.
28 β Group Inventory
Section titled β28 β Group InventoryβCreate:
collect_groups() { write_section "04 β Group Inventory"
awk -F: ' { printf "group=%s gid=%s members=%s\n", $1, $3, $4 } ' /etc/group >> "$REPORT_FILE"}29 β Privileged Group Context
Section titled β29 β Privileged Group ContextβCommon administrative groups may include:
sudo
wheelDistribution-dependent.
Create:
collect_privileged_groups() { write_section "05 β Privileged Group Review"
{ if getent group sudo >/dev/null 2>&1; then echo "sudo group:" getent group sudo else echo "sudo group not present" fi
echo
if getent group wheel >/dev/null 2>&1; then echo "wheel group:" getent group wheel else echo "wheel group not present" fi } >> "$REPORT_FILE"}30 β Root Account Review
Section titled β30 β Root Account ReviewβCreate:
collect_root_context() { write_section "06 β Root Account Context"
{ getent passwd root
echo
echo "Accounts with UID 0:" awk -F: '$3 == 0 {print $1}' /etc/passwd } >> "$REPORT_FILE"}Security Observation
Section titled βSecurity ObservationβNormally you should understand why every:
UID 0account exists.
Unexpected UID 0 accounts require review.
31 β Home Directory Review
Section titled β31 β Home Directory ReviewβCreate:
collect_home_directories() { write_section "07 β Home Directory Review"
{ echo "Home directories:" ls -ld /home/* 2>/dev/null || true } >> "$REPORT_FILE"}32 β Why Home Permissions Matter
Section titled β32 β Why Home Permissions MatterβOverly permissive home directories can expose:
CONFIGURATION FILES
SHELL HISTORY
APPLICATION DATA
USER FILES33 β World-Writable Files Concept
Section titled β33 β World-Writable Files ConceptβA world-writable file can be modified by any local user.
This may be legitimate in some locations, but deserves review when found in sensitive directories.
34 β Search Controlled Locations First
Section titled β34 β Search Controlled Locations FirstβDo not start by scanning every mounted filesystem.
For the lab, inspect:
/etc
/opt
/usr/localwhere permitted.
35 β World-Writable File Review
Section titled β35 β World-Writable File ReviewβCreate:
collect_world_writable_files() { write_section "08 β World-Writable File Review"
{ find /etc /opt /usr/local \ -xdev \ -type f \ -perm -0002 \ -print \ 2>/dev/null || true } >> "$REPORT_FILE"}36 β Why -xdev?
Section titled β36 β Why -xdev?β-xdev helps prevent find from crossing into other mounted filesystems.
This reduces unexpected scope.
37 β World-Writable Directory Review
Section titled β37 β World-Writable Directory ReviewβCreate:
collect_world_writable_directories() { write_section "09 β World-Writable Directory Review"
{ find /etc /opt /usr/local \ -xdev \ -type d \ -perm -0002 \ -print \ 2>/dev/null || true } >> "$REPORT_FILE"}38 β Interpret Results Carefully
Section titled β38 β Interpret Results CarefullyβWorld-writable does not automatically equal:
VULNERABILITYFor example, some directories intentionally support shared writing.
Always inspect:
PURPOSE
OWNER
STICKY BIT
LOCATION
APPLICATION REQUIREMENT39 β SUID Files Concept
Section titled β39 β SUID Files ConceptβSUID files execute with the file ownerβs effective permissions.
They deserve inventory and review.
40 β Inventory SUID Files
Section titled β40 β Inventory SUID FilesβFor a controlled lab:
find /usr/bin /usr/sbin /bin /sbin \ -xdev \ -type f \ -perm -4000 \ -print \ 2>/dev/null41 β Add SUID Inventory
Section titled β41 β Add SUID InventoryβCreate:
collect_suid_files() { write_section "10 β SUID File Inventory"
find \ /usr/bin \ /usr/sbin \ /bin \ /sbin \ -xdev \ -type f \ -perm -4000 \ -print \ 2>/dev/null \ >> "$REPORT_FILE" || true}Important
Section titled βImportantβThis lab inventories privileged files for defensive review.
Do not use the results to attempt unauthorized privilege escalation.
42 β SGID File Inventory
Section titled β42 β SGID File InventoryβCreate:
collect_sgid_files() { write_section "11 β SGID File Inventory"
find \ /usr/bin \ /usr/sbin \ /bin \ /sbin \ -xdev \ -type f \ -perm -2000 \ -print \ 2>/dev/null \ >> "$REPORT_FILE" || true}43 β Process Inventory
Section titled β43 β Process InventoryβCreate:
collect_processes() { write_section "12 β Running Processes"
ps aux \ --sort=-%cpu \ >> "$REPORT_FILE"}44 β Why Processes Matter
Section titled β44 β Why Processes MatterβProcesses show:
WHAT IS CURRENTLY EXECUTING?
WHO OWNS IT?
HOW MUCH CPU / MEMORY?
WHAT COMMAND STARTED IT?45 β Top CPU Processes
Section titled β45 β Top CPU ProcessesβAdd:
{ echo echo "Top CPU-consuming processes:" ps aux --sort=-%cpu | head -n 11} >> "$REPORT_FILE"46 β Top Memory Processes
Section titled β46 β Top Memory ProcessesβAdd:
{ echo echo "Top memory-consuming processes:" ps aux --sort=-%mem | head -n 11} >> "$REPORT_FILE"47 β Service Inventory
Section titled β47 β Service InventoryβIf systemd is available:
systemctl list-units \ --type=service \ --state=running48 β Add Running Services
Section titled β48 β Add Running ServicesβCreate:
collect_running_services() { write_section "13 β Running Services"
if command -v systemctl >/dev/null 2>&1; then systemctl list-units \ --type=service \ --state=running \ --no-pager \ >> "$REPORT_FILE" || true else echo "systemctl unavailable" \ >> "$REPORT_FILE" fi}49 β Enabled Services
Section titled β49 β Enabled ServicesβA service may not be running now but may start automatically during boot.
Create:
collect_enabled_services() { write_section "14 β Enabled Services"
if command -v systemctl >/dev/null 2>&1; then systemctl list-unit-files \ --type=service \ --state=enabled \ --no-pager \ >> "$REPORT_FILE" || true else echo "systemctl unavailable" \ >> "$REPORT_FILE" fi}50 β Security Question
Section titled β50 β Security QuestionβFor every running or enabled service, ask:
DO WE NEED THIS SERVICE?
WHO OWNS IT?
IS IT PATCHED?
IS IT NETWORK ACCESSIBLE?
IS IT CONFIGURED SECURELY?51 β Listening Port Inventory
Section titled β51 β Listening Port InventoryβModern Linux systems commonly provide:
ssRun:
ss -lntup52 β Understand ss
Section titled β52 β Understand ssβCommon options:
-lListening
-nNumeric
-tTCP
-uUDP
-pProcess informationSome process information may require elevated privileges.
53 β Add Network Listener Collection
Section titled β53 β Add Network Listener CollectionβCreate:
collect_network_listeners() { write_section "15 β Network Listeners"
if command -v ss >/dev/null 2>&1; then ss -lntup \ >> "$REPORT_FILE" \ 2>&1 || true else echo "ss command unavailable" \ >> "$REPORT_FILE" fi}54 β Why Listener Review Matters
Section titled β54 β Why Listener Review MattersβListening services expand the systemβs:
NETWORK ATTACK SURFACEAsk:
WHY IS THIS PORT OPEN?
WHICH PROCESS OWNS IT?
IS IT EXPECTED?
WHICH INTERFACE IS IT BOUND TO?
IS A FIREWALL RESTRICTING IT?55 β Localhost vs All Interfaces
Section titled β55 β Localhost vs All InterfacesβExample:
127.0.0.1:5432means:
LOCALHOST ONLYWhereas:
0.0.0.0:5432usually means:
ALL IPv4 INTERFACESThis difference matters.
56 β Active Network Connections
Section titled β56 β Active Network ConnectionsβCreate:
collect_network_connections() { write_section "16 β Active Network Connections"
if command -v ss >/dev/null 2>&1; then ss -ntup \ >> "$REPORT_FILE" \ 2>&1 || true else echo "ss unavailable" \ >> "$REPORT_FILE" fi}57 β Route Information
Section titled β57 β Route InformationβCreate:
collect_routes() { write_section "17 β Routing Information"
if command -v ip >/dev/null 2>&1; then ip route \ >> "$REPORT_FILE" \ 2>&1 || true fi}58 β Interface Information
Section titled β58 β Interface InformationβCreate:
collect_interfaces() { write_section "18 β Network Interfaces"
if command -v ip >/dev/null 2>&1; then ip -brief address \ >> "$REPORT_FILE" \ 2>&1 || true fi}59 β Disk Usage
Section titled β59 β Disk UsageβCreate:
collect_disk_usage() { write_section "19 β Disk Usage"
df -h \ >> "$REPORT_FILE"}60 β Why Disk Usage Matters to Security
Section titled β60 β Why Disk Usage Matters to SecurityβA full disk can cause:
LOGGING FAILURE
APPLICATION FAILURE
DATABASE FAILURE
MONITORING BLIND SPOTSTherefore disk health is a security-relevant operational control.
61 β Inode Usage
Section titled β61 β Inode UsageβCreate:
collect_inode_usage() { write_section "20 β Inode Usage"
df -i \ >> "$REPORT_FILE"}62 β Memory Usage
Section titled β62 β Memory UsageβCreate:
collect_memory_usage() { write_section "21 β Memory Usage"
if command -v free >/dev/null 2>&1; then free -h \ >> "$REPORT_FILE" else cat /proc/meminfo \ >> "$REPORT_FILE" fi}63 β CPU Load
Section titled β63 β CPU LoadβCreate:
collect_load() { write_section "22 β System Load"
uptime \ >> "$REPORT_FILE"
echo \ >> "$REPORT_FILE"
cat /proc/loadavg \ >> "$REPORT_FILE"}64 β Mounted Filesystems
Section titled β64 β Mounted FilesystemsβCreate:
collect_mounts() { write_section "23 β Mounted Filesystems"
findmnt \ >> "$REPORT_FILE" \ 2>&1 || mount \ >> "$REPORT_FILE"}65 β Authentication Logs
Section titled β65 β Authentication LogsβLinux authentication logs vary by distribution.
Common locations:
/var/log/auth.logor:
/var/log/secure66 β Detect Authentication Log
Section titled β66 β Detect Authentication LogβCreate:
get_auth_log() { if [[ -f /var/log/auth.log ]]; then echo "/var/log/auth.log"
elif [[ -f /var/log/secure ]]; then echo "/var/log/secure"
else echo "" fi}67 β Collect Recent Authentication Events
Section titled β67 β Collect Recent Authentication EventsβCreate:
collect_authentication_events() { write_section "24 β Recent Authentication Events"
local auth_log
auth_log="$(get_auth_log)"
if [[ -n "$auth_log" ]]; then tail -n 100 "$auth_log" \ >> "$REPORT_FILE" \ 2>&1 || true
elif command -v journalctl >/dev/null 2>&1; then journalctl \ --no-pager \ -n 100 \ _COMM=sshd \ >> "$REPORT_FILE" \ 2>&1 || true
else echo "Authentication logs unavailable" \ >> "$REPORT_FILE" fi}68 β Permission Consideration
Section titled β68 β Permission ConsiderationβAuthentication logs may require:
ROOT
sudo
SECURITY LOG GROUP MEMBERSHIPYour script should not assume every command succeeds.
69 β Failed SSH Login Review
Section titled β69 β Failed SSH Login ReviewβCreate:
collect_failed_authentication() { write_section "25 β Failed Authentication Summary"
local auth_log
auth_log="$(get_auth_log)"
if [[ -n "$auth_log" ]]; then grep -iE \ "failed password|authentication failure" \ "$auth_log" \ 2>/dev/null \ | tail -n 100 \ >> "$REPORT_FILE" || true
elif command -v journalctl >/dev/null 2>&1; then journalctl \ --no-pager \ _COMM=sshd \ 2>/dev/null \ | grep -i "failed" \ | tail -n 100 \ >> "$REPORT_FILE" || true
else echo "Failed authentication data unavailable" \ >> "$REPORT_FILE" fi}70 β Count Repeated Source IPs
Section titled β70 β Count Repeated Source IPsβFor compatible SSH failure records, you can perform a simple summary.
Example:
grep "Failed password" /var/log/auth.log \ | awk '{print $(NF-3)}' \ | sort \ | uniq -c \ | sort -nrLog formats differ, so validate the field position before relying on this in production.
71 β Why Log Parsing Needs Validation
Section titled β71 β Why Log Parsing Needs ValidationβThis works only when the record format matches your assumption.
Do not write:
FIELD 11 = IPFOREVERwithout checking actual logs.
72 β Build a Safer Failed-Source Function
Section titled β72 β Build a Safer Failed-Source FunctionβFor the lab:
collect_failed_sources() { write_section "26 β Failed Login Source Summary"
local auth_log
auth_log="$(get_auth_log)"
if [[ -z "$auth_log" ]]; then echo "Authentication log unavailable" \ >> "$REPORT_FILE" return fi
grep -i "Failed password" "$auth_log" \ 2>/dev/null \ | grep -oE \ '([0-9]{1,3}\.){3}[0-9]{1,3}' \ | sort \ | uniq -c \ | sort -nr \ | head -n 20 \ >> "$REPORT_FILE" || true}Important Limitation
Section titled βImportant LimitationβThe regex finds IPv4-looking strings but does not fully validate them.
A production pipeline should validate extracted IPs before using them for decisions.
73 β Successful Authentication Review
Section titled β73 β Successful Authentication ReviewβCreate:
collect_successful_authentication() { write_section "27 β Successful Authentication Summary"
local auth_log
auth_log="$(get_auth_log)"
if [[ -n "$auth_log" ]]; then grep -i "Accepted" "$auth_log" \ 2>/dev/null \ | tail -n 100 \ >> "$REPORT_FILE" || true
elif command -v journalctl >/dev/null 2>&1; then journalctl \ --no-pager \ _COMM=sshd \ 2>/dev/null \ | grep -i "Accepted" \ | tail -n 100 \ >> "$REPORT_FILE" || true
else echo "Successful authentication data unavailable" \ >> "$REPORT_FILE" fi}74 β Last Login Information
Section titled β74 β Last Login InformationβCreate:
collect_login_history() { write_section "28 β Login History"
last -n 20 \ >> "$REPORT_FILE" \ 2>&1 || true}75 β Failed Login History
Section titled β75 β Failed Login HistoryβIf available:
lastbmay display failed login history.
Add:
collect_failed_login_history() { write_section "29 β Failed Login History"
if command -v lastb >/dev/null 2>&1; then lastb -n 20 \ >> "$REPORT_FILE" \ 2>&1 || true else echo "lastb unavailable" \ >> "$REPORT_FILE" fi}76 β Cron Review
Section titled β76 β Cron ReviewβScheduled jobs should be understood.
Create:
collect_cron_configuration() { write_section "30 β Scheduled Task Inventory"
{ echo "/etc/crontab:" cat /etc/crontab \ 2>/dev/null || true
echo echo "/etc/cron.d:" ls -la /etc/cron.d \ 2>/dev/null || true
echo echo "Current user crontab:" crontab -l \ 2>/dev/null || \ echo "No accessible user crontab" } >> "$REPORT_FILE"}77 β Scheduled Task Security Questions
Section titled β77 β Scheduled Task Security QuestionsβAsk:
WHO CREATED THE JOB?
WHAT COMMAND RUNS?
WHICH ACCOUNT RUNS IT?
WHAT FILES CAN IT MODIFY?
IS THE SCRIPT PATH WRITABLE?78 β SSH Configuration Review
Section titled β78 β SSH Configuration ReviewβCreate:
collect_ssh_configuration() { write_section "31 β SSH Configuration Review"
local ssh_config="/etc/ssh/sshd_config"
if [[ -f "$ssh_config" ]]; then grep -Ei \ '^[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|MaxAuthTries|AllowUsers|AllowGroups)' \ "$ssh_config" \ >> "$REPORT_FILE" || true else echo "sshd_config unavailable" \ >> "$REPORT_FILE" fi}79 β Why Ignore Comments?
Section titled β79 β Why Ignore Comments?βThe pattern begins with:
^and looks for actual configuration lines rather than commented examples where possible.
But keep in mind:
INCLUDED CONFIGURATION FILESmay also affect effective SSH configuration.
80 β Effective SSH Configuration
Section titled β80 β Effective SSH ConfigurationβWhere supported:
sshd -Tcan show effective configuration.
This may require appropriate permissions and a valid SSH configuration.
For the lab, treat this as optional.
81 β Firewall Status
Section titled β81 β Firewall StatusβCommon Linux firewall tools include:
ufw
firewalld
nftables82 β Add Firewall Review
Section titled β82 β Add Firewall ReviewβCreate:
collect_firewall_status() { write_section "32 β Firewall Status"
if command -v ufw >/dev/null 2>&1; then ufw status verbose \ >> "$REPORT_FILE" \ 2>&1 || true
elif command -v firewall-cmd >/dev/null 2>&1; then firewall-cmd --state \ >> "$REPORT_FILE" \ 2>&1 || true
firewall-cmd --list-all \ >> "$REPORT_FILE" \ 2>&1 || true
elif command -v nft >/dev/null 2>&1; then nft list ruleset \ >> "$REPORT_FILE" \ 2>&1 || true
else echo "No supported firewall utility detected" \ >> "$REPORT_FILE" fi}83 β Do Not Modify Firewall Rules
Section titled β83 β Do Not Modify Firewall RulesβThis lab only:
READS
REPORTSfirewall configuration.
Do not automatically alter production firewall rules during an assessment.
84 β Package Update Awareness
Section titled β84 β Package Update AwarenessβPackage commands vary by distribution.
The assessment can record package manager information without automatically installing updates.
85 β Detect Package Manager
Section titled β85 β Detect Package ManagerβCreate:
collect_package_manager() { write_section "33 β Package Manager Information"
for command_name in \ apt \ dnf \ yum \ zypper \ pacman do if command -v "$command_name" >/dev/null 2>&1; then echo "Detected package manager: $command_name" \ >> "$REPORT_FILE" fi done}86 β Why Not Automatically Patch?
Section titled β86 β Why Not Automatically Patch?βAutomatic patching can affect:
APPLICATION COMPATIBILITY
REBOOT REQUIREMENTS
BUSINESS OPERATIONSAssessment automation should generally report:
STATEbefore changing:
STATE87 β Time Synchronization
Section titled β87 β Time SynchronizationβAccurate time matters for security logs.
Create:
collect_time_status() { write_section "34 β Time Synchronization"
{ date
echo
if command -v timedatectl >/dev/null 2>&1; then timedatectl status fi } >> "$REPORT_FILE" 2>&1 || true}88 β Why Time Matters
Section titled β88 β Why Time MattersβIf server clocks differ significantly:
LOG CORRELATIONbecomes difficult.
Incident timelines depend on trustworthy timestamps.
89 β Environment Information
Section titled β89 β Environment InformationβCreate:
collect_environment_summary() { write_section "35 β Environment Summary"
{ echo "PATH:" printf '%s\n' "${PATH:-unknown}"
echo echo "Shell:" printf '%s\n' "${SHELL:-unknown}"
echo echo "Locale:" locale 2>/dev/null || true } >> "$REPORT_FILE"}90 β Security Note on PATH
Section titled β90 β Security Note on PATHβA poorly controlled PATH can create unexpected command resolution behavior.
Your script should use trusted execution environments and controlled paths where appropriate.
91 β File Integrity Hash for the Report
Section titled β91 β File Integrity Hash for the ReportβCreate:
hash_report() { local hash_file="${REPORT_FILE}.sha256"
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$REPORT_FILE" \ > "$hash_file" elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$REPORT_FILE" \ > "$hash_file" fi}92 β Why Hash the Report?
Section titled β92 β Why Hash the Report?βA cryptographic hash helps identify whether the report changed after generation.
Conceptually:
REPORT βSHA-256 βINTEGRITY VALUE93 β Build a Summary Section
Section titled β93 β Build a Summary SectionβCreate:
generate_summary() { write_section "36 β Assessment Summary"
{ echo "Assessment completed." echo echo "Review areas:" echo "- Local accounts" echo "- Administrative groups" echo "- Privileged files" echo "- Running services" echo "- Network listeners" echo "- Authentication failures" echo "- Scheduled tasks" echo "- Firewall status" echo echo "Important:" echo "This report identifies configuration and activity requiring review." echo "It does not automatically prove compromise or vulnerability." } >> "$REPORT_FILE"}94 β Assemble the Complete Main Workflow
Section titled β94 β Assemble the Complete Main WorkflowβYour main() should now resemble:
main() { log_message "INFO" \ "Linux security assessment started"
initialize_report
collect_system_information collect_current_user collect_local_users collect_groups collect_privileged_groups collect_root_context collect_home_directories collect_world_writable_files collect_world_writable_directories collect_suid_files collect_sgid_files collect_processes collect_running_services collect_enabled_services collect_network_listeners collect_network_connections collect_routes collect_interfaces collect_disk_usage collect_inode_usage collect_memory_usage collect_load collect_mounts collect_authentication_events collect_failed_authentication collect_failed_sources collect_successful_authentication collect_login_history collect_failed_login_history collect_cron_configuration collect_ssh_configuration collect_firewall_status collect_package_manager collect_time_status collect_environment_summary
generate_summary
hash_report
log_message "INFO" \ "Linux security assessment completed"
echo echo "Report:" echo "$REPORT_FILE"}95 β Run the Complete Script
Section titled β95 β Run the Complete ScriptβRun:
./src/linux-security-audit.sh96 β Review Output
Section titled β96 β Review OutputβYou should now have:
reports/|+-- linux-security-report-<timestamp>.txt|+-- linux-security-report-<timestamp>.txt.sha256and:
logs/|+-- linux-security-audit-<timestamp>.log97 β Run with Appropriate Privileges
Section titled β97 β Run with Appropriate PrivilegesβSome data may be inaccessible to a standard account.
Start with:
./src/linux-security-audit.shThen, only on your authorized lab system, compare with:
sudo ./src/linux-security-audit.shSecurity Principle
Section titled βSecurity PrincipleβDo not automatically assume:
MORE PRIVILEGE=BETTER SCRIPTUse:
LEAST PRIVILEGEand elevate only when the data requirement justifies it.
98 β Review the Report as an Analyst
Section titled β98 β Review the Report as an AnalystβDo not just confirm the script ran.
Ask:
ARE UNEXPECTED USERS PRESENT?
WHO HAS ADMINISTRATIVE ACCESS?
ARE THERE EXTRA UID 0 ACCOUNTS?
ARE WORLD-WRITABLE FILES PRESENT?
ARE PRIVILEGED FILES EXPECTED?
WHICH SERVICES ARE RUNNING?
ARE UNEXPECTED PORTS LISTENING?
ARE AUTHENTICATION FAILURES REPEATED?
IS THE FIREWALL ENABLED?
IS SYSTEM TIME SYNCHRONIZED?99 β Build a Finding Classification
Section titled β99 β Build a Finding ClassificationβFor the training lab, use:
INFORMATIONAL
REVIEW
HIGH REVIEWAvoid automatically calling configuration differences:
CRITICAL VULNERABILITIESwithout validation.
100 β Example Finding: Unexpected UID 0 Account
Section titled β100 β Example Finding: Unexpected UID 0 AccountβPossible output:
Finding:Additional UID 0 account detected
Evidence:service-admin
Status:HIGH REVIEWAnalyst action:
VALIDATE BUSINESS REQUIREMENT
CHECK ACCOUNT OWNER
CHECK CREATION HISTORY
CHECK RECENT AUTHENTICATION101 β Example Finding: Unexpected Listener
Section titled β101 β Example Finding: Unexpected ListenerβSuppose:
0.0.0.0:8080is listening.
Do not assume:
MALICIOUSAsk:
WHICH PROCESS OWNS IT?
IS THE SERVICE APPROVED?
IS IT EXTERNALLY ACCESSIBLE?
IS THE FIREWALL RESTRICTING IT?
IS AUTHENTICATION ENABLED?102 β Example Finding: Authentication Failures
Section titled β102 β Example Finding: Authentication FailuresβSuppose:
25 failed loginscome from one source.
Investigate:
SOURCE IP
TARGET USERS
TIME WINDOW
SUCCESSFUL LOGIN AFTER FAILURES
VPN / NAT CONTEXT
MFA EVENTS103 β Example Finding: World-Writable File
Section titled β103 β Example Finding: World-Writable FileβSuppose the script identifies:
/opt/example/config.txtReview:
OWNER
GROUP
APPLICATION REQUIREMENT
WHO CAN MODIFY IT?
DOES A PRIVILEGED SERVICE READ IT?104 β Export Dedicated Files
Section titled β104 β Export Dedicated FilesβFor a more mature version, create separate outputs:
system-information.txt
users.txt
privileged-access.txt
processes.txt
services.txt
network-listeners.txt
authentication-summary.txt
permissions-review.txt105 β Why Separate Outputs?
Section titled β105 β Why Separate Outputs?βIt makes integration easier.
For example:
BASH βJSON / TEXT βPYTHON βNORMALIZE βCENTRAL REPORT106 β Add CSV Output for Listening Ports
Section titled β106 β Add CSV Output for Listening PortsβA future version could convert:
ssoutput into:
protocol
address
port
processThen export:
network-listeners.csv107 β Add JSON Output
Section titled β107 β Add JSON OutputβBash can generate JSON, but complex JSON construction is easier and safer with tools such as:
jqor by passing raw results to Python.
108 β Check for jq
Section titled β108 β Check for jqβRun:
command -v jqIf installed:
jqcan support structured JSON workflows.
109 β Bash vs Python Decision
Section titled β109 β Bash vs Python DecisionβUse Bash when:
COMMANDS ALREADY EXIST
LINUX DATA IS TEXT-ORIENTED
WORKFLOW IS SIMPLE
SHELL PIPELINES ARE CLEARMove toward Python when:
DATA STRUCTURES BECOME COMPLEX
MULTIPLE APIs ARE REQUIRED
JSON IS HEAVY
CORRELATION GROWS
TESTING NEEDS INCREASE110 β Add Script Help
Section titled β110 β Add Script HelpβCreate:
show_help() { cat <<'EOF'Linux Security Audit
Usage: ./linux-security-audit.sh
Purpose: Collect defensive Linux security inventory and generate a local assessment report.
Run only on systems you own or areexplicitly authorized to administer.EOF}111 β Add --help
Section titled β111 β Add --helpβBefore running the assessment:
if [[ "${1:-}" == "--help" ]]; then show_help exit 0fi112 β Test
Section titled β112 β TestβRun:
./src/linux-security-audit.sh --help113 β Add Dependency Checks
Section titled β113 β Add Dependency ChecksβCreate:
check_dependencies() { local commands=( awk grep sort find ps df )
local missing=0
for command_name in "${commands[@]}"; do if ! command -v "$command_name" >/dev/null 2>&1; then log_message \ "ERROR" \ "Missing command: $command_name"
missing=1 fi done
if [[ "$missing" -ne 0 ]]; then return 1 fi}114 β Why Dependency Checking Matters
Section titled β114 β Why Dependency Checking MattersβWithout it:
SCRIPT STARTS βCOMMAND MISSING βPARTIAL REPORTWith validation:
CHECK FIRST βKNOWN ENVIRONMENT βRUN115 β Add Dependency Check to Main
Section titled β115 β Add Dependency Check to MainβAt the beginning:
if ! check_dependencies; then log_message \ "ERROR" \ "Required dependencies missing"
exit 1fi116 β Add Temporary File Safety
Section titled β116 β Add Temporary File SafetyβIf your future script needs temporary files, use:
mktempinstead of predictable names such as:
/tmp/report.tmp117 β Temporary Directory Pattern
Section titled β117 β Temporary Directory PatternβExample:
TEMP_DIR="$(mktemp -d)"Then:
cleanup() { rm -rf "$TEMP_DIR"}
trap cleanup EXIT118 β Why trap Matters
Section titled β118 β Why trap MattersβIt ensures cleanup even when the script exits unexpectedly.
CREATE TEMP DATA βSCRIPT RUNS βEXIT βCLEANUP119 β Avoid Unsafe Expansion
Section titled β119 β Avoid Unsafe ExpansionβPrefer:
"$variable"instead of:
$variablewhen handling paths or user-supplied values.
120 β Avoid eval
Section titled β120 β Avoid evalβDo not build command strings and execute them using:
evalunless you have a very specific, reviewed requirement.
Prefer explicit commands and arguments.
121 β ShellCheck
Section titled β121 β ShellCheckβIf available, validate the script using:
shellcheck src/linux-security-audit.shShellCheck helps identify:
QUOTING ISSUES
UNSAFE EXPANSIONS
COMMON SHELL BUGS122 β Syntax Check
Section titled β122 β Syntax CheckβRun:
bash -n src/linux-security-audit.shNo output generally means syntax parsing succeeded.
123 β Test on a Non-Critical VM
Section titled β123 β Test on a Non-Critical VMβBefore using any assessment script against a production system:
LAB VM βTEST βREVIEW OUTPUT βFIX βCONTROLLED DEPLOYMENT124 β Failure Test: Missing Command
Section titled β124 β Failure Test: Missing CommandβTemporarily add a fake dependency such as:
not-a-real-commandVerify:
DEPENDENCY CHECK FAILSCLEANLYThen remove it.
125 β Failure Test: Read Permission
Section titled β125 β Failure Test: Read PermissionβRun as a normal user.
Document which sections return:
PERMISSION DENIED
PARTIAL DATA
NO DATAThis teaches an important lesson:
NO RESULTdoes not always mean:
NO ACTIVITY126 β Failure Test: Missing Authentication Log
Section titled β126 β Failure Test: Missing Authentication LogβOn distributions using only the journal:
/var/log/auth.logmay not exist.
Verify the script falls back to:
journalctlwhere possible.
127 β Performance Test
Section titled β127 β Performance TestβTime the script:
time ./src/linux-security-audit.shSecurity assessment automation should not create excessive load.
128 β Scope Control
Section titled β128 β Scope ControlβAvoid commands such as unrestricted:
find /by default on production systems.
Why?
Because it may:
SCAN HUGE FILESYSTEMS
CROSS NETWORK MOUNTS
CREATE LOAD
GENERATE LARGE OUTPUT129 β Better Scope Principle
Section titled β129 β Better Scope PrincipleβUse:
KNOWN DIRECTORIES
-xdev
MAX DEPTH WHERE APPROPRIATE
EXPLICIT TARGETS130 β Add Host Metadata
Section titled β130 β Add Host MetadataβAdd to the report header:
echo "Collection user: $(whoami)"echo "Collection UID: $(id -u)"This helps document how the report was collected.
131 β Add Script Version
Section titled β131 β Add Script VersionβNear the top:
SCRIPT_VERSION="1.0.0"Include it in the report:
echo "Script version: $SCRIPT_VERSION"132 β Why Versioning Matters
Section titled β132 β Why Versioning MattersβIf a report was created three months ago, you need to know:
WHICH SCRIPT VERSIONGENERATED IT?133 β Create an Assessment ID
Section titled β133 β Create an Assessment IDβAdd:
ASSESSMENT_ID="LINUX-${RUN_TIMESTAMP}"Include it in:
REPORT
LOG134 β Evidence Metadata
Section titled β134 β Evidence MetadataβA professional assessment should capture:
ASSESSMENT ID
HOSTNAME
DATE
COLLECTION USER
SCRIPT VERSION
REPORT HASH135 β Create the README
Section titled β135 β Create the READMEβCreate:
README.mdInclude:
PROJECT PURPOSE
LAB REQUIREMENTS
AUTHORIZED USE
SUPPORTED SYSTEMS
HOW TO RUN
OUTPUT FILES
SECURITY CHECKS
LIMITATIONS
TROUBLESHOOTING136 β README Security Statement
Section titled β136 β README Security StatementβInclude:
This project performs read-only Linux securityinventory and defensive assessment activities.
Use it only against Linux systems you own orare explicitly authorized to administer.137 β Document Limitations
Section titled β137 β Document LimitationsβYour script does not provide:
FULL CIS BENCHMARK ASSESSMENT
MALWARE DETECTION
ROOTKIT DETECTION
FULL PACKAGE VULNERABILITY ANALYSIS
APPLICATION CONFIGURATION REVIEW
CONTAINER ANALYSIS
CLOUD CONTROL-PLANE REVIEWThese require dedicated workflows.
138 β Portfolio Architecture
Section titled β138 β Portfolio ArchitectureβYour final repository can look like:
linux-security-automation/|+-- src/| +-- linux-security-audit.sh|+-- reports/|+-- logs/|+-- tests/|+-- README.md|+-- architecture.md139 β Lab Deliverables
Section titled β139 β Lab DeliverablesβSubmit:
linux-security-audit.sh
Sample Report
Report SHA-256
Execution Log
README
Architecture Diagram
Analyst Findings Summary140 β Analyst Findings Summary Template
Section titled β140 β Analyst Findings Summary TemplateβCreate:
Assessment ID:Host:Date:
Finding 01:Title:Evidence:Status:Recommendation:
Finding 02:Title:Evidence:Status:Recommendation:141 β Security Assessment Workflow
Section titled β141 β Security Assessment WorkflowβThe lab has now created:
LINUX SERVER βSYSTEM INVENTORY βIDENTITY REVIEW βPERMISSION REVIEW βPROCESS REVIEW βSERVICE REVIEW βNETWORK REVIEW βAUTHENTICATION REVIEW βSECURITY REPORT142 β Common Bash Security Automation Mistakes
Section titled β142 β Common Bash Security Automation MistakesβAvoid:
RUNNING EVERYTHING AS ROOT
UNQUOTED VARIABLES
NO ERROR HANDLING
NO DEPENDENCY CHECKS
NO LOGGING
SCANNING ENTIRE FILESYSTEMS NEEDLESSLY
SUPPRESSING EVERY ERROR
ASSUMING LOG FORMATS NEVER CHANGE
AUTOMATICALLY CHANGING CONFIGURATION
NO REPORT TIMESTAMP
NO SCRIPT VERSION
NO DOCUMENTATION143 β The Problem with 2>/dev/null
Section titled β143 β The Problem with 2>/dev/nullβYou have used it selectively in this training script.
Do not apply:
2>/dev/nullto everything.
Otherwise you may hide:
PERMISSION ERRORS
COMMAND FAILURES
FILESYSTEM PROBLEMSSometimes errors are security-relevant information.
144 β Read-Only First
Section titled β144 β Read-Only FirstβThis lab follows:
COLLECT βASSESS βREPORTnot:
COLLECT βAUTOMATICALLY MODIFY SYSTEMThis is deliberate.
145 β Hardening Comes After Assessment
Section titled β145 β Hardening Comes After AssessmentβA professional workflow is:
ASSESS βVALIDATE FINDING βUNDERSTAND BUSINESS IMPACT βCREATE CHANGE βTEST βAPPROVE βREMEDIATE βVERIFY146 β Mission Validation Checklist
Section titled β146 β Mission Validation ChecklistβConfirm:
- Bash script created
-
set -euo pipefailused - Variables quoted
- Report directory created
- Logging implemented
- Host information collected
- User information collected
- Group information collected
- UID 0 accounts reviewed
- Administrative groups reviewed
- Home directories reviewed
- World-writable files reviewed
- World-writable directories reviewed
- SUID files inventoried
- SGID files inventoried
- Processes inventoried
- Running services inventoried
- Enabled services inventoried
- Listening ports inventoried
- Active connections inventoried
- Routes reviewed
- Interfaces reviewed
- Disk usage collected
- Inodes collected
- Memory status collected
- Load collected
- Authentication logs reviewed
- Failed authentication summarized
- Successful authentication reviewed
- Login history collected
- Cron configuration reviewed
- SSH configuration reviewed
- Firewall status reviewed
- Time synchronization reviewed
- Report generated
- Report hashed
- Script syntax checked
- Script tested in an authorized lab
- Findings manually validated
Mission Review
Section titled βMission ReviewβYou started with:
LINUX COMMANDSsuch as:
whoami
id
ps
ss
find
grep
awk
systemctl
journalctlYou converted them into:
ONE REPEATABLESECURITY ASSESSMENTWORKFLOWWhat You Built
Section titled βWhat You BuiltβYou now have a Bash-based Linux security assessment tool capable of collecting:
HOST INFORMATION
IDENTITY DATA
PRIVILEGED ACCESS CONTEXT
PERMISSION DATA
PROCESS DATA
SERVICE DATA
NETWORK DATA
AUTHENTICATION DATA
SYSTEM HEALTH DATAand transforming it into:
ANALYST-READYSECURITY REPORTKey Security Lesson
Section titled βKey Security LessonβThe most important lesson is:
INVENTORYCOMES BEFORESECURITY DECISIONSYou cannot decide whether something is suspicious until you first understand:
WHAT EXISTS
WHAT IS RUNNING
WHO HAS ACCESS
WHAT IS EXPOSED
WHAT ACTIVITY OCCURREDFinal Mental Model
Section titled βFinal Mental ModelβWhenever you assess a Linux system, think:
WHO AM I? βWHAT SYSTEM IS THIS? βWHO CAN LOG IN? βWHO HAS PRIVILEGE? βWHAT IS RUNNING? βWHAT IS LISTENING? βWHAT FILES ARE HIGH RISK? βWHAT AUTHENTICATION ACTIVITY OCCURRED? βWHAT REQUIRES HUMAN REVIEW?The value of Bash is not:
RUNNING ONE COMMANDThe value is:
TURNING MANY SMALLLINUX SECURITY CHECKSINTO ONE SAFE,REPEATABLE WORKFLOWWhatβs Next?
Section titled βWhatβs Next?ββ‘οΈ Lab 04 β Windows Security Automation with PowerShell
In the next lab, you will move from Linux security automation into Windows security assessment.
You will build:
WINDOWS HOST βPOWERSHELL βSYSTEM INVENTORY βLOCAL USERS βLOCAL ADMINISTRATORS βPROCESSES βSERVICES βNETWORK CONNECTIONS βWINDOWS EVENT LOGS βDEFENDER STATUS βFIREWALL STATUS βSECURITY REPORTYou will use PowerShell objects and defensive Windows administration capabilities to create a reusable Windows security inventory and assessment workflow.