360 lines
15 KiB
Python
360 lines
15 KiB
Python
|
|
# IDS, log monitoring, login tracking, file integrity, process auditing, and alerting
|
||
|
|
# Each function returns a bash command string that app.py executes via ssh_run()
|
||
|
|
|
||
|
|
|
||
|
|
def auth_log_cmd(lines=100):
|
||
|
|
"""Return bash cmd to get recent auth log entries (failed/successful logins)."""
|
||
|
|
return (
|
||
|
|
"echo '=== Recent Auth Log Entries ===' && "
|
||
|
|
"if [ -f /var/log/auth.log ]; then "
|
||
|
|
f" grep -E '(Failed|Accepted|Invalid|session opened|session closed|authentication failure)' /var/log/auth.log | tail -n {lines}; "
|
||
|
|
"elif command -v journalctl >/dev/null 2>&1; then "
|
||
|
|
f" journalctl -u sshd -u ssh --no-pager -n {lines} 2>&1; "
|
||
|
|
"else "
|
||
|
|
" echo 'No auth.log found and journalctl not available'; "
|
||
|
|
"fi"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def login_tracker_cmd():
|
||
|
|
"""Return bash cmd to show login attempts with IP, count, and geo info."""
|
||
|
|
return (
|
||
|
|
"echo '=== Login Tracker: Top 20 IPs ===' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Failed Logins ---' && "
|
||
|
|
"if [ -f /var/log/auth.log ]; then "
|
||
|
|
" FAILED_IPS=$(grep -oP 'Failed password.*from \\K[0-9.]+' /var/log/auth.log 2>/dev/null | "
|
||
|
|
" sort | uniq -c | sort -rn | head -20); "
|
||
|
|
"else "
|
||
|
|
" FAILED_IPS=$(journalctl -u sshd -u ssh --no-pager 2>/dev/null | "
|
||
|
|
" grep -oP 'Failed password.*from \\K[0-9.]+' | "
|
||
|
|
" sort | uniq -c | sort -rn | head -20); "
|
||
|
|
"fi && "
|
||
|
|
"if [ -z \"$FAILED_IPS\" ]; then "
|
||
|
|
" echo 'No failed login attempts found'; "
|
||
|
|
"else "
|
||
|
|
" echo \"$FAILED_IPS\" | while read COUNT IP; do "
|
||
|
|
" GEO=''; "
|
||
|
|
" if command -v geoiplookup >/dev/null 2>&1; then "
|
||
|
|
" GEO=$(geoiplookup \"$IP\" 2>/dev/null | head -1 | sed 's/GeoIP Country Edition: //'); "
|
||
|
|
" elif command -v whois >/dev/null 2>&1; then "
|
||
|
|
" GEO=$(whois \"$IP\" 2>/dev/null | grep -i -m1 'country' | awk '{print $NF}'); "
|
||
|
|
" fi; "
|
||
|
|
" printf '%6s %-16s %s\\n' \"$COUNT\" \"$IP\" \"$GEO\"; "
|
||
|
|
" done; "
|
||
|
|
"fi && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Accepted Logins ---' && "
|
||
|
|
"if [ -f /var/log/auth.log ]; then "
|
||
|
|
" ACCEPT_IPS=$(grep -oP 'Accepted.*from \\K[0-9.]+' /var/log/auth.log 2>/dev/null | "
|
||
|
|
" sort | uniq -c | sort -rn | head -20); "
|
||
|
|
"else "
|
||
|
|
" ACCEPT_IPS=$(journalctl -u sshd -u ssh --no-pager 2>/dev/null | "
|
||
|
|
" grep -oP 'Accepted.*from \\K[0-9.]+' | "
|
||
|
|
" sort | uniq -c | sort -rn | head -20); "
|
||
|
|
"fi && "
|
||
|
|
"if [ -z \"$ACCEPT_IPS\" ]; then "
|
||
|
|
" echo 'No accepted logins found'; "
|
||
|
|
"else "
|
||
|
|
" echo \"$ACCEPT_IPS\" | while read COUNT IP; do "
|
||
|
|
" GEO=''; "
|
||
|
|
" if command -v geoiplookup >/dev/null 2>&1; then "
|
||
|
|
" GEO=$(geoiplookup \"$IP\" 2>/dev/null | head -1 | sed 's/GeoIP Country Edition: //'); "
|
||
|
|
" elif command -v whois >/dev/null 2>&1; then "
|
||
|
|
" GEO=$(whois \"$IP\" 2>/dev/null | grep -i -m1 'country' | awk '{print $NF}'); "
|
||
|
|
" fi; "
|
||
|
|
" printf '%6s %-16s %s\\n' \"$COUNT\" \"$IP\" \"$GEO\"; "
|
||
|
|
" done; "
|
||
|
|
"fi"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def active_sessions_cmd():
|
||
|
|
"""Return bash cmd to show who is currently logged in."""
|
||
|
|
return (
|
||
|
|
"echo '=== Currently Logged In ===' && "
|
||
|
|
"w 2>&1 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '=== Active Sessions (who) ===' && "
|
||
|
|
"who 2>&1 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '=== Recent Logins (last 10) ===' && "
|
||
|
|
"last -10 2>&1"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def file_integrity_check_cmd(paths="/etc /usr/bin /usr/sbin /var/www"):
|
||
|
|
"""Return bash cmd to create/check file integrity baseline."""
|
||
|
|
db = "/var/lib/setec-integrity.db"
|
||
|
|
return (
|
||
|
|
f"if [ -f {db} ]; then "
|
||
|
|
f" echo '=== File Integrity Check (comparing to baseline) ===' && "
|
||
|
|
f" CURRENT=$(mktemp) && "
|
||
|
|
f" find {paths} -type f -exec md5sum {{}} + 2>/dev/null | sort -k2 > \"$CURRENT\" && "
|
||
|
|
f" BASELINE=$(sort -k2 {db}) && "
|
||
|
|
" echo '' && "
|
||
|
|
" echo '--- Modified Files ---' && "
|
||
|
|
f" diff <(echo \"$BASELINE\") \"$CURRENT\" 2>/dev/null | grep '^[<>]' | head -50 && "
|
||
|
|
" echo '' && "
|
||
|
|
" echo '--- New Files (not in baseline) ---' && "
|
||
|
|
f" comm -13 <(awk '{{print $2}}' {db} | sort) <(awk '{{print $2}}' \"$CURRENT\" | sort) | head -50 && "
|
||
|
|
" echo '' && "
|
||
|
|
" echo '--- Deleted Files (in baseline but missing) ---' && "
|
||
|
|
f" comm -23 <(awk '{{print $2}}' {db} | sort) <(awk '{{print $2}}' \"$CURRENT\" | sort) | head -50 && "
|
||
|
|
" MODIFIED=$(diff <(echo \"$BASELINE\") \"$CURRENT\" 2>/dev/null | grep -c '^[<>]' || true) && "
|
||
|
|
" echo '' && "
|
||
|
|
" echo \"Summary: $MODIFIED differences found\" && "
|
||
|
|
" rm -f \"$CURRENT\"; "
|
||
|
|
"else "
|
||
|
|
f" echo 'No baseline found at {db}. Creating initial baseline...' && "
|
||
|
|
f" find {paths} -type f -exec md5sum {{}} + 2>/dev/null | sort -k2 > {db} && "
|
||
|
|
f" COUNT=$(wc -l < {db}) && "
|
||
|
|
" echo \"Baseline created with $COUNT files\"; "
|
||
|
|
"fi"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def file_integrity_init_cmd(paths="/etc /usr/bin /usr/sbin /var/www"):
|
||
|
|
"""Return bash cmd to initialize/reset the integrity baseline."""
|
||
|
|
db = "/var/lib/setec-integrity.db"
|
||
|
|
return (
|
||
|
|
f"echo '=== Initializing File Integrity Baseline ===' && "
|
||
|
|
f"mkdir -p /var/lib && "
|
||
|
|
f"find {paths} -type f -exec md5sum {{}} + 2>/dev/null | sort -k2 > {db} && "
|
||
|
|
f"COUNT=$(wc -l < {db}) && "
|
||
|
|
f"echo \"Baseline created at {db} with $COUNT files\" && "
|
||
|
|
f"echo \"Monitored paths: {paths}\""
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def process_audit_cmd():
|
||
|
|
"""Return bash cmd to find suspicious processes."""
|
||
|
|
return (
|
||
|
|
"echo '=== Process Security Audit ===' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Listening Ports with Processes ---' && "
|
||
|
|
"ss -tlnp 2>&1 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- UDP Listening Ports ---' && "
|
||
|
|
"ss -ulnp 2>&1 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Processes Running as Root (non-kernel) ---' && "
|
||
|
|
"ps aux --sort=-%mem 2>/dev/null | awk '$1==\"root\" && $11!~/^\\[/' | head -30 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Processes with Deleted Binaries (suspicious) ---' && "
|
||
|
|
"ls -la /proc/*/exe 2>/dev/null | grep '(deleted)' || echo 'None found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Recently Modified Binaries (last 7 days) ---' && "
|
||
|
|
"find /usr/bin /usr/sbin /usr/local/bin -type f -mtime -7 -ls 2>/dev/null | head -30 || echo 'None found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Unusual SUID/SGID Binaries ---' && "
|
||
|
|
"find /usr/bin /usr/sbin /usr/local/bin /tmp /var/tmp -type f \\( -perm -4000 -o -perm -2000 \\) -ls 2>/dev/null | head -30 || echo 'None found'"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def security_log_cmd(lines=100):
|
||
|
|
"""Return bash cmd to get combined security-relevant logs."""
|
||
|
|
return (
|
||
|
|
"echo '=== Combined Security Logs ===' && "
|
||
|
|
"( "
|
||
|
|
" [ -f /var/log/auth.log ] && grep -E '(Failed|Accepted|Invalid|authentication failure|sudo|su:)' /var/log/auth.log 2>/dev/null; "
|
||
|
|
" [ -f /var/log/fail2ban.log ] && tail -50 /var/log/fail2ban.log 2>/dev/null; "
|
||
|
|
" [ -f /var/log/kern.log ] && grep -iE '(iptables|firewall|segfault|oom)' /var/log/kern.log 2>/dev/null; "
|
||
|
|
" [ -f /var/log/syslog ] && grep -iE '(security|attack|denied|unauthorized|blocked)' /var/log/syslog 2>/dev/null; "
|
||
|
|
" journalctl -p warning --since '24 hours ago' --no-pager 2>/dev/null | head -50; "
|
||
|
|
f") | sort -t' ' -k1,3 2>/dev/null | tail -n {lines}"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def alert_setup_cmd(email, webhook_url=""):
|
||
|
|
"""Return bash cmd to set up alert script for suspicious activity."""
|
||
|
|
webhook_section = ""
|
||
|
|
if webhook_url:
|
||
|
|
webhook_section = (
|
||
|
|
f" if command -v curl >/dev/null 2>&1; then\\n"
|
||
|
|
f" curl -s -X POST -H 'Content-Type: application/json' "
|
||
|
|
f"-d \\\"{{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}}\\\" "
|
||
|
|
f"'{webhook_url}' >/dev/null 2>&1\\n"
|
||
|
|
f" fi\\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
script = (
|
||
|
|
"#!/bin/bash\\n"
|
||
|
|
"# Setec Labs Security Alert Script\\n"
|
||
|
|
"LOGFILE=/var/log/setec-alerts.log\\n"
|
||
|
|
"STATEFILE=/var/lib/setec-alert-state\\n"
|
||
|
|
"THRESHOLD=10\\n"
|
||
|
|
"\\n"
|
||
|
|
"mkdir -p /var/lib\\n"
|
||
|
|
"touch \\\"$STATEFILE\\\" \\\"$LOGFILE\\\"\\n"
|
||
|
|
"\\n"
|
||
|
|
"LAST_CHECK=$(cat \\\"$STATEFILE\\\" 2>/dev/null || echo 0)\\n"
|
||
|
|
"NOW=$(date +%s)\\n"
|
||
|
|
"echo \\\"$NOW\\\" > \\\"$STATEFILE\\\"\\n"
|
||
|
|
"\\n"
|
||
|
|
"# Check failed SSH logins since last check\\n"
|
||
|
|
"if [ -f /var/log/auth.log ]; then\\n"
|
||
|
|
" FAILED=$(grep 'Failed password' /var/log/auth.log 2>/dev/null | wc -l)\\n"
|
||
|
|
"else\\n"
|
||
|
|
" FAILED=$(journalctl -u sshd --since '5 minutes ago' --no-pager 2>/dev/null | grep -c 'Failed password')\\n"
|
||
|
|
"fi\\n"
|
||
|
|
"\\n"
|
||
|
|
"# Check fail2ban bans\\n"
|
||
|
|
"BANS=0\\n"
|
||
|
|
"if [ -f /var/log/fail2ban.log ]; then\\n"
|
||
|
|
" BANS=$(grep -c 'Ban' /var/log/fail2ban.log 2>/dev/null || echo 0)\\n"
|
||
|
|
"fi\\n"
|
||
|
|
"\\n"
|
||
|
|
"# Check for new SUID files\\n"
|
||
|
|
"NEWSUID=$(find /tmp /var/tmp /home -type f -perm -4000 2>/dev/null | wc -l)\\n"
|
||
|
|
"\\n"
|
||
|
|
"ALERT=0\\n"
|
||
|
|
"MSG=\\\"\\\"\\n"
|
||
|
|
"\\n"
|
||
|
|
"if [ \\\"$FAILED\\\" -gt \\\"$THRESHOLD\\\" ]; then\\n"
|
||
|
|
" MSG=\\\"ALERT: $FAILED failed SSH login attempts detected\\\"\\n"
|
||
|
|
" ALERT=1\\n"
|
||
|
|
"fi\\n"
|
||
|
|
"\\n"
|
||
|
|
"if [ \\\"$NEWSUID\\\" -gt 0 ]; then\\n"
|
||
|
|
" MSG=\\\"$MSG\\\\nALERT: $NEWSUID SUID files found in /tmp, /var/tmp, or /home\\\"\\n"
|
||
|
|
" ALERT=1\\n"
|
||
|
|
"fi\\n"
|
||
|
|
"\\n"
|
||
|
|
"if [ \\\"$ALERT\\\" -eq 1 ]; then\\n"
|
||
|
|
" HOSTNAME=$(hostname)\\n"
|
||
|
|
" MSG=\\\"[$HOSTNAME] $(date): $MSG\\\"\\n"
|
||
|
|
" echo \\\"$MSG\\\" >> \\\"$LOGFILE\\\"\\n"
|
||
|
|
f" if command -v mail >/dev/null 2>&1; then\\n"
|
||
|
|
f" echo -e \\\"$MSG\\\" | mail -s \\\"Setec Security Alert: $HOSTNAME\\\" {email} 2>/dev/null\\n"
|
||
|
|
f" fi\\n"
|
||
|
|
f"{webhook_section}"
|
||
|
|
"fi\\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
return (
|
||
|
|
"echo '=== Setting Up Security Alerting ===' && "
|
||
|
|
f"echo -e '{script}' > /usr/local/bin/setec-alert.sh && "
|
||
|
|
"chmod +x /usr/local/bin/setec-alert.sh && "
|
||
|
|
"touch /var/log/setec-alerts.log && "
|
||
|
|
"(crontab -l 2>/dev/null | grep -v 'setec-alert.sh'; "
|
||
|
|
"echo '*/5 * * * * /usr/local/bin/setec-alert.sh') | crontab - && "
|
||
|
|
f"echo 'Alert script installed at /usr/local/bin/setec-alert.sh' && "
|
||
|
|
f"echo 'Cron job added: runs every 5 minutes' && "
|
||
|
|
f"echo 'Alert email: {email}'"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def alert_status_cmd():
|
||
|
|
"""Return bash cmd to check if alerting is configured and show recent alerts."""
|
||
|
|
return (
|
||
|
|
"echo '=== Alert System Status ===' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Script ---' && "
|
||
|
|
"if [ -f /usr/local/bin/setec-alert.sh ]; then "
|
||
|
|
" echo 'Alert script: INSTALLED'; "
|
||
|
|
" ls -la /usr/local/bin/setec-alert.sh; "
|
||
|
|
"else "
|
||
|
|
" echo 'Alert script: NOT INSTALLED'; "
|
||
|
|
"fi && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Cron Job ---' && "
|
||
|
|
"if crontab -l 2>/dev/null | grep -q 'setec-alert.sh'; then "
|
||
|
|
" echo 'Cron job: ACTIVE'; "
|
||
|
|
" crontab -l 2>/dev/null | grep 'setec-alert.sh'; "
|
||
|
|
"else "
|
||
|
|
" echo 'Cron job: NOT FOUND'; "
|
||
|
|
"fi && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Recent Alerts (last 20) ---' && "
|
||
|
|
"if [ -f /var/log/setec-alerts.log ]; then "
|
||
|
|
" tail -20 /var/log/setec-alerts.log; "
|
||
|
|
"else "
|
||
|
|
" echo 'No alert log found'; "
|
||
|
|
"fi"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def alert_remove_cmd():
|
||
|
|
"""Return bash cmd to remove alerting."""
|
||
|
|
return (
|
||
|
|
"echo '=== Removing Security Alerting ===' && "
|
||
|
|
"(crontab -l 2>/dev/null | grep -v 'setec-alert.sh') | crontab - && "
|
||
|
|
"rm -f /usr/local/bin/setec-alert.sh && "
|
||
|
|
"echo 'Alert script removed' && "
|
||
|
|
"echo 'Cron job removed' && "
|
||
|
|
"echo 'Alert log preserved at /var/log/setec-alerts.log'"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def suid_audit_cmd():
|
||
|
|
"""Return bash cmd to find all SUID/SGID binaries."""
|
||
|
|
return (
|
||
|
|
"echo '=== SUID/SGID Binary Audit ===' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- SUID Binaries ---' && "
|
||
|
|
"find / -type f -perm -4000 -not -path '/proc/*' -not -path '/sys/*' -ls 2>/dev/null && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- SGID Binaries ---' && "
|
||
|
|
"find / -type f -perm -2000 -not -path '/proc/*' -not -path '/sys/*' -ls 2>/dev/null && "
|
||
|
|
"echo '' && "
|
||
|
|
"SUID_COUNT=$(find / -type f -perm -4000 -not -path '/proc/*' -not -path '/sys/*' 2>/dev/null | wc -l) && "
|
||
|
|
"SGID_COUNT=$(find / -type f -perm -2000 -not -path '/proc/*' -not -path '/sys/*' 2>/dev/null | wc -l) && "
|
||
|
|
"echo \"Total: $SUID_COUNT SUID, $SGID_COUNT SGID binaries\""
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def world_writable_cmd():
|
||
|
|
"""Return bash cmd to find world-writable files/dirs."""
|
||
|
|
return (
|
||
|
|
"echo '=== World-Writable Files ===' && "
|
||
|
|
"find / -type f -perm -0002 "
|
||
|
|
"-not -path '/proc/*' -not -path '/sys/*' -not -path '/dev/*' "
|
||
|
|
"-ls 2>/dev/null | head -50 && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '=== World-Writable Directories (without sticky bit) ===' && "
|
||
|
|
"find / -type d -perm -0002 -not -perm -1000 "
|
||
|
|
"-not -path '/proc/*' -not -path '/sys/*' -not -path '/dev/*' "
|
||
|
|
"-ls 2>/dev/null | head -50"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def cron_audit_cmd():
|
||
|
|
"""Return bash cmd to audit all cron jobs on the system."""
|
||
|
|
return (
|
||
|
|
"echo '=== Cron Job Audit ===' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- System Crontab (/etc/crontab) ---' && "
|
||
|
|
"cat /etc/crontab 2>/dev/null || echo 'Not found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- /etc/cron.d/ ---' && "
|
||
|
|
"for f in /etc/cron.d/*; do "
|
||
|
|
" [ -f \"$f\" ] && echo \"== $f ==\" && cat \"$f\" && echo ''; "
|
||
|
|
"done 2>/dev/null && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- /etc/cron.daily/ ---' && "
|
||
|
|
"ls -la /etc/cron.daily/ 2>/dev/null || echo 'Not found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- /etc/cron.hourly/ ---' && "
|
||
|
|
"ls -la /etc/cron.hourly/ 2>/dev/null || echo 'Not found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- /etc/cron.weekly/ ---' && "
|
||
|
|
"ls -la /etc/cron.weekly/ 2>/dev/null || echo 'Not found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- /etc/cron.monthly/ ---' && "
|
||
|
|
"ls -la /etc/cron.monthly/ 2>/dev/null || echo 'Not found' && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- User Crontabs ---' && "
|
||
|
|
"for user in $(cut -f1 -d: /etc/passwd 2>/dev/null); do "
|
||
|
|
" CRON=$(crontab -u \"$user\" -l 2>/dev/null); "
|
||
|
|
" if [ -n \"$CRON\" ]; then "
|
||
|
|
" echo \"== $user ==\"; "
|
||
|
|
" echo \"$CRON\"; "
|
||
|
|
" echo ''; "
|
||
|
|
" fi; "
|
||
|
|
"done && "
|
||
|
|
"echo '' && "
|
||
|
|
"echo '--- Systemd Timers ---' && "
|
||
|
|
"systemctl list-timers --all --no-pager 2>/dev/null || echo 'systemctl not available'"
|
||
|
|
)
|