#!/bin/bash # Audit complet serveur — applicatif + reseau + correlation # Compatible RHEL 7/8/9 # Usage : bash server_audit.sh HOSTNAME=$(hostname -s 2>/dev/null || hostname) DATE=$(date '+%Y-%m-%d %H:%M:%S') EXCLUDE_PROC="kworker|ksoftirq|migration|watchdog|kthread|rcu_|irq/|scsi|ata_|writeback|sshd:.*notty|bash /tmp|server_audit|capture_state|PM2.*God Daemon" EXCLUDE_SVC="auditd|chronyd|crond|dbus|firewalld|getty|irqbalance|kdump|lvm2|lvmetad|NetworkManager|polkit|rsyslog|sshd|sssd|systemd|tuned|qualys|sentinelone|zabbix|commvault|veeam|tina|login|agetty|mingetty|logind|accounts-daemon|udisksd" echo "########################################################" echo "# AUDIT COMPLET — $HOSTNAME" echo "# $DATE" echo "# OS: $(cat /etc/redhat-release 2>/dev/null || head -1 /etc/os-release 2>/dev/null)" echo "# Kernel: $(uname -r)" echo "# Uptime: $(uptime -p 2>/dev/null || uptime)" echo "########################################################" ############################################################## # PARTIE 1 — APPLICATIF ############################################################## echo "" echo "========================================================" echo " PARTIE 1 — ANALYSE APPLICATIVE" echo "========================================================" # ── 1.1 Services applicatifs running ── echo "" echo "=== 1.1 SERVICES APPLICATIFS RUNNING ===" echo "SERVICE|ENABLED|MAIN_PID|USER|EXEC_START" for svc in $(sudo systemctl list-units --type=service --state=running --no-pager --no-legend | awk '{print $1}'); do svc_short=$(echo "$svc" | sed 's/.service//') echo "$svc_short" | grep -qiE "$EXCLUDE_SVC" && continue enabled=$(sudo systemctl is-enabled "$svc" 2>/dev/null) pid=$(sudo systemctl show -p MainPID "$svc" 2>/dev/null | awk -F= '{print $2}') user=$(sudo systemctl show -p User "$svc" 2>/dev/null | awk -F= '{print $2}') execstart="" [ -n "$pid" ] && [ "$pid" != "0" ] && [ -f "/proc/$pid/cmdline" ] && \ execstart=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | cut -c1-150) if [ -z "$execstart" ]; then execstart=$(sudo systemctl show -p ExecStart "$svc" 2>/dev/null | grep -oP '(?<=path=)\S+' | head -1) fi echo "$svc_short|$enabled|$pid|${user:-root}|$execstart" done # ── 1.2 Processus applicatifs ── echo "" echo "=== 1.2 PROCESSUS APPLICATIFS ===" echo "PID|PPID|USER|EXE|CWD|CMDLINE|RESTART_HINT" for pid in $(sudo ls -d /proc/[0-9]* 2>/dev/null | sed 's|/proc/||' | sort -n); do [ ! -f /proc/$pid/cmdline ] && continue cmd=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null) [ -z "$cmd" ] && continue echo "$cmd" | grep -qE '^\[' && continue echo "$cmd" | grep -qiE "$EXCLUDE_PROC" && continue user=$(sudo stat -c %U /proc/$pid 2>/dev/null) ppid=$(sudo awk '/PPid/{print $2}' /proc/$pid/status 2>/dev/null) exe=$(sudo readlink /proc/$pid/exe 2>/dev/null) cwd=$(sudo readlink /proc/$pid/cwd 2>/dev/null) is_interesting=0 [ "$ppid" = "1" ] && is_interesting=1 echo "$cwd" | grep -qi "/applis" && is_interesting=1 echo "$exe" | grep -qiE "node|java|python|ruby|perl|php|tomcat" && is_interesting=1 echo "$cmd" | grep -qiE "/applis|\.jar|\.js |\.py |\.rb " && is_interesting=1 sudo ss -tlnp 2>/dev/null | grep "pid=$pid," >/dev/null && is_interesting=1 [ "$is_interesting" = "0" ] && continue echo "$cmd" | grep -qiE "$EXCLUDE_SVC" && continue # Exclure workers enfants (meme exe que le parent) if [ "$ppid" != "1" ] && [ -n "$exe" ]; then parent_exe=$(sudo readlink /proc/$ppid/exe 2>/dev/null) [ "$exe" = "$parent_exe" ] && continue # Exclure enfants dont le grandparent est PM2 God Daemon grandppid=$(sudo awk '/PPid/{print $2}' /proc/$ppid/status 2>/dev/null) if [ -n "$grandppid" ]; then grandp_cmd=$(tr '\0' ' ' < /proc/$grandppid/cmdline 2>/dev/null) echo "$grandp_cmd" | grep -qiE "PM2.*God Daemon" && continue fi fi # Construire hint de redemarrage hint="" # Priorite 1: systemd svc_match=$(sudo systemctl status $pid 2>/dev/null | head -1 | grep -oP '\S+\.service' | head -1) if [ -z "$svc_match" ]; then exe_name=$(basename "$exe" 2>/dev/null) [ -n "$exe_name" ] && svc_match=$(sudo systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null | grep -i "$exe_name" | awk '{print $1}' | head -1) fi if [ -n "$svc_match" ]; then hint="sudo systemctl restart $svc_match" # Priorite 2: PM2 elif command -v pm2 &>/dev/null; then pm2_name="" pm2_json="" for pm2_user in "$user" root $(sudo ps aux 2>/dev/null | grep -i 'PM2\|pm2' | grep -v grep | awk '{print $1}' | sort -u); do pm2_json=$(su - "$pm2_user" -c "pm2 jlist 2>/dev/null" 2>/dev/null) [ -z "$pm2_json" ] && continue pm2_name=$(echo "$pm2_json" | grep -oP '"pid"\s*:\s*'$pid'\b[^}]*"name"\s*:\s*"\K[^"]+' 2>/dev/null | head -1) [ -n "$pm2_name" ] && break pm2_name=$(echo "$pm2_json" | grep -oP '"name"\s*:\s*"\K[^"]+(?=[^}]*"pid"\s*:\s*'$pid'\b)' 2>/dev/null | head -1) [ -n "$pm2_name" ] && break done if [ -n "$pm2_name" ]; then pm2_bin=$(which pm2 2>/dev/null || echo "/usr/local/bin/pm2") pm2_exec=$(echo "$pm2_json" | grep -oP '"pm_exec_path"\s*:\s*"\K[^"]+' 2>/dev/null | head -1) pm2_args=$(echo "$pm2_json" | grep -oP '"args"\s*:\s*\[\K[^\]]+' 2>/dev/null | head -1 | sed 's/"//g; s/,/ /g') if [ -n "$pm2_exec" ] && [ -n "$pm2_args" ]; then hint="su - $pm2_user -c '$pm2_bin start $pm2_exec --name $pm2_name -- $pm2_args'" else hint="su - $pm2_user -c '$pm2_bin restart $pm2_name'" fi fi fi # Priorite 3: start script dans cwd if [ -z "$hint" ] && [ -n "$cwd" ] && [ -d "$cwd" ]; then start_script=$(find "$cwd" -maxdepth 1 \( -name "start*" -o -name "run*" -o -name "*.sh" \) -executable 2>/dev/null | head -1) if [ -n "$start_script" ]; then hint="su - $user -c 'cd $cwd && $start_script'" else hint="su - $user -c 'cd $cwd && $(echo $cmd | cut -c1-120)'" fi fi # Priorite 4: commande directe if [ -z "$hint" ]; then hint="su - $user -c '$(echo $cmd | cut -c1-120)'" fi echo "$pid|$ppid|$user|$exe|$cwd|$(echo $cmd | cut -c1-150)|$hint" done # ── 1.3 Services failed ── echo "" echo "=== 1.3 SERVICES EN ECHEC ===" failed=$(sudo systemctl list-units --type=service --state=failed --no-pager --no-legend 2>/dev/null) if [ -n "$failed" ]; then echo "$failed" else echo "Aucun service en echec" fi # ── 1.4 Needs-restarting ── echo "" echo "=== 1.4 NEEDS-RESTARTING ===" if ! command -v needs-restarting &>/dev/null; then major=$(rpm -E %{rhel} 2>/dev/null || cat /etc/redhat-release 2>/dev/null | grep -oP '\d+' | head -1) if [ "$major" -ge 9 ] 2>/dev/null; then dnf install -y dnf-utils 2>&1 | tail -1 elif [ "$major" -ge 8 ] 2>/dev/null; then dnf install -y yum-utils 2>&1 | tail -1 else yum install -y yum-utils 2>&1 | tail -1 fi fi if command -v needs-restarting &>/dev/null; then sudo needs-restarting -r 2>/dev/null echo "EXIT_CODE=$?" echo "--- Services a redemarrer ---" sudo needs-restarting -s 2>/dev/null else echo "sudo needs-restarting non disponible" fi # ── 1.5 Espace disque ── echo "" echo "=== 1.5 ESPACE DISQUE ===" df -h --output=target,size,used,avail,pcent 2>/dev/null | grep -vE '^(tmpfs|devtmpfs)' || df -h 2>/dev/null ############################################################## # PARTIE 2 — RESEAU ############################################################## echo "" echo "========================================================" echo " PARTIE 2 — ANALYSE RESEAU" echo "========================================================" # ── 2.1 Interfaces et IPs ── echo "" echo "=== 2.1 INTERFACES RESEAU ===" echo "INTERFACE|IP|MASK|STATE|MAC" ip -4 -o addr show 2>/dev/null | while read idx iface scope ip_mask rest; do ip=$(echo "$ip_mask" | cut -d/ -f1) mask=$(echo "$ip_mask" | cut -d/ -f2) state=$(ip link show "$iface" 2>/dev/null | grep -oP '(?<=state )\S+' | head -1) mac=$(ip link show "$iface" 2>/dev/null | grep -oP 'link/ether \K\S+' | head -1) echo "$iface|$ip|/$mask|${state:-UP}|${mac:--}" done # ── 2.2 Routes ── echo "" echo "=== 2.2 TABLE DE ROUTAGE ===" echo "DESTINATION|GATEWAY|INTERFACE|METRIC" ip route show 2>/dev/null | while read line; do dest=$(echo "$line" | awk '{print $1}') gw=$(echo "$line" | grep -oP '(?<=via )\S+') iface=$(echo "$line" | grep -oP '(?<=dev )\S+') metric=$(echo "$line" | grep -oP '(?<=metric )\S+') echo "$dest|${gw:-direct}|${iface:--}|${metric:--}" done # ── 2.3 Ports en ecoute ── echo "" echo "=== 2.3 PORTS EN ECOUTE ===" echo "PROTO|ADDR:PORT|PID|PROCESS|USER|SERVICE" sudo ss -tlnp 2>/dev/null | grep LISTEN | while read state recvq sendq local peer info; do pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1) proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1) port=$(echo "$local" | grep -oP ':\K[0-9]+$') user="" [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null) svc=$(getent services "$port/tcp" 2>/dev/null | awk '{print $1}') echo "TCP|$local|${pid:--}|${proc:--}|${user:--}|${svc:--}" done sudo ss -ulnp 2>/dev/null | grep -v 'State' | while read state recvq sendq local peer info; do [ -z "$local" ] && continue pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1) proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1) port=$(echo "$local" | grep -oP ':\K[0-9]+$') user="" [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null) svc=$(getent services "$port/udp" 2>/dev/null | awk '{print $1}') echo "UDP|$local|${pid:--}|${proc:--}|${user:--}|${svc:--}" done # ── 2.4 Connexions etablies ── echo "" echo "=== 2.4 CONNEXIONS ETABLIES ===" echo "DIRECTION|PROTO|LOCAL|REMOTE|PID|PROCESS|USER|STATE" listen_ports=$(sudo ss -tlnp 2>/dev/null | grep LISTEN | grep -oP '\S+:(\d+)\s' | grep -oP ':\K\d+' | sort -u) sudo ss -tnp 2>/dev/null | grep -v 'State' | while read state recvq sendq local remote info; do [ "$state" = "State" ] && continue [ -z "$local" ] && continue pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1) proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1) user="" [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null) local_port=$(echo "$local" | grep -oP ':\K[0-9]+$') direction="OUT" echo "$listen_ports" | grep -qx "$local_port" && direction="IN" echo "$direction|TCP|$local|$remote|${pid:--}|${proc:--}|${user:--}|$state" done # ── 2.5 Resume flux entrants ── echo "" echo "=== 2.5 RESUME FLUX ENTRANTS (par port) ===" echo "PORT|SERVICE|PROCESS|NB_CONNEXIONS|SOURCES" for port in $(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -oP ':\K\d+$' | sort -n | uniq); do echo "$listen_ports" | grep -qx "$port" || continue svc=$(getent services "$port/tcp" 2>/dev/null | awk '{print $1}') proc=$(sudo ss -tlnp 2>/dev/null | grep ":$port " | grep -oP '"\K[^"]+' | head -1) count=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -P ":${port}$" | wc -l) sources=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk -v p=":${port}$" '$4 ~ p {print $5}' | grep -oP '^[^:]+' | sort -u | head -10 | tr '\n' ',' | sed 's/,$//') echo "$port|${svc:--}|${proc:--}|$count|${sources:--}" done # ── 2.6 Resume flux sortants ── echo "" echo "=== 2.6 RESUME FLUX SORTANTS (par destination:port) ===" echo "DEST_IP|DEST_PORT|SERVICE|PROCESS|NB_CONNEXIONS" sudo ss -tnp 2>/dev/null | grep ESTAB | while read state recvq sendq local remote info; do local_port=$(echo "$local" | grep -oP ':\K[0-9]+$') echo "$listen_ports" | grep -qx "$local_port" && continue echo "$remote|$(echo "$info" | grep -oP '"\K[^"]+' | head -1)" done | sort | uniq -c | sort -rn | head -50 | while read count dest_info; do dest_ip=$(echo "$dest_info" | grep -oP '^\S+(?=:\d+)') dest_port=$(echo "$dest_info" | grep -oP ':\K\d+(?=\|)') proc=$(echo "$dest_info" | awk -F'|' '{print $2}') svc=$(getent services "$dest_port/tcp" 2>/dev/null | awk '{print $1}') dns=$(timeout 2 host "$dest_ip" 2>/dev/null | grep -oP 'pointer \K\S+' | sed 's/\.$//' | head -1) if [ -n "$dns" ]; then echo "$dest_ip ($dns)|$dest_port|${svc:--}|${proc:--}|$count" else echo "$dest_ip|$dest_port|${svc:--}|${proc:--}|$count" fi done # ── 2.7 Connexions en attente ── echo "" echo "=== 2.7 CONNEXIONS EN ATTENTE ===" echo "STATE|COUNT" for st in TIME-WAIT CLOSE-WAIT FIN-WAIT-1 FIN-WAIT-2 SYN-SENT SYN-RECV LAST-ACK CLOSING; do cnt=$(sudo ss -tn state "$st" 2>/dev/null | grep -c -v 'State') [ "$cnt" -gt 0 ] && echo "$st|$cnt" done # ── 2.8 Stats reseau ── echo "" echo "=== 2.8 STATISTIQUES RESEAU ===" echo "METRIC|VALUE" echo "TCP ESTABLISHED|$(sudo ss -tn state established 2>/dev/null | grep -c -v 'State')" echo "TCP LISTEN|$(sudo ss -tln 2>/dev/null | grep -c LISTEN)" echo "UDP LISTEN|$(sudo ss -uln 2>/dev/null | grep -c -v 'State')" # ── 2.9 Trafic par interface ── echo "" echo "=== 2.9 TRAFIC PAR INTERFACE ===" echo "INTERFACE|RX_BYTES|RX_PACKETS|RX_ERRORS|TX_BYTES|TX_PACKETS|TX_ERRORS" sudo cat /proc/net/dev 2>/dev/null | tail -n+3 | while read line; do iface=$(echo "$line" | awk -F: '{print $1}' | tr -d ' ') stats=$(echo "$line" | awk -F: '{print $2}') rx_bytes=$(echo "$stats" | awk '{print $1}') rx_pkt=$(echo "$stats" | awk '{print $2}') rx_err=$(echo "$stats" | awk '{print $3}') tx_bytes=$(echo "$stats" | awk '{print $9}') tx_pkt=$(echo "$stats" | awk '{print $10}') tx_err=$(echo "$stats" | awk '{print $11}') [ "$iface" = "lo" ] && continue rx_h=$(numfmt --to=iec "$rx_bytes" 2>/dev/null || echo "${rx_bytes}B") tx_h=$(numfmt --to=iec "$tx_bytes" 2>/dev/null || echo "${tx_bytes}B") echo "$iface|$rx_h|$rx_pkt|$rx_err|$tx_h|$tx_pkt|$tx_err" done # ── 2.10 Firewall ── echo "" echo "=== 2.10 FIREWALL ===" if command -v iptables &>/dev/null; then echo "--- POLICY ---" for chain in INPUT OUTPUT FORWARD; do policy=$(iptables -L "$chain" -n 2>/dev/null | head -1 | grep -oP 'policy \K\w+') echo "$chain|$policy" done echo "--- INPUT ---" echo "NUM|TARGET|PROTO|SOURCE|DEST|PORT|INFO" iptables -L INPUT -n -v --line-numbers 2>/dev/null | tail -n+3 | head -30 | while read num pkts bytes target prot opt in out source dest rest; do port=$(echo "$rest" | grep -oP '(dpt|dpts):\K\S+' | head -1) echo "$num|$target|$prot|$source|$dest|${port:--}|$rest" done echo "--- OUTPUT ---" echo "NUM|TARGET|PROTO|SOURCE|DEST|PORT|INFO" iptables -L OUTPUT -n -v --line-numbers 2>/dev/null | tail -n+3 | head -30 | while read num pkts bytes target prot opt in out source dest rest; do port=$(echo "$rest" | grep -oP '(dpt|dpts):\K\S+' | head -1) echo "$num|$target|$prot|$source|$dest|${port:--}|$rest" done else echo "iptables non disponible" fi if sudo systemctl is-active firewalld &>/dev/null 2>&1; then echo "--- FIREWALLD ---" echo "ZONE|SERVICES|PORTS" for zone in $(firewall-cmd --get-active-zones 2>/dev/null | grep -v '^\s' | grep -v '^$'); do svcs=$(firewall-cmd --zone="$zone" --list-services 2>/dev/null | tr ' ' ',') ports=$(firewall-cmd --zone="$zone" --list-ports 2>/dev/null | tr ' ' ',') echo "$zone|${svcs:--}|${ports:--}" done fi ############################################################## # PARTIE 3 — CORRELATION ############################################################## echo "" echo "========================================================" echo " PARTIE 3 — CORRELATION PROCESS ↔ PORTS ↔ FLUX" echo "========================================================" echo "" echo "=== 3.1 MATRICE PROCESS → PORTS → CONNEXIONS ===" echo "PROCESS|USER|PID|LISTEN_PORTS|CONN_IN|CONN_OUT|REMOTE_DESTINATIONS" # Pour chaque process qui ecoute sur un port sudo ss -tlnp 2>/dev/null | grep LISTEN | while read state recvq sendq local peer info; do pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1) proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1) [ -z "$pid" ] && continue port=$(echo "$local" | grep -oP ':\K[0-9]+$') echo "$pid|$proc|$port" done | sort -t'|' -k1,1n -u | while IFS='|' read pid proc port; do user=$(sudo stat -c %U /proc/$pid 2>/dev/null) # Tous les ports en ecoute pour ce PID ports=$(sudo ss -tlnp 2>/dev/null | grep "pid=$pid," | grep -oP '\S+:\K\d+(?=\s)' | sort -un | tr '\n' ',' | sed 's/,$//') # Connexions entrantes sur ces ports conn_in=0 for p in $(echo "$ports" | tr ',' ' '); do c=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -c ":${p}$") conn_in=$((conn_in + c)) done # Connexions sortantes de ce PID conn_out=$(sudo ss -tnp 2>/dev/null | grep ESTAB | grep "pid=$pid," | while read st rq sq lc rm inf; do lp=$(echo "$lc" | grep -oP ':\K\d+$') echo "$listen_ports" | grep -qx "$lp" || echo OUT done | wc -l) # Destinations sortantes dests=$(sudo ss -tnp 2>/dev/null | grep ESTAB | grep "pid=$pid," | awk '{print $5}' | sort -u | head -5 | tr '\n' ',' | sed 's/,$//') echo "$proc|${user:--}|$pid|$ports|$conn_in|$conn_out|${dests:--}" done # Processus avec connexions sortantes mais sans port en ecoute echo "" echo "=== 3.2 PROCESS SORTANTS UNIQUEMENT (pas de port en ecoute) ===" echo "PROCESS|USER|PID|DESTINATIONS" sudo ss -tnp 2>/dev/null | grep ESTAB | while read state recvq sendq local remote info; do pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1) proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1) [ -z "$pid" ] && continue local_port=$(echo "$local" | grep -oP ':\K[0-9]+$') # Verifier si ce process n'ecoute PAS sudo ss -tlnp 2>/dev/null | grep "pid=$pid," >/dev/null && continue echo "$pid|$proc|$remote" done | sort -t'|' -k1,1n -k3 | awk -F'|' '{ if ($1 != prev_pid) { if (prev_pid != "") print prev_pid"|"prev_proc"|"dests prev_pid=$1; prev_proc=$2; dests=$3 } else { dests=dests","$3 } } END { if (prev_pid != "") print prev_pid"|"prev_proc"|"dests }' | while IFS='|' read pid proc dests; do user=$(sudo stat -c %U /proc/$pid 2>/dev/null) echo "$proc|${user:--}|$pid|$dests" done echo "" echo "########################################################" echo "# FIN AUDIT — $HOSTNAME — $(date '+%H:%M:%S')" echo "########################################################"