Initial commit
This commit is contained in:
892
linux_system_audit.sh
Executable file
892
linux_system_audit.sh
Executable file
@@ -0,0 +1,892 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ===============================================================================
|
||||
# Comprehensive Linux System Enumeration and Security Audit Script
|
||||
# For Ubuntu, Debian, and Fedora systems
|
||||
# Created for automated device inventory and security assessment
|
||||
# Version: 2.0 - Enhanced with better error handling and security features
|
||||
# ===============================================================================
|
||||
|
||||
# Configuration
|
||||
OUTPUT_DIR="/tmp/system_audit_$(hostname)_$(date +%Y%m%d_%H%M%S)"
|
||||
LOG_FILE="${OUTPUT_DIR}/audit.log"
|
||||
RESULTS_FILE="${OUTPUT_DIR}/results.json"
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Enhanced error handling
|
||||
error_handler() {
|
||||
local exit_code=$1
|
||||
local line_no=$2
|
||||
local bash_lineno=$3
|
||||
local last_command=$4
|
||||
local func_stack=$5
|
||||
|
||||
echo -e "${RED}Error occurred in: $last_command${NC}" | tee -a "$LOG_FILE"
|
||||
echo -e "${RED}Exit code: $exit_code${NC}" | tee -a "$LOG_FILE"
|
||||
echo -e "${RED}Line number: $line_no${NC}" | tee -a "$LOG_FILE"
|
||||
echo -e "${RED}Function stack: $func_stack${NC}" | tee -a "$LOG_FILE"
|
||||
|
||||
# Cleanup on error
|
||||
if [ -d "$OUTPUT_DIR" ]; then
|
||||
rm -rf "$OUTPUT_DIR"
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Set up error trap
|
||||
trap 'error_handler $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]:-})' ERR
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Enhanced logging function with levels
|
||||
log() {
|
||||
local level=$1
|
||||
shift
|
||||
local message="$*"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_info() { log "INFO" "$*"; }
|
||||
log_warn() { log "WARN" "$*"; }
|
||||
log_error() { log "ERROR" "$*"; }
|
||||
log_debug() { log "DEBUG" "$*"; }
|
||||
|
||||
print_section() {
|
||||
echo -e "\n${BLUE}==== $1 ====${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection() {
|
||||
echo -e "\n${GREEN}--- $1 ---${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}WARNING: $1${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}ERROR: $1${NC}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Input validation and environment checking
|
||||
validate_environment() {
|
||||
log_info "Validating environment and dependencies..."
|
||||
|
||||
local required_tools=("hostname" "uname" "lsblk" "ip" "cat" "grep" "awk")
|
||||
local missing_tools=()
|
||||
|
||||
for tool in "${required_tools[@]}"; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
missing_tools+=("$tool")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_tools[@]} -gt 0 ]; then
|
||||
log_error "Missing required tools: ${missing_tools[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for optional but recommended tools
|
||||
local optional_tools=("jq" "docker" "podman" "nmap" "vnstat" "ethtool")
|
||||
for tool in "${optional_tools[@]}"; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
log_warn "Optional tool not found: $tool"
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Environment validation completed"
|
||||
}
|
||||
|
||||
# Check if running as root with enhanced security
|
||||
check_privileges() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
print_warning "Script not running as root. Some checks may be limited."
|
||||
SUDO_CMD="sudo"
|
||||
# Verify sudo access
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
log_warn "Passwordless sudo not available. Some operations may fail."
|
||||
fi
|
||||
else
|
||||
SUDO_CMD=""
|
||||
log_info "Running with root privileges"
|
||||
fi
|
||||
}
|
||||
|
||||
# System Information Collection
|
||||
collect_system_info() {
|
||||
print_section "SYSTEM INFORMATION"
|
||||
|
||||
print_subsection "Basic System Details"
|
||||
{
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "FQDN: $(hostname -f 2>/dev/null || echo 'N/A')"
|
||||
echo "IP Addresses: $(hostname -I 2>/dev/null || echo 'N/A')"
|
||||
echo "Date/Time: $(date)"
|
||||
echo "Uptime: $(uptime)"
|
||||
echo "Load Average: $(cat /proc/loadavg)"
|
||||
echo "Architecture: $(uname -m)"
|
||||
echo "Kernel: $(uname -r)"
|
||||
echo "Distribution: $(lsb_release -d 2>/dev/null | cut -f2 || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"')"
|
||||
echo "Kernel Version: $(uname -v)"
|
||||
} | tee -a "$LOG_FILE"
|
||||
|
||||
print_subsection "Hardware Information"
|
||||
{
|
||||
echo "CPU Info:"
|
||||
lscpu | tee -a "$LOG_FILE"
|
||||
echo -e "\nMemory Info:"
|
||||
free -h | tee -a "$LOG_FILE"
|
||||
echo -e "\nDisk Usage:"
|
||||
df -h | tee -a "$LOG_FILE"
|
||||
echo -e "\nBlock Devices:"
|
||||
lsblk | tee -a "$LOG_FILE"
|
||||
echo -e "\nPCI Devices:"
|
||||
lspci | tee -a "$LOG_FILE"
|
||||
echo -e "\nUSB Devices:"
|
||||
lsusb 2>/dev/null | tee -a "$LOG_FILE"
|
||||
}
|
||||
}
|
||||
|
||||
# Network Information
|
||||
collect_network_info() {
|
||||
print_section "NETWORK INFORMATION"
|
||||
|
||||
print_subsection "Network Interfaces"
|
||||
{
|
||||
echo "Network Interfaces (ip addr):"
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
ip addr show | tee -a "$LOG_FILE"
|
||||
else
|
||||
log_warn "ip command not available, using ifconfig fallback"
|
||||
ifconfig 2>/dev/null | tee -a "$LOG_FILE" || echo "No network interface information available"
|
||||
fi
|
||||
|
||||
echo -e "\nRouting Table:"
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
ip route show | tee -a "$LOG_FILE"
|
||||
else
|
||||
route -n 2>/dev/null | tee -a "$LOG_FILE" || echo "No routing information available"
|
||||
fi
|
||||
|
||||
echo -e "\nDNS Configuration:"
|
||||
if [ -f /etc/resolv.conf ]; then
|
||||
cat /etc/resolv.conf | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "DNS configuration file not found" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nNetwork Connections:"
|
||||
# Try multiple methods to get listening ports
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
$SUDO_CMD ss -tuln | tee -a "$LOG_FILE"
|
||||
elif command -v netstat >/dev/null 2>&1; then
|
||||
$SUDO_CMD netstat -tuln 2>/dev/null | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "No network connection tools available (ss/netstat)" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nActive Network Connections:"
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
$SUDO_CMD ss -tupln | tee -a "$LOG_FILE"
|
||||
elif command -v netstat >/dev/null 2>&1; then
|
||||
$SUDO_CMD netstat -tupln 2>/dev/null | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "No active connection tools available" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nNetwork Statistics:"
|
||||
if [ -f /proc/net/dev ]; then
|
||||
cat /proc/net/dev | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "Network statistics not available" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nNetwork Interface Details:"
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
for iface in $(ip link show | grep -E '^[0-9]+:' | cut -d: -f2 | tr -d ' '); do
|
||||
if [ "$iface" != "lo" ]; then
|
||||
echo "Interface: $iface" | tee -a "$LOG_FILE"
|
||||
if command -v ethtool >/dev/null 2>&1; then
|
||||
ethtool "$iface" 2>/dev/null | grep -E '(Speed|Duplex|Link detected)' | tee -a "$LOG_FILE" || echo " ethtool info not available"
|
||||
else
|
||||
echo " ethtool not available" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "\nBandwidth Usage (if available):"
|
||||
if command -v vnstat >/dev/null 2>&1; then
|
||||
# Try multiple interfaces
|
||||
for iface in eth0 eth1 wlan0; do
|
||||
if ip link show "$iface" >/dev/null 2>&1; then
|
||||
echo "Interface $iface:" | tee -a "$LOG_FILE"
|
||||
vnstat -i "$iface" 2>/dev/null | tee -a "$LOG_FILE" || echo "No vnstat data available for $iface"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "vnstat not installed" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
print_subsection "Firewall Status"
|
||||
{
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
echo "UFW Status:"
|
||||
$SUDO_CMD ufw status verbose | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if command -v iptables >/dev/null 2>&1; then
|
||||
echo -e "\nIPTables Rules:"
|
||||
$SUDO_CMD iptables -L -n | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if command -v firewall-cmd >/dev/null 2>&1; then
|
||||
echo -e "\nFirewalld Status:"
|
||||
$SUDO_CMD firewall-cmd --list-all | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
# Docker and Container Information
|
||||
collect_container_info() {
|
||||
print_section "CONTAINER INFORMATION"
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
print_subsection "Docker Information"
|
||||
{
|
||||
echo "Docker Version:"
|
||||
docker --version | tee -a "$LOG_FILE"
|
||||
echo -e "\nDocker System Info:"
|
||||
$SUDO_CMD docker system info 2>/dev/null | tee -a "$LOG_FILE"
|
||||
echo -e "\nRunning Containers:"
|
||||
$SUDO_CMD docker ps -a | tee -a "$LOG_FILE"
|
||||
echo -e "\nDocker Images:"
|
||||
$SUDO_CMD docker images | tee -a "$LOG_FILE"
|
||||
echo -e "\nDocker Networks:"
|
||||
$SUDO_CMD docker network ls | tee -a "$LOG_FILE"
|
||||
echo -e "\nDocker Volumes:"
|
||||
$SUDO_CMD docker volume ls | tee -a "$LOG_FILE"
|
||||
|
||||
echo -e "\nDocker Compose Services:"
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
find /opt /home -name "docker-compose.yml" -o -name "docker-compose.yaml" 2>/dev/null | head -10 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nContainer Management Tools:"
|
||||
$SUDO_CMD docker ps --format "table {{.Names}} {{.Image}} {{.Ports}}" | grep -E "(portainer|watchtower|traefik|nginx-proxy|heimdall|dashboard)" | tee -a "$LOG_FILE" || echo "No common management tools detected"
|
||||
|
||||
echo -e "\nContainer Resource Usage:"
|
||||
$SUDO_CMD docker stats --no-stream --format "table {{.Container}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}}" 2>/dev/null | head -20 | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Check for docker socket permissions
|
||||
if [ -S /var/run/docker.sock ]; then
|
||||
echo -e "\nDocker Socket Permissions:" | tee -a "$LOG_FILE"
|
||||
ls -la /var/run/docker.sock | tee -a "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "Docker not installed or not in PATH" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
print_subsection "Podman Information"
|
||||
{
|
||||
echo "Podman Version:"
|
||||
podman --version | tee -a "$LOG_FILE"
|
||||
echo -e "\nPodman Containers:"
|
||||
podman ps -a | tee -a "$LOG_FILE"
|
||||
echo -e "\nPodman Images:"
|
||||
podman images | tee -a "$LOG_FILE"
|
||||
}
|
||||
fi
|
||||
|
||||
# Check for container runtime files
|
||||
if [ -f "/.dockerenv" ]; then
|
||||
print_warning "This system appears to be running inside a Docker container"
|
||||
fi
|
||||
}
|
||||
|
||||
# Software and Package Information
|
||||
collect_software_info() {
|
||||
print_section "SOFTWARE INFORMATION"
|
||||
|
||||
print_subsection "Installed Packages"
|
||||
|
||||
# Determine package manager and list packages
|
||||
if command -v dpkg >/dev/null 2>&1; then
|
||||
echo "Installed Debian/Ubuntu packages:" | tee -a "$LOG_FILE"
|
||||
dpkg -l > "${OUTPUT_DIR}/packages_dpkg.txt"
|
||||
echo "Package list saved to packages_dpkg.txt ($(wc -l < "${OUTPUT_DIR}/packages_dpkg.txt") packages)" | tee -a "$LOG_FILE"
|
||||
|
||||
# Check for security updates
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
echo -e "\nAvailable Security Updates:" | tee -a "$LOG_FILE"
|
||||
apt list --upgradable 2>/dev/null | grep -i security | tee -a "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v rpm >/dev/null 2>&1; then
|
||||
echo "Installed RPM packages:" | tee -a "$LOG_FILE"
|
||||
rpm -qa > "${OUTPUT_DIR}/packages_rpm.txt"
|
||||
echo "Package list saved to packages_rpm.txt ($(wc -l < "${OUTPUT_DIR}/packages_rpm.txt") packages)" | tee -a "$LOG_FILE"
|
||||
|
||||
# Check for security updates (Fedora/RHEL)
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
echo -e "\nAvailable Security Updates:" | tee -a "$LOG_FILE"
|
||||
dnf check-update --security 2>/dev/null | tee -a "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_subsection "Running Services"
|
||||
{
|
||||
echo "Systemd Services:"
|
||||
systemctl list-units --type=service --state=running | tee -a "$LOG_FILE"
|
||||
echo -e "\nEnabled Services:"
|
||||
systemctl list-unit-files --type=service --state=enabled | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection "Running Processes"
|
||||
{
|
||||
echo "Process List (top 20 by CPU):"
|
||||
ps aux --sort=-%cpu | head -20 | tee -a "$LOG_FILE"
|
||||
echo -e "\nProcess Tree:"
|
||||
pstree | tee -a "$LOG_FILE"
|
||||
}
|
||||
}
|
||||
|
||||
# Security Assessment
|
||||
collect_security_info() {
|
||||
print_section "SECURITY ASSESSMENT"
|
||||
|
||||
print_subsection "User Accounts"
|
||||
{
|
||||
echo "User accounts with shell access:"
|
||||
set +o pipefail
|
||||
cat /etc/passwd | grep -E '/bin/(bash|sh|zsh|fish)' | tee -a "$LOG_FILE"
|
||||
set -o pipefail
|
||||
echo -e "\nUsers with UID 0 (root privileges):"
|
||||
awk -F: '$3 == 0 {print $1}' /etc/passwd | tee -a "$LOG_FILE"
|
||||
echo -e "\nSudo group members:"
|
||||
if [ -f /etc/group ]; then
|
||||
grep -E '^(sudo|wheel):' /etc/group | tee -a "$LOG_FILE"
|
||||
fi
|
||||
echo -e "\nCurrently logged in users:"
|
||||
who | tee -a "$LOG_FILE"
|
||||
echo -e "\nLast logins:"
|
||||
last -10 | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection "SSH Configuration"
|
||||
{
|
||||
if [ -f /etc/ssh/sshd_config ]; then
|
||||
echo "SSH Configuration highlights:"
|
||||
grep -E '^(Port|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|Protocol)' /etc/ssh/sshd_config | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo -e "\nSSH Failed Login Attempts (last 50):"
|
||||
$SUDO_CMD grep "Failed password" /var/log/auth.log 2>/dev/null | tail -50 | tee -a "$LOG_FILE" || echo "Auth log not accessible"
|
||||
}
|
||||
|
||||
print_subsection "File Permissions and SUID"
|
||||
{
|
||||
echo "World-writable files (excluding /proc, /sys, /dev):"
|
||||
# Use parallel processing for better performance
|
||||
find / -type f -perm -002 -not -path "/proc/*" -not -path "/sys/*" -not -path "/dev/*" -not -path "/tmp/*" -not -path "/var/tmp/*" 2>/dev/null | head -20 | tee -a "$LOG_FILE"
|
||||
|
||||
echo -e "\nSUID/SGID files:"
|
||||
find / -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | head -30 | tee -a "$LOG_FILE"
|
||||
|
||||
echo -e "\nSUID/SGID Risk Assessment:"
|
||||
# Check for dangerous SUID binaries
|
||||
local dangerous_suid=("/bin/su" "/usr/bin/sudo" "/usr/bin/passwd" "/usr/bin/chfn" "/usr/bin/chsh" "/usr/bin/gpasswd" "/usr/bin/newgrp" "/usr/bin/mount" "/usr/bin/umount" "/usr/bin/ping" "/usr/bin/ping6")
|
||||
for binary in "${dangerous_suid[@]}"; do
|
||||
if [ -f "$binary" ] && [ -u "$binary" ]; then
|
||||
echo " WARNING: Potentially dangerous SUID binary found: $binary" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\nWorld-writable directories (excluding /proc, /sys, /dev, /tmp):"
|
||||
find / -type d -perm -002 -not -path "/proc/*" -not -path "/sys/*" -not -path "/dev/*" -not -path "/tmp/*" -not -path "/var/tmp/*" 2>/dev/null | head -10 | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection "Cron Jobs"
|
||||
{
|
||||
echo "Root crontab:"
|
||||
$SUDO_CMD crontab -l 2>/dev/null | tee -a "$LOG_FILE" || echo "No root crontab"
|
||||
|
||||
echo -e "\nSystem cron jobs:"
|
||||
if [ -d /etc/cron.d ]; then
|
||||
ls -la /etc/cron.d/ | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if [ -f /etc/crontab ]; then
|
||||
echo -e "\n/etc/crontab contents:"
|
||||
cat /etc/crontab | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
print_subsection "Shell History"
|
||||
{
|
||||
echo "Checking for sensitive information in shell history..."
|
||||
local sensitive_patterns=(
|
||||
"password"
|
||||
"passwd"
|
||||
"secret"
|
||||
"token"
|
||||
"key"
|
||||
"api_key"
|
||||
"private_key"
|
||||
"ssh_key"
|
||||
"aws_access"
|
||||
"aws_secret"
|
||||
"database_url"
|
||||
"connection_string"
|
||||
"credential"
|
||||
"auth"
|
||||
"login"
|
||||
)
|
||||
|
||||
for histfile in /home/*/.bash_history /root/.bash_history /home/*/.zsh_history /home/*/.fish_history; do
|
||||
if [ -f "$histfile" ] && [ -r "$histfile" ]; then
|
||||
echo "Analyzing: $histfile" | tee -a "$LOG_FILE"
|
||||
local found_sensitive=false
|
||||
|
||||
for pattern in "${sensitive_patterns[@]}"; do
|
||||
if grep -q -i "$pattern" "$histfile" 2>/dev/null; then
|
||||
echo " WARNING: Pattern '$pattern' found in $histfile" | tee -a "$LOG_FILE"
|
||||
found_sensitive=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$found_sensitive" = false ]; then
|
||||
echo " No obvious sensitive patterns found" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for command history in memory
|
||||
if [ -f /proc/$$/environ ]; then
|
||||
echo -e "\nChecking for sensitive environment variables..."
|
||||
if grep -q -i "password\|secret\|token\|key" /proc/$$/environ 2>/dev/null; then
|
||||
print_warning "Sensitive environment variables detected in current process"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
print_subsection "Tailscale Configuration"
|
||||
{
|
||||
if command -v tailscale >/dev/null 2>&1; then
|
||||
echo "Tailscale Status:"
|
||||
tailscale status 2>/dev/null | tee -a "$LOG_FILE" || echo "Tailscale not running"
|
||||
echo -e "\nTailscale IP:"
|
||||
tailscale ip -4 2>/dev/null | tee -a "$LOG_FILE" || echo "No Tailscale IP"
|
||||
else
|
||||
echo "Tailscale not installed" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
# Vulnerability Assessment
|
||||
run_vulnerability_scan() {
|
||||
print_section "VULNERABILITY ASSESSMENT"
|
||||
|
||||
print_subsection "Kernel Vulnerabilities"
|
||||
{
|
||||
echo "Kernel version and potential CVEs:"
|
||||
uname -r | tee -a "$LOG_FILE"
|
||||
|
||||
# Enhanced kernel vulnerability assessment
|
||||
kernel_version=$(uname -r)
|
||||
kernel_major=$(echo "$kernel_version" | cut -d. -f1)
|
||||
kernel_minor=$(echo "$kernel_version" | cut -d. -f2)
|
||||
|
||||
echo "Current kernel: $kernel_version" | tee -a "$LOG_FILE"
|
||||
echo "Kernel major version: $kernel_major" | tee -a "$LOG_FILE"
|
||||
echo "Kernel minor version: $kernel_minor" | tee -a "$LOG_FILE"
|
||||
|
||||
# Enhanced kernel version checking
|
||||
local risk_level="LOW"
|
||||
local risk_message=""
|
||||
|
||||
if [ "$kernel_major" -lt 4 ]; then
|
||||
risk_level="CRITICAL"
|
||||
risk_message="Kernel version is very outdated and likely has multiple critical vulnerabilities"
|
||||
elif [ "$kernel_major" -eq 4 ] && [ "$kernel_minor" -lt 19 ]; then
|
||||
risk_level="HIGH"
|
||||
risk_message="Kernel version is outdated and may have security vulnerabilities"
|
||||
elif [ "$kernel_major" -eq 5 ] && [ "$kernel_minor" -lt 4 ]; then
|
||||
risk_level="MEDIUM"
|
||||
risk_message="Kernel version may be outdated. Consider updating for security patches"
|
||||
elif [ "$kernel_major" -eq 5 ] && [ "$kernel_minor" -lt 10 ]; then
|
||||
risk_level="LOW"
|
||||
risk_message="Kernel version is relatively recent but may have some vulnerabilities"
|
||||
else
|
||||
risk_level="LOW"
|
||||
risk_message="Kernel version is recent and likely secure"
|
||||
fi
|
||||
|
||||
echo "Risk Level: $risk_level" | tee -a "$LOG_FILE"
|
||||
echo "Assessment: $risk_message" | tee -a "$LOG_FILE"
|
||||
|
||||
# Check for specific known vulnerable kernel versions
|
||||
local known_vulnerable_versions=(
|
||||
"4.9.0" "4.9.1" "4.9.2" "4.9.3" "4.9.4" "4.9.5" "4.9.6" "4.9.7" "4.9.8" "4.9.9"
|
||||
"4.14.0" "4.14.1" "4.14.2" "4.14.3" "4.14.4" "4.14.5" "4.14.6" "4.14.7" "4.14.8" "4.14.9"
|
||||
"4.19.0" "4.19.1" "4.19.2" "4.19.3" "4.19.4" "4.19.5" "4.19.6" "4.19.7" "4.19.8" "4.19.9"
|
||||
)
|
||||
|
||||
for vulnerable_version in "${known_vulnerable_versions[@]}"; do
|
||||
if [[ "$kernel_version" == "$vulnerable_version"* ]]; then
|
||||
print_warning "Kernel version $kernel_version matches known vulnerable pattern: $vulnerable_version"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check kernel configuration for security features
|
||||
echo -e "\nKernel Security Features:" | tee -a "$LOG_FILE"
|
||||
if [ -f /proc/sys/kernel/randomize_va_space ]; then
|
||||
local aslr=$(cat /proc/sys/kernel/randomize_va_space)
|
||||
if [ "$aslr" -eq 2 ]; then
|
||||
echo " ASLR (Address Space Layout Randomization): ENABLED" | tee -a "$LOG_FILE"
|
||||
else
|
||||
print_warning "ASLR is not fully enabled (value: $aslr)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f /proc/sys/kernel/dmesg_restrict ]; then
|
||||
local dmesg_restrict=$(cat /proc/sys/kernel/dmesg_restrict)
|
||||
if [ "$dmesg_restrict" -eq 1 ]; then
|
||||
echo " Dmesg restriction: ENABLED" | tee -a "$LOG_FILE"
|
||||
else
|
||||
print_warning "Dmesg restriction is disabled"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
print_subsection "Open Ports Security Check"
|
||||
{
|
||||
echo "Potentially risky open ports:"
|
||||
$SUDO_CMD netstat -tuln 2>/dev/null | awk 'NR>2 {print $4}' | sed 's/.*://' | sort -n | uniq | while read port; do
|
||||
case $port in
|
||||
21) echo "Port $port (FTP) - Consider secure alternatives" ;;
|
||||
23) echo "Port $port (Telnet) - Insecure, use SSH instead" ;;
|
||||
53) echo "Port $port (DNS) - Ensure properly configured" ;;
|
||||
80) echo "Port $port (HTTP) - Consider HTTPS" ;;
|
||||
135|139|445) echo "Port $port (SMB/NetBIOS) - Potentially risky" ;;
|
||||
3389) echo "Port $port (RDP) - Ensure secure configuration" ;;
|
||||
esac
|
||||
done | tee -a "$LOG_FILE"
|
||||
}
|
||||
}
|
||||
|
||||
# Environment Variables and Configuration
|
||||
collect_env_info() {
|
||||
print_section "ENVIRONMENT AND CONFIGURATION"
|
||||
|
||||
print_subsection "Environment Variables"
|
||||
{
|
||||
echo "Key environment variables:"
|
||||
env | grep -E '^(PATH|HOME|USER|SHELL|LANG)=' | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection "Mount Points"
|
||||
{
|
||||
echo "Current mounts:"
|
||||
mount | tee -a "$LOG_FILE"
|
||||
echo -e "\nDisk usage by mount point:"
|
||||
df -h | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
print_subsection "System Limits"
|
||||
{
|
||||
echo "System limits:"
|
||||
ulimit -a | tee -a "$LOG_FILE"
|
||||
}
|
||||
}
|
||||
|
||||
# Generate JSON summary
|
||||
generate_json_summary() {
|
||||
print_section "GENERATING SUMMARY"
|
||||
log "Generating JSON summary..."
|
||||
|
||||
# Calculate execution time
|
||||
END_TIME=$(date +%s)
|
||||
EXECUTION_TIME=$((END_TIME - START_TIME))
|
||||
|
||||
# Gather data safely, with fallbacks
|
||||
hostname_val=$(hostname)
|
||||
fqdn_val=$(hostname -f 2>/dev/null || echo 'unknown')
|
||||
ip_addresses_val=$(hostname -I 2>/dev/null | tr ' ' ',' || echo 'unknown')
|
||||
os_val=$(lsb_release -d 2>/dev/null | cut -f2 | tr -d '"' || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"')
|
||||
kernel_val=$(uname -r)
|
||||
arch_val=$(uname -m)
|
||||
uptime_val=$(uptime -p 2>/dev/null || uptime)
|
||||
docker_installed_val=$(command -v docker >/dev/null 2>&1 && echo "true" || echo "false")
|
||||
podman_installed_val=$(command -v podman >/dev/null 2>&1 && echo "true" || echo "false")
|
||||
running_containers_val=$(if command -v docker >/dev/null 2>&1; then $SUDO_CMD docker ps -q 2>/dev/null | wc -l; else echo "0"; fi)
|
||||
ssh_root_login_val=$(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo 'unknown')
|
||||
ufw_status_val=$(command -v ufw >/dev/null 2>&1 && $SUDO_CMD ufw status | head -1 | awk '{print $2}' || echo 'not_installed')
|
||||
failed_ssh_attempts_val=$(grep "Failed password" /var/log/auth.log 2>/dev/null | wc -l || echo "0")
|
||||
|
||||
# Collect open ports with better fallback
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
open_ports=$($SUDO_CMD ss -tuln 2>/dev/null | awk 'NR>2 {print $5}' | sed 's/.*://' | sort -n | uniq | tr '\n' ',' | sed 's/,$//')
|
||||
elif command -v netstat >/dev/null 2>&1; then
|
||||
open_ports=$($SUDO_CMD netstat -tuln 2>/dev/null | awk 'NR>2 {print $4}' | sed 's/.*://' | sort -n | uniq | tr '\n' ',' | sed 's/,$//')
|
||||
else
|
||||
open_ports="unknown"
|
||||
fi
|
||||
|
||||
# Check for jq and use it if available, otherwise generate basic JSON
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
# Build JSON with jq
|
||||
jq -n \
|
||||
--arg timestamp "$(date -Iseconds)" \
|
||||
--arg hostname "$hostname_val" \
|
||||
--arg scan_duration "${EXECUTION_TIME}s" \
|
||||
--arg fqdn "$fqdn_val" \
|
||||
--arg ip_addresses "$ip_addresses_val" \
|
||||
--arg os "$os_val" \
|
||||
--arg kernel "$kernel_val" \
|
||||
--arg architecture "$arch_val" \
|
||||
--arg uptime "$uptime_val" \
|
||||
--argjson docker_installed "$docker_installed_val" \
|
||||
--argjson podman_installed "$podman_installed_val" \
|
||||
--arg running_containers "$running_containers_val" \
|
||||
--arg ssh_root_login "$ssh_root_login_val" \
|
||||
--arg ufw_status "$ufw_status_val" \
|
||||
--arg failed_ssh_attempts "$failed_ssh_attempts_val" \
|
||||
--arg open_ports "$open_ports" \
|
||||
'{
|
||||
"scan_info": {
|
||||
"timestamp": $timestamp,
|
||||
"hostname": $hostname,
|
||||
"scanner_version": "2.0",
|
||||
"scan_duration": $scan_duration
|
||||
},
|
||||
"system": {
|
||||
"hostname": $hostname,
|
||||
"fqdn": $fqdn,
|
||||
"ip_addresses": $ip_addresses,
|
||||
"os": $os,
|
||||
"kernel": $kernel,
|
||||
"architecture": $architecture,
|
||||
"uptime": $uptime
|
||||
},
|
||||
"containers": {
|
||||
"docker_installed": $docker_installed,
|
||||
"podman_installed": $podman_installed,
|
||||
"running_containers": ($running_containers | tonumber)
|
||||
},
|
||||
"security": {
|
||||
"ssh_root_login": $ssh_root_login,
|
||||
"ufw_status": $ufw_status,
|
||||
"failed_ssh_attempts": ($failed_ssh_attempts | tonumber),
|
||||
"open_ports": ($open_ports | split(","))
|
||||
}
|
||||
}' > "$RESULTS_FILE"
|
||||
else
|
||||
# Generate basic JSON without jq
|
||||
log_warn "jq not available, generating basic JSON summary"
|
||||
cat > "$RESULTS_FILE" << EOF
|
||||
{
|
||||
"scan_info": {
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"hostname": "$hostname_val",
|
||||
"scanner_version": "2.0",
|
||||
"scan_duration": "${EXECUTION_TIME}s"
|
||||
},
|
||||
"system": {
|
||||
"hostname": "$hostname_val",
|
||||
"fqdn": "$fqdn_val",
|
||||
"ip_addresses": "$ip_addresses_val",
|
||||
"os": "$os_val",
|
||||
"kernel": "$kernel_val",
|
||||
"architecture": "$arch_val",
|
||||
"uptime": "$uptime_val"
|
||||
},
|
||||
"containers": {
|
||||
"docker_installed": $docker_installed_val,
|
||||
"podman_installed": $podman_installed_val,
|
||||
"running_containers": $running_containers_val
|
||||
},
|
||||
"security": {
|
||||
"ssh_root_login": "$ssh_root_login_val",
|
||||
"ufw_status": "$ufw_status_val",
|
||||
"failed_ssh_attempts": $failed_ssh_attempts_val,
|
||||
"open_ports": ["$open_ports"]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log_info "JSON summary generated successfully: $RESULTS_FILE"
|
||||
else
|
||||
print_error "Failed to generate JSON summary."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Network scan function (optional - can be run from central location)
|
||||
network_discovery() {
|
||||
if [ "$1" = "--network-scan" ]; then
|
||||
print_section "NETWORK DISCOVERY"
|
||||
|
||||
if command -v nmap >/dev/null 2>&1; then
|
||||
local_subnet=$(ip route | grep "src $(hostname -I | awk '{print $1}')" | awk '{print $1}' | head -1)
|
||||
if [ ! -z "$local_subnet" ]; then
|
||||
echo "Scanning local subnet: $local_subnet" | tee -a "$LOG_FILE"
|
||||
nmap -sn "$local_subnet" > "${OUTPUT_DIR}/network_scan.txt" 2>&1
|
||||
echo "Network scan results saved to network_scan.txt" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "nmap not available for network scanning" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_info "Starting comprehensive system audit on $(hostname)"
|
||||
log_info "Output directory: $OUTPUT_DIR"
|
||||
log_info "Script version: 2.0"
|
||||
|
||||
# Validate environment first
|
||||
validate_environment
|
||||
|
||||
# Check privileges
|
||||
check_privileges
|
||||
|
||||
# Run audit modules with error handling
|
||||
local modules=(
|
||||
"collect_system_info"
|
||||
"collect_network_info"
|
||||
"collect_container_info"
|
||||
"collect_software_info"
|
||||
"collect_security_info"
|
||||
"run_vulnerability_scan"
|
||||
"collect_env_info"
|
||||
)
|
||||
|
||||
for module in "${modules[@]}"; do
|
||||
log_info "Running module: $module"
|
||||
if ! $module; then
|
||||
log_warn "Module $module encountered issues, continuing..."
|
||||
fi
|
||||
done
|
||||
|
||||
# Run network discovery if requested
|
||||
if [ $# -gt 0 ]; then
|
||||
log_info "Running network discovery with args: $*"
|
||||
network_discovery "$@"
|
||||
fi
|
||||
|
||||
# Generate JSON summary
|
||||
log_info "Generating JSON summary"
|
||||
if ! generate_json_summary; then
|
||||
log_warn "JSON summary generation failed, but continuing..."
|
||||
fi
|
||||
|
||||
print_section "AUDIT COMPLETE"
|
||||
END_TIME=$(date +%s)
|
||||
EXECUTION_TIME=$((END_TIME - START_TIME))
|
||||
log_info "Audit completed successfully in ${EXECUTION_TIME} seconds"
|
||||
log_info "Results available in: $OUTPUT_DIR"
|
||||
|
||||
# Create an enhanced summary file
|
||||
create_enhanced_summary
|
||||
|
||||
# Display final information
|
||||
echo -e "\n${GREEN}Audit Complete!${NC}"
|
||||
echo -e "${BLUE}Results directory: $OUTPUT_DIR${NC}"
|
||||
echo -e "${BLUE}Main log file: $LOG_FILE${NC}"
|
||||
echo -e "${BLUE}JSON summary: $RESULTS_FILE${NC}"
|
||||
|
||||
# Compress results with better error handling
|
||||
compress_results
|
||||
}
|
||||
|
||||
# Create enhanced summary file
|
||||
create_enhanced_summary() {
|
||||
local summary_file="${OUTPUT_DIR}/SUMMARY.txt"
|
||||
|
||||
cat > "$summary_file" << EOF
|
||||
=== COMPREHENSIVE AUDIT SUMMARY ===
|
||||
Generated: $(date)
|
||||
Script Version: 2.0
|
||||
Hostname: $(hostname)
|
||||
FQDN: $(hostname -f 2>/dev/null || echo 'N/A')
|
||||
IP Addresses: $(hostname -I 2>/dev/null || echo 'N/A')
|
||||
|
||||
=== SYSTEM INFORMATION ===
|
||||
OS: $(lsb_release -d 2>/dev/null | cut -f2 || cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '"')
|
||||
Kernel: $(uname -r)
|
||||
Architecture: $(uname -m)
|
||||
Uptime: $(uptime -p 2>/dev/null || uptime)
|
||||
|
||||
=== SECURITY STATUS ===
|
||||
SSH Root Login: $(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo 'unknown')
|
||||
UFW Status: $(command -v ufw >/dev/null 2>&1 && $SUDO_CMD ufw status | head -1 | awk '{print $2}' || echo 'not_installed')
|
||||
Failed SSH Attempts: $(grep "Failed password" /var/log/auth.log 2>/dev/null | wc -l || echo "0")
|
||||
|
||||
=== CONTAINER STATUS ===
|
||||
Docker: $(command -v docker >/dev/null 2>&1 && echo "Installed" || echo "Not installed")
|
||||
Podman: $(command -v podman >/dev/null 2>&1 && echo "Installed" || echo "Not installed")
|
||||
Running Containers: $(if command -v docker >/dev/null 2>&1; then $SUDO_CMD docker ps -q 2>/dev/null | wc -l; else echo "0"; fi)
|
||||
|
||||
=== FILES GENERATED ===
|
||||
EOF
|
||||
|
||||
ls -la "$OUTPUT_DIR" >> "$summary_file"
|
||||
|
||||
log_info "Enhanced summary created: $summary_file"
|
||||
}
|
||||
|
||||
# Compress results with better error handling
|
||||
compress_results() {
|
||||
if ! command -v tar >/dev/null 2>&1; then
|
||||
log_warn "tar not available, skipping compression"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Compressing audit results..."
|
||||
|
||||
local parent_dir=$(dirname "$OUTPUT_DIR")
|
||||
local archive_name=$(basename "$OUTPUT_DIR").tar.gz
|
||||
|
||||
if cd "$parent_dir" && tar -czf "$archive_name" "$(basename "$OUTPUT_DIR")"; then
|
||||
log_info "Compressed results: $parent_dir/$archive_name"
|
||||
|
||||
# Verify archive integrity
|
||||
if tar -tzf "$archive_name" >/dev/null 2>&1; then
|
||||
log_info "Archive verified successfully"
|
||||
|
||||
# Calculate archive size
|
||||
local archive_size=$(du -h "$archive_name" | cut -f1)
|
||||
log_info "Archive size: $archive_size"
|
||||
else
|
||||
log_error "Archive verification failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "Compression failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user