Security Audit Script
Comprehensive Linux security audit and vulnerability check
Table of Contents
Comprehensive security audit script that checks for common Linux security vulnerabilities and misconfigurations.
Overview
This script performs essential security checks on Linux systems, identifying potential vulnerabilities like root-equivalent users, world-writable files, SUID/SGID binaries, weak password configurations, and firewall status.
Use Case: Regular security audits, compliance checks, and hardening verification.
Platform: Linux (all distributions) Requirements: Root/sudo privileges for complete audit Execution Time: 2-10 minutes (depending on filesystem size)
The Script
1#!/bin/bash
2
3#
4# security-audit.sh
5#
6# Comprehensive Linux security audit script
7# Checks for common vulnerabilities and security misconfigurations
8#
9# Author: glyph.sh
10# Usage: sudo ./security-audit.sh [options]
11# Options:
12# -o, --output FILE Save report to file
13# -v, --verbose Show detailed output
14# -h, --help Show this help message
15#
16
17set -euo pipefail
18
19# Colors for output
20RED='\033[0;31m'
21YELLOW='\033[1;33m'
22GREEN='\033[0;32m'
23BLUE='\033[0;34m'
24CYAN='\033[0;36m'
25NC='\033[0m' # No Color
26
27# Variables
28OUTPUT_FILE=""
29VERBOSE=false
30ISSUES_FOUND=0
31
32# Parse command line arguments
33while [[ $# -gt 0 ]]; do
34 case $1 in
35 -o|--output)
36 OUTPUT_FILE="$2"
37 shift 2
38 ;;
39 -v|--verbose)
40 VERBOSE=true
41 shift
42 ;;
43 -h|--help)
44 echo "Usage: sudo $0 [options]"
45 echo "Options:"
46 echo " -o, --output FILE Save report to file"
47 echo " -v, --verbose Show detailed output"
48 echo " -h, --help Show this help message"
49 exit 0
50 ;;
51 *)
52 echo "Unknown option: $1"
53 exit 1
54 ;;
55 esac
56done
57
58# Function to print colored messages
59print_header() {
60 echo -e "\n${CYAN}========================================${NC}"
61 echo -e "${CYAN} $1${NC}"
62 echo -e "${CYAN}========================================${NC}\n"
63}
64
65print_section() {
66 echo -e "\n${BLUE}[+] $1${NC}"
67}
68
69print_pass() {
70 echo -e " ${GREEN}✓${NC} $1"
71}
72
73print_warning() {
74 echo -e " ${YELLOW}⚠${NC} $1"
75 ((ISSUES_FOUND++))
76}
77
78print_fail() {
79 echo -e " ${RED}✗${NC} $1"
80 ((ISSUES_FOUND++))
81}
82
83print_info() {
84 echo -e " ${CYAN}ℹ${NC} $1"
85}
86
87# Check if running as root
88check_root() {
89 if [[ $EUID -ne 0 ]]; then
90 echo -e "${YELLOW}Warning: Not running as root. Some checks may be incomplete.${NC}"
91 echo ""
92 fi
93}
94
95# Check for users with UID 0 (root privileges)
96check_uid_zero() {
97 print_section "Checking for users with UID 0 (root privileges)"
98
99 local uid_zero_users=$(awk -F: '$3 == 0 {print $1}' /etc/passwd)
100 local count=$(echo "$uid_zero_users" | wc -l)
101
102 if [[ $count -eq 1 ]] && [[ "$uid_zero_users" == "root" ]]; then
103 print_pass "Only root user has UID 0"
104 else
105 print_fail "Multiple users with UID 0 found:"
106 echo "$uid_zero_users" | while read -r user; do
107 if [[ "$user" != "root" ]]; then
108 echo " - $user"
109 fi
110 done
111 fi
112}
113
114# Check for world-writable files
115check_world_writable() {
116 print_section "Checking for world-writable files (excluding /tmp, /proc, /sys)"
117
118 print_info "Scanning filesystem (this may take a few minutes)..."
119
120 local writable_files=$(find / -xdev -type f -perm -0002 \
121 ! -path "/proc/*" \
122 ! -path "/sys/*" \
123 ! -path "/tmp/*" \
124 ! -path "/var/tmp/*" \
125 ! -path "/run/*" \
126 2>/dev/null | head -20)
127
128 if [[ -z "$writable_files" ]]; then
129 print_pass "No suspicious world-writable files found"
130 else
131 print_warning "World-writable files found:"
132 echo "$writable_files" | while read -r file; do
133 echo " - $file"
134 done
135 echo " (showing first 20 results)"
136 fi
137}
138
139# Check for SUID/SGID files
140check_suid_sgid() {
141 print_section "Checking for SUID/SGID files"
142
143 print_info "Scanning for SUID files..."
144
145 local suid_files=$(find / -xdev \( -perm -4000 -o -perm -2000 \) -type f \
146 2>/dev/null | wc -l)
147
148 print_info "Found $suid_files SUID/SGID files"
149
150 # Check for suspicious SUID files
151 local suspicious_suid=$(find / -xdev -perm -4000 -type f \
152 2>/dev/null | grep -E "(nmap|perl|python|ruby|find|vim|less|more|awk)" || true)
153
154 if [[ -z "$suspicious_suid" ]]; then
155 print_pass "No suspicious SUID binaries found"
156 else
157 print_warning "Suspicious SUID binaries found (potential privilege escalation):"
158 echo "$suspicious_suid" | while read -r file; do
159 echo " - $file"
160 done
161 fi
162
163 if [[ "$VERBOSE" == true ]]; then
164 print_info "All SUID/SGID files:"
165 find / -xdev \( -perm -4000 -o -perm -2000 \) -type f \
166 -exec ls -lh {} \; 2>/dev/null | while read -r line; do
167 echo " $line"
168 done
169 fi
170}
171
172# Check for empty passwords in /etc/shadow
173check_empty_passwords() {
174 print_section "Checking for empty password fields in /etc/shadow"
175
176 if [[ ! -r /etc/shadow ]]; then
177 print_warning "Cannot read /etc/shadow (need root privileges)"
178 return
179 fi
180
181 local empty_pass=$(awk -F: '$2 == "" || $2 == "!" || $2 == "*" {print $1}' /etc/shadow)
182 local locked_count=$(echo "$empty_pass" | wc -l)
183
184 # Check for truly empty passwords (not locked accounts)
185 local truly_empty=$(awk -F: '$2 == "" {print $1}' /etc/shadow)
186
187 if [[ -z "$truly_empty" ]]; then
188 print_pass "No accounts with empty passwords found"
189 else
190 print_fail "Accounts with empty passwords found:"
191 echo "$truly_empty" | while read -r user; do
192 echo " - $user"
193 done
194 fi
195
196 if [[ "$VERBOSE" == true ]] && [[ -n "$empty_pass" ]]; then
197 print_info "Locked/disabled accounts: $locked_count"
198 fi
199}
200
201# Check firewall status
202check_firewall() {
203 print_section "Checking firewall status"
204
205 # Check iptables
206 if command -v iptables &> /dev/null; then
207 print_info "Checking iptables..."
208
209 local iptables_rules=$(iptables -L -n 2>/dev/null | wc -l || echo "0")
210
211 if [[ $iptables_rules -gt 8 ]]; then
212 print_pass "iptables is configured with rules"
213
214 if [[ "$VERBOSE" == true ]]; then
215 echo " Current iptables rules:"
216 iptables -L -n 2>/dev/null | head -20 | while read -r line; do
217 echo " $line"
218 done
219 fi
220 else
221 print_warning "iptables appears to have minimal or no rules"
222 fi
223 fi
224
225 # Check firewalld
226 if command -v firewall-cmd &> /dev/null; then
227 print_info "Checking firewalld..."
228
229 if systemctl is-active --quiet firewalld 2>/dev/null; then
230 print_pass "firewalld is active and running"
231
232 if [[ "$VERBOSE" == true ]]; then
233 local default_zone=$(firewall-cmd --get-default-zone 2>/dev/null || echo "unknown")
234 local active_zones=$(firewall-cmd --get-active-zones 2>/dev/null | grep -v "interfaces:" || echo "none")
235 echo " Default zone: $default_zone"
236 echo " Active zones: $active_zones"
237 fi
238 else
239 print_warning "firewalld is installed but not active"
240 fi
241 fi
242
243 # Check ufw (Ubuntu/Debian)
244 if command -v ufw &> /dev/null; then
245 print_info "Checking ufw..."
246
247 local ufw_status=$(ufw status 2>/dev/null | grep -i "Status:" | awk '{print $2}')
248
249 if [[ "$ufw_status" == "active" ]]; then
250 print_pass "ufw is active"
251
252 if [[ "$VERBOSE" == true ]]; then
253 echo " UFW rules:"
254 ufw status numbered 2>/dev/null | tail -n +4 | while read -r line; do
255 echo " $line"
256 done
257 fi
258 else
259 print_warning "ufw is installed but not active"
260 fi
261 fi
262
263 # If no firewall found
264 if ! command -v iptables &> /dev/null && \
265 ! command -v firewall-cmd &> /dev/null && \
266 ! command -v ufw &> /dev/null; then
267 print_fail "No firewall tools found (iptables, firewalld, ufw)"
268 fi
269}
270
271# Check SSH configuration
272check_ssh_config() {
273 print_section "Checking SSH configuration security"
274
275 local ssh_config="/etc/ssh/sshd_config"
276
277 if [[ ! -r "$ssh_config" ]]; then
278 print_warning "Cannot read $ssh_config (need root privileges)"
279 return
280 fi
281
282 # Check PermitRootLogin
283 local root_login=$(grep -i "^PermitRootLogin" "$ssh_config" | awk '{print $2}')
284 if [[ "$root_login" == "no" ]] || [[ "$root_login" == "without-password" ]] || [[ "$root_login" == "prohibit-password" ]]; then
285 print_pass "Root login is restricted: $root_login"
286 elif [[ -z "$root_login" ]]; then
287 print_warning "PermitRootLogin not explicitly set (may default to 'yes')"
288 else
289 print_fail "Root login is enabled: $root_login"
290 fi
291
292 # Check PasswordAuthentication
293 local pass_auth=$(grep -i "^PasswordAuthentication" "$ssh_config" | awk '{print $2}')
294 if [[ "$pass_auth" == "no" ]]; then
295 print_pass "Password authentication is disabled (key-only)"
296 elif [[ -z "$pass_auth" ]]; then
297 print_info "PasswordAuthentication not explicitly set (may default to 'yes')"
298 else
299 print_info "Password authentication is enabled: $pass_auth"
300 fi
301
302 # Check Protocol
303 local protocol=$(grep -i "^Protocol" "$ssh_config" | awk '{print $2}')
304 if [[ "$protocol" == "2" ]]; then
305 print_pass "SSH Protocol 2 is enforced"
306 elif [[ -z "$protocol" ]]; then
307 print_info "SSH Protocol not explicitly set (modern systems default to 2)"
308 else
309 print_warning "SSH Protocol setting: $protocol"
310 fi
311
312 # Check PermitEmptyPasswords
313 local empty_pass=$(grep -i "^PermitEmptyPasswords" "$ssh_config" | awk '{print $2}')
314 if [[ "$empty_pass" == "no" ]]; then
315 print_pass "Empty passwords are not permitted"
316 elif [[ -z "$empty_pass" ]]; then
317 print_info "PermitEmptyPasswords not explicitly set (should default to 'no')"
318 else
319 print_fail "Empty passwords are permitted!"
320 fi
321
322 # Check X11Forwarding
323 local x11=$(grep -i "^X11Forwarding" "$ssh_config" | awk '{print $2}')
324 if [[ "$x11" == "no" ]]; then
325 print_pass "X11 forwarding is disabled"
326 else
327 print_info "X11 forwarding is enabled or not explicitly disabled"
328 fi
329
330 # Check for custom port
331 local port=$(grep -i "^Port" "$ssh_config" | awk '{print $2}')
332 if [[ -n "$port" ]] && [[ "$port" != "22" ]]; then
333 print_pass "SSH is running on non-standard port: $port"
334 else
335 print_info "SSH is running on default port 22"
336 fi
337}
338
339# Main execution
340main() {
341 print_header "Linux Security Audit Script"
342 echo "Starting security audit at $(date)"
343 echo ""
344
345 check_root
346
347 check_uid_zero
348 check_world_writable
349 check_suid_sgid
350 check_empty_passwords
351 check_firewall
352 check_ssh_config
353
354 # Summary
355 print_header "Security Audit Complete"
356
357 if [[ $ISSUES_FOUND -eq 0 ]]; then
358 echo -e "${GREEN}No security issues found!${NC}"
359 elif [[ $ISSUES_FOUND -le 3 ]]; then
360 echo -e "${YELLOW}Found $ISSUES_FOUND potential security issue(s)${NC}"
361 else
362 echo -e "${RED}Found $ISSUES_FOUND potential security issues${NC}"
363 fi
364
365 echo ""
366 echo "Audit completed at $(date)"
367 echo ""
368
369 if [[ -n "$OUTPUT_FILE" ]]; then
370 print_info "Report saved to: $OUTPUT_FILE"
371 fi
372
373 echo -e "${CYAN}Recommendations:${NC}"
374 echo " 1. Review and address any warnings or failures"
375 echo " 2. Ensure all software is up to date"
376 echo " 3. Enable and configure firewall rules"
377 echo " 4. Use SSH key authentication when possible"
378 echo " 5. Regularly audit file permissions and SUID binaries"
379 echo " 6. Monitor system logs for suspicious activity"
380 echo ""
381}
382
383# Execute main function
384if [[ -n "$OUTPUT_FILE" ]]; then
385 main 2>&1 | tee "$OUTPUT_FILE"
386else
387 main
388fiUsage
Basic Audit
1sudo ./security-audit.shVerbose Output
1sudo ./security-audit.sh --verboseSave Report to File
1sudo ./security-audit.sh --output security-report-$(date +%Y%m%d).txtCombined Options
1sudo ./security-audit.sh --verbose --output audit.txtWhat It Checks
1. Users with UID 0
- Identifies all users with root-equivalent privileges
- Only the root user should have UID 0
- Additional UID 0 users are a serious security risk
2. World-Writable Files
- Scans filesystem for files writable by anyone
- Excludes temporary directories (/tmp, /proc, /sys)
- World-writable files can be exploited for privilege escalation
3. SUID/SGID Files
- Lists files with Set-UID and Set-GID permissions
- Identifies suspicious SUID binaries (perl, python, nmap, etc.)
- These files run with elevated privileges and can be exploited
4. Empty Password Fields
- Checks /etc/shadow for accounts without passwords
- Distinguishes between empty and locked accounts
- Empty passwords allow login without authentication
5. Firewall Status
- Checks iptables, firewalld, and ufw status
- Verifies that firewall rules are configured
- Shows active zones and rule counts
6. SSH Configuration
- PermitRootLogin: Should be ’no’ or ‘without-password’
- PasswordAuthentication: Preferably ’no’ (key-only)
- PermitEmptyPasswords: Must be ’no'
- Protocol: Should be ‘2’ (or default on modern systems)
- X11Forwarding: Consider disabling if not needed
- Port: Non-standard port reduces automated attacks
Understanding Results
Pass ✓
Configuration follows security best practices.
Warning ⚠
Potential security concern that should be reviewed.
Fail ✗
Security issue that requires immediate attention.
Info ℹ
Informational message about configuration state.
Common Security Issues
| Issue | Risk Level | Remediation |
|---|---|---|
| Multiple UID 0 users | Critical | Remove non-root UID 0 users |
| Empty passwords | Critical | Set passwords or lock accounts |
| World-writable files | High | Change permissions to 644 or 600 |
| Suspicious SUID binaries | High | Remove SUID bit: chmod u-s file |
| Root SSH login enabled | High | Set PermitRootLogin no |
| No firewall active | Medium | Enable and configure firewall |
| Default SSH port (22) | Low | Change to non-standard port |
Remediation Examples
Remove SUID Bit
1sudo chmod u-s /path/to/binaryDisable Root SSH Login
1sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
2sudo systemctl restart sshdEnable Firewall (Ubuntu/Debian)
1sudo ufw enable
2sudo ufw default deny incoming
3sudo ufw default allow outgoing
4sudo ufw allow sshEnable Firewall (RHEL/CentOS)
1sudo systemctl start firewalld
2sudo systemctl enable firewalld
3sudo firewall-cmd --set-default-zone=publicFix World-Writable File
1sudo chmod 644 /path/to/fileLock Account with Empty Password
1sudo passwd -l usernameScheduling Regular Audits
Add to crontab for weekly audits:
1# Run security audit every Sunday at 2 AM
20 2 * * 0 /path/to/security-audit.sh --output /var/log/security-audit-$(date +\%Y\%m\%d).txtLimitations
- Does not check for rootkits or malware
- Does not analyze running processes or network connections
- Does not verify software patch levels
- Filesystem scan can be time-consuming on large systems
- Some checks require root privileges
See Also
Download
1curl -O https://glyph.sh/scripts/security-audit.sh
2chmod +x security-audit.sh
3sudo ./security-audit.sh