Sommaire
Introduction
Linux fait tourner la plupart des serveurs exposés à Internet, des conteneurs et des charges cloud visés par les attaquants, et ses incidents diffèrent de ceux de Windows. Il n’y a ni registre ni prefetch, la journalisation dépend beaucoup de la configuration de la machine, et de nombreuses intrusions aboutissent à un mineur de cryptomonnaie, un web shell ou une clé SSH discrète plutôt qu’à un rançongiciel. Ce guide couvre le triage d’un hôte Linux suspect, du premier contact au confinement : quoi collecter, dans quel ordre, avec quelles commandes et quels outils, et ce que signifient les preuves.
Règles de base du triage Linux
- Ne faites pas confiance aux binaires de l’hôte. Les rootkits en espace utilisateur remplacent ps, ls, netstat et ss, et les rootkits LD_PRELOAD interceptent les appels de bibliothèque de ces outils. Apportez des outils liés statiquement sur un support en lecture seule et comparez leurs résultats avec /proc.
- Collectez par ordre de volatilité : la mémoire, puis les connexions réseau et les processus, puis les utilisateurs connectés et les systèmes de fichiers temporaires comme /tmp, /dev/shm et /run, puis le disque et les journaux.
- Limitez votre empreinte. Chaque commande modifie des dates d’accès, écrit dans l’historique du shell et peut écraser des données supprimées. Écrivez sur un support externe ou diffusez la sortie sur le réseau, jamais sur le système de fichiers suspect.
- Ne redémarrez pas. La mémoire, les logiciels malveillants actifs, le contenu de /dev/shm et l’état du réseau disparaissent, et un redémarrage peut déclencher un nettoyage de l’attaquant ou une charge destructrice placée dans un script de démarrage.
- Travaillez en UTC et notez chaque commande avec son horodatage. L’utilitaire script le fait pour vous.
- Si l’hôte est une VM cloud, faites un instantané des disques via l’API du fournisseur avant toute action invasive. Cet instantané est votre copie de preuve si l’hôte est détruit.
sudo mkdir -p /mnt/ir && sudo mount -o ro /dev/sdb1 /mnt/ir # read-only toolkit
export PATH=/mnt/ir/bin:$PATH # trusted static binaries first
export LC_ALL=C TZ=UTC
unset HISTFILE # do not overwrite user history
OUT=/mnt/evidence/$(hostname)-$(date -u +%Y%m%dT%H%M%SZ) # separate writable evidence disk
mkdir -p "$OUT"
script -q -a "$OUT/session.log"
# No spare disk? Stream each output to your analysis host instead:
ps auxwwf | ssh analyst@10.0.0.50 "cat > /cases/host01/ps.txt"cat /etc/ld.so.preload 2>/dev/null # normally missing or empty
echo "LD_PRELOAD=$LD_PRELOAD"
grep -r LD_PRELOAD /etc/environment /etc/profile /etc/profile.d /etc/bash.bashrc 2>/dev/null
ldd /bin/ps /bin/ls /usr/bin/ss # unexpected libraries are a warning signSi /etc/ld.so.preload existe et mentionne une bibliothèque inexpliquée, supposez qu’un rootkit en espace utilisateur est actif et que les outils ordinaires mentent. Passez entièrement aux binaires statiques et à la lecture directe de /proc, et prévoyez une analyse de la mémoire.
La boîte à outils de triage Linux
| Outil | Fonction | Remarques |
|---|---|---|
| UAC (Unix-like Artifacts Collector) | Réponse à chaud scriptée et collecte d’artefacts | Gratuit ; Linux, BSD, macOS, Solaris, AIX ; aucune installation |
| Velociraptor | Réponse à chaud à distance et chasse sur tout le parc | Gratuit ; même serveur pour Windows, Linux et macOS |
| AVML | Acquisition de la mémoire sans module du noyau | Gratuit, par Microsoft ; fonctionne sur la plupart des noyaux modernes |
| LiME | Acquisition de la mémoire via un module du noyau chargeable | Doit être compilé pour le noyau exact en cours d’exécution |
| Volatility 3 | Analyse de la mémoire | Exige une table de symboles (ISF) construite pour le noyau exact |
| busybox et coreutils statiques | Remplaçants de confiance pour ps, ls, netstat et autres | À construire ou télécharger à l’avance, avec calcul des empreintes |
| unhide, chkrootkit, rkhunter | Contrôle des processus et ports cachés et des rootkits | Faux positifs à prévoir ; comparez à une référence saine |
| YARA, THOR Lite, Loki | Analyse par signatures des fichiers et de la mémoire des processus | Règles communautaires pour les mineurs, les web shells et les logiciels malveillants Linux |
| bpftool | Liste des programmes et maps eBPF chargés | Détecte les rootkits eBPF et le traçage inattendu |
| The Sleuth Kit et Plaso | Analyse d’images disque et chronologies | fls, mactime, istat ; log2timeline pour les super chronologies |
| osquery | Requêtes SQL sur l’état du système en direct | Utile pour poser les mêmes questions sur de nombreux hôtes |
Les premières minutes : le contexte
{
date -u; hostname -f; cat /etc/os-release; uname -a
uptime; cat /proc/uptime; timedatectl 2>/dev/null
ip -br addr; ip route
cat /etc/resolv.conf
df -hT; findmnt -D
who -a; w
} > "$OUT/context.txt" 2>&1Notez tout ce qui semble déjà anormal : une durée de fonctionnement correspondant au début de l’activité suspecte, des montages inhabituels comme un tmpfs mal placé, un résolveur DNS inconnu ou une session d’administrateur depuis une adresse inconnue. Si l’hôte fait partie d’un cluster ou se trouve derrière un répartiteur de charge, notez ses pairs ; vous devrez probablement les examiner aussi.
Acquisition et analyse de la mémoire
Les logiciels malveillants sans fichier, le code injecté, les binaires supprimés encore actifs et les configurations déchiffrées se trouvent tous en mémoire. AVML est l’outil le plus simple sur les systèmes modernes, car il n’exige aucun module du noyau ; LiME est la solution de repli lorsqu’AVML ne peut pas lire la mémoire, mais il doit être construit pour la version exacte du noyau, idéalement sur une machine saine équivalente plutôt que sur l’hôte suspect.
# AVML: a single static binary
sudo /mnt/ir/bin/avml "$OUT/memory.lime"
sha256sum "$OUT/memory.lime" | tee -a "$OUT/hashes.txt"
# LiME: module compiled for $(uname -r) on a clean build host
sudo insmod /mnt/ir/lime/lime-$(uname -r).ko "path=$OUT/memory.lime format=lime"
sudo rmmod limeVolatility 3 a besoin d’une table de symboles pour la version exacte du noyau. Générez-la avec dwarf2json à partir des symboles de débogage, publiés sous linux-image-*-dbgsym sur Ubuntu, linux-image-*-dbg sur Debian et kernel-debuginfo sur RHEL. Faites-le sur votre machine d’analyse, pas sur l’hôte suspect.
dwarf2json linux --elf /usr/lib/debug/boot/vmlinux-6.8.0-45-generic > volatility3/symbols/linux/6.8.0-45-generic.json
vol -f memory.lime linux.pslist
vol -f memory.lime linux.pstree
vol -f memory.lime linux.psaux # full command lines
vol -f memory.lime linux.bash # bash history recovered from memory
vol -f memory.lime linux.sockstat # sockets per process
vol -f memory.lime linux.lsof --pid 4127
vol -f memory.lime linux.malfind # injected executable memory
vol -f memory.lime linux.lsmod
vol -f memory.lime linux.check_modules # modules hidden from the module list
vol -f memory.lime linux.check_syscall # hooked system call table entries
vol -f memory.lime linux.envars --pid 4127Le plugin linux.bash est souvent le résultat le plus utile : il récupère l’historique des commandes dans la mémoire des processus bash actifs, y compris les commandes de sessions où l’attaquant a désactivé ou supprimé le fichier d’historique.
Processus
ps -eo pid,ppid,user,lstart,etime,stat,%cpu,%mem,cmd --forest > "$OUT/ps.txt"
ps auxwwf >> "$OUT/ps.txt"
# Raw view from /proc, which a userland rootkit cannot easily filter
for p in /proc/[0-9]*; do
pid=$(basename "$p")
printf '%s\t%s\t%s\t%s\n' "$pid" "$(awk '/^PPid/{print $2}' "$p/status" 2>/dev/null)" \
"$(readlink "$p/exe" 2>/dev/null)" "$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null)"
done > "$OUT/proc-list.tsv"P=4127
ls -l /proc/$P/exe /proc/$P/cwd # "(deleted)" means the binary was removed
tr '\0' ' ' < /proc/$P/cmdline; echo
tr '\0' '\n' < /proc/$P/environ # SSH_CLIENT, PWD, unusual variables
cat /proc/$P/status | grep -E 'Name|PPid|Uid|Gid'
ls -l /proc/$P/fd # open files, sockets, pipes
cat /proc/$P/maps | grep -v '\.so' # anonymous and odd mappings
cat /proc/$P/net/tcp /proc/$P/net/tcp6
# Recover the binary even if it was deleted from disk
cp /proc/$P/exe "$OUT/pid-$P.bin" && sha256sum "$OUT/pid-$P.bin" | tee -a "$OUT/hashes.txt"# Processes whose binary has been deleted
ls -l /proc/[0-9]*/exe 2>/dev/null | grep deleted
# Processes running from temporary or hidden locations
ls -l /proc/[0-9]*/exe 2>/dev/null | grep -E '/tmp/|/var/tmp/|/dev/shm/|/run/user/|/\.'
# User processes pretending to be kernel threads: real kernel threads have PPID 2 and no exe
ps -eo pid,ppid,args | awk '$2 != 2 && $2 != 0 && $3 ~ /^\[/'
# Hash every running executable through /proc, including deleted ones
sha256sum /proc/[0-9]*/exe 2>/dev/null | sort -u -k1,1 > "$OUT/exe-hashes.txt"
# Processes hidden from ps
unhide proc; unhide sys- Interpréteurs avec sockets réseau : bash, sh, python, perl ou php tenant une connexion TCP signifie généralement un reverse shell. Recherchez /dev/tcp dans les lignes de commande.
- Enfants du serveur web : nginx, apache2, httpd, php-fpm ou java lançant sh, bash, curl, wget ou nc indique une exécution de code via l’application web.
- Une charge processeur élevée et durable d’un processus au nom inconnu est le signe classique d’un mineur, souvent nommé comme un service système (kworkerds, kthreaddi, systemd-udevd au mauvais emplacement).
- Processus dont le répertoire de travail est /tmp, /dev/shm ou un dossier caché, et processus de comptes de service comme www-data ou postgres qui ne devraient jamais lancer d’outils interactifs.
État du réseau
{
ss -tunap; ss -xp
ip addr; ip route; ip neigh
ip link | grep -i promisc # sniffers put interfaces in promiscuous mode
iptables-save 2>/dev/null; nft list ruleset 2>/dev/null
cat /etc/hosts
conntrack -L 2>/dev/null | head -500
} > "$OUT/network.txt" 2>&1
# Compare against the kernel tables directly
cat /proc/net/tcp /proc/net/tcp6 /proc/net/udp > "$OUT/proc-net.txt"
unhide-tcp # ports in use but hidden from ssSurveillez les connexions vers des ports inhabituels (3333, 4444, 5555, 7777 et 14444 sont des ports courants de pools de minage et de reverse shells), les connexions sortantes durables des processus serveur, les écoutes sur des ports élevés liées à toutes les interfaces et les règles de pare-feu non créées par la gestion de configuration. Une courte capture de paquets peut confirmer le contenu d’une connexion suspecte ; excluez-en votre propre session.
timeout 300 tcpdump -i any -nn -s 0 -w "$OUT/capture.pcap" 'not (host 10.0.0.50 and port 22)'Utilisateurs, clés et authentification
Sous Linux, les attaquants conservent leur accès surtout via des comptes et des clés SSH plutôt que des logiciels malveillants. Une nouvelle clé autorisée survit à chaque correctif, à chaque nettoyage et à chaque changement de mot de passe : la revue des clés et des comptes est donc l’une des parties les plus importantes du triage Linux.
awk -F: '$3 == 0 {print "UID 0:", $1}' /etc/passwd # only root expected
awk -F: '$3 >= 1000 || $7 !~ /(nologin|false)$/ {print $1, $3, $6, $7}' /etc/passwd
awk -F: '$2 !~ /^[!*]/ {print "has password:", $1}' /etc/shadow
grep -E '^(sudo|wheel|adm|docker|lxd|disk):' /etc/group
cat /etc/sudoers /etc/sudoers.d/* 2>/dev/null | grep -Ev '^\s*(#|$)'
ls -la --time-style=full-iso /etc/passwd /etc/shadow /etc/group /etc/sudoers /etc/sudoers.d/L’appartenance à docker, lxd ou disk équivaut en pratique à root, et les attaquants s’y ajoutent parce que peu de gens le vérifient. Une règle NOPASSWD pour ALL, ou pour un éditeur, un interpréteur ou find, équivaut aussi à root. Comparez les dates de modification des fichiers d’identité à la fenêtre de l’incident.
sshd -T 2>/dev/null | grep -Ei 'authorizedkeysfile|authorizedkeyscommand|permitrootlogin|passwordauthentication'
awk -F: '{print $1, $6}' /etc/passwd | while read -r user home; do
for f in "$home/.ssh/authorized_keys" "$home/.ssh/authorized_keys2"; do
[ -f "$f" ] || continue
echo "===== $user $f"
ls -la --time-style=full-iso "$f"
cat "$f"
done
done > "$OUT/authorized-keys.txt" 2>&1
lsattr /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null # immutable flag set?Recherchez les clés impossibles à relier à une personne ou à un système, les commentaires hors de votre convention (ou absents), les options forced command ou from que vous n’avez pas définies, les clés sur des comptes de service comme www-data ou postgres, et les fichiers authorized_keys rendus immuables avec chattr pour faire échouer le nettoyage. Vérifiez aussi que sshd ne lit que les fichiers de clés attendus ; un attaquant peut ajouter un chemin AuthorizedKeysFile dans sshd_config.
last -Faiwx > "$OUT/last.txt" # wtmp: logins, reboots, shutdowns
lastb -Faiw > "$OUT/lastb.txt" 2>/dev/null # btmp: failed logins
lastlog 2>/dev/null > "$OUT/lastlog.txt"
# Newer distributions record logins in wtmpdb and lastlog2 instead
wtmpdb last 2>/dev/null >> "$OUT/last.txt"; lastlog2 2>/dev/null >> "$OUT/lastlog.txt"
# Debian and Ubuntu: /var/log/auth.log RHEL: /var/log/secure
zgrep -h 'Accepted' /var/log/auth.log* /var/log/secure* 2>/dev/null | sort > "$OUT/ssh-accepted.txt"
zgrep -h 'Failed password' /var/log/auth.log* /var/log/secure* 2>/dev/null |
grep -oE 'from [0-9a-f.:]+' | sort | uniq -c | sort -rn | head -30
journalctl _COMM=sshd --since "2026-10-20" -o short-iso-precise --utc | grep -E 'Accepted|session opened'
journalctl _COMM=sudo --since "2026-10-20" -o short-iso-precise --utcAvec LogLevel VERBOSE, chaque connexion par clé acceptée est journalisée avec l’empreinte de la clé, que vous pouvez comparer aux entrées d’authorized_keys avec ssh-keygen -lf. Une connexion réussie depuis une adresse présente aussi dans la liste des échecs de mot de passe est la signature d’une force brute réussie. Vérifiez la falsification de wtmp avec utmpdump : enregistrements mis à zéro, trous dans les entrées de redémarrage ou fichier bien plus petit que ne le suggère la rotation des journaux.
Journaux et historique
| Source | Debian et Ubuntu | Famille RHEL | Ce qu’on y voit |
|---|---|---|---|
| Authentification | /var/log/auth.log | /var/log/secure | SSH, sudo, su, PAM, changements d’utilisateurs |
| Système | /var/log/syslog | /var/log/messages | Services, cron, noyau, la plupart des démons |
| Journal systemd | /var/log/journal/ (s’il est persistant) | /var/log/journal/ (s’il est persistant) | Tout ce qui précède, structuré, avec les métadonnées des processus |
| Audit | /var/log/audit/audit.log | /var/log/audit/audit.log | Appels système, exécutions, surveillance de fichiers, connexions si configuré |
| Changements de paquets | /var/log/dpkg.log, /var/log/apt/history.log | /var/log/dnf.log, dnf history | Logiciels installés ou supprimés par l’attaquant |
| Cron | syslog, journal | /var/log/cron | Exécutions des tâches planifiées |
| Serveurs web | /var/log/nginx/, /var/log/apache2/ | /var/log/nginx/, /var/log/httpd/ | Tentatives d’exploitation, usage de web shells |
| Connexions | /var/log/wtmp, btmp, lastlog | /var/log/wtmp, btmp, lastlog | Sessions, échecs, dernière connexion par utilisateur |
| Historique du shell | ~/.bash_history, ~/.zsh_history | ~/.bash_history, ~/.zsh_history | Commandes saisies, si non désactivé |
| Conteneurs | /var/lib/docker/containers/*/*-json.log | /var/lib/docker/containers/*/*-json.log | stdout et stderr des conteneurs |
| Agents cloud | /var/log/cloud-init.log, agents du fournisseur | /var/log/cloud-init.log, agents du fournisseur | Scripts user data et changements de métadonnées |
journalctl --list-boots
journalctl --since "2026-10-20 00:00" --until "2026-10-28 00:00" -o short-iso-precise --utc > "$OUT/journal.txt"
journalctl -k --since "2026-10-20" --utc | grep -Ei 'module|taint|segfault|denied'
journalctl --verify 2>&1 | tail # detects corrupted or altered journal files
ausearch -i -m USER_LOGIN,USER_AUTH,USER_CMD --start 10/20/2026 00:00:00 > "$OUT/audit-auth.txt"
ausearch -i -m EXECVE --start recent | head -200
aureport -x --summary # most executed programs
aureport --login --failed --summary
# ausearch dates follow the system locale; use "recent", "today" or "this-week" to avoid surprisesfor h in /root /home/*; do
for f in "$h"/.*history; do
[ -f "$f" ] || continue
echo "===== $f"
ls -la --time-style=full-iso "$f"
cat "$f"
done
done > "$OUT/histories.txt" 2>&1
ls -la /root/.bash_history /home/*/.bash_history 2>/dev/null | grep -E ' -> |^l| 0 ' # symlinked to /dev/null or emptyBash n’écrit l’historique qu’à la fermeture normale d’un shell, sans horodatage sauf si HISTTIMEFORMAT est défini, et il se désactive très facilement. Voyez-le comme un bonus plutôt qu’un enregistrement fiable. Les signes de falsification sont un fichier d’historique lié à /dev/null, HISTFILE ou HISTSIZE modifiés dans un script de profil, un historique qui s’arrête brusquement et des commandes comme history -c, unset HISTFILE ou shred dans ce qu’il reste.
L=/var/log/nginx/access.log*
zcat -f $L | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
zcat -f $L | grep -E '"POST [^"]+\.(php|jsp|aspx?|cgi)' | awk '{print $7}' | sort | uniq -c | sort -rn | head -30
zcat -f $L | grep -Ei 'union.+select|\.\./\.\.|/etc/passwd|cmd=|exec=|base64_decode|wget%20|curl%20|\$\{jndi:' | head -50
zcat -f $L | awk '$9 == 200 && $7 ~ /\.(php|jsp)/' | grep -Ev 'index\.php|wp-cron' | awk '{print $7}' | sort | uniq -c | sort -n | headUn web shell apparaît généralement comme un script rarement demandé, souvent dans un dossier d’envoi ou de cache, recevant des requêtes POST d’un petit nombre d’adresses et renvoyant 200. Dès que vous trouvez une requête, partez de l’adresse du client et de l’user agent pour trouver tout ce qu’il a fait, puis du nom du fichier pour savoir quand il a été créé.
Rechercher la persistance
Les mécanismes de persistance Linux sont de simples fichiers à des endroits connus, faciles à rechercher une fois la liste connue. Le tableau résume les emplacements courants ; les commandes ci-dessous les collectent. Examinez d’abord les dates de modification dans la fenêtre de l’incident et tout ce qui télécharge, décode ou lance quelque chose depuis un répertoire temporaire.
| Mécanisme | Où regarder | Signaux d’alerte |
|---|---|---|
| Cron et at | /etc/crontab, /etc/cron.*, /var/spool/cron/, /etc/anacrontab, atq | curl ou wget envoyés vers sh, Base64, tâches à chaque minute, tâches de comptes de service |
| Unités et timers systemd | /etc/systemd/system/, /usr/lib/systemd/system/, ~/.config/systemd/user/, générateurs | Nouvelles unités aux noms génériques, ExecStart dans /tmp ou un dossier personnel, Restart=always sur des binaires inconnus |
| SSH | authorized_keys, sshd_config, ~/.ssh/rc, /etc/ssh/sshrc | Clés inconnues, chemins AuthorizedKeysFile supplémentaires, scripts rc |
| Fichiers de démarrage du shell | /etc/profile, /etc/profile.d/, /etc/bash.bashrc, ~/.bashrc, ~/.profile, ~/.bash_logout | Commandes en arrière-plan, alias remplaçant sudo ou ssh, LD_PRELOAD exporté |
| Éditeur de liens dynamique | /etc/ld.so.preload, /etc/ld.so.conf.d/ | Toute entrée preload ; bibliothèques à des chemins inhabituels |
| PAM | /etc/pam.d/, /lib/x86_64-linux-gnu/security/, /usr/lib64/security/ | pam_unix.so modifié, modules inconnus, lignes pam_exec |
| Modules du noyau | /etc/modules, /etc/modules-load.d/, lsmod, /sys/module | Modules non signés ou hors arbre, noyau marqué tainted, modules absents de lsmod |
| Scripts d’init et rc.local | /etc/rc.local, /etc/init.d/ | Scripts récemment ajoutés sur des hôtes systemd |
| MOTD et hooks de connexion | /etc/update-motd.d/ | Scripts exécutés en root à chaque connexion SSH |
| Règles udev | /etc/udev/rules.d/ | Entrées RUN+= lançant des scripts |
| Hooks du gestionnaire de paquets | /etc/apt/apt.conf.d/, /etc/yum/pluginconf.d/, /etc/dnf/plugins/ | Commandes Pre-Invoke ou Post-Invoke |
| Web shells | Racines de documents, dossiers d’envoi, de plugins et de cache | Scripts récemment modifiés avec eval, assert, system ou des charges Base64 |
| setuid et capabilities | Partout sur le disque | Nouveaux binaires setuid, cap_setuid sur des interpréteurs |
{
ls -la --time-style=full-iso /etc/cron* /var/spool/cron /var/spool/cron/crontabs 2>/dev/null
cat /etc/crontab /etc/cron.d/* /etc/anacrontab 2>/dev/null
for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$u" 2>/dev/null | sed "s/^/[$u] /"; done
atq 2>/dev/null; ls -la /var/spool/cron/atjobs /var/spool/at 2>/dev/null
} > "$OUT/cron.txt" 2>&1
{
systemctl list-unit-files --state=enabled --no-pager
systemctl list-timers --all --no-pager
find /etc/systemd /usr/lib/systemd /lib/systemd /run/systemd /home/*/.config/systemd /root/.config/systemd \
-type f -newermt "2026-10-01" -printf '%TY-%Tm-%Td %TT %p\n' 2>/dev/null | sort
ls -la /etc/systemd/system-generators /usr/lib/systemd/system-generators 2>/dev/null
} > "$OUT/systemd.txt" 2>&1
systemctl cat suspicious.servicels -la --time-style=full-iso /etc/profile /etc/profile.d/ /etc/bash.bashrc /etc/environment \
/root/.bashrc /root/.profile /home/*/.bashrc /home/*/.profile /home/*/.bash_logout 2>/dev/null
grep -HnE 'curl|wget|nc |ncat|/dev/tcp|base64|python.*-c|nohup|setsid|LD_PRELOAD|alias (sudo|ssh)' \
/etc/profile /etc/profile.d/* /etc/bash.bashrc /root/.bashrc /home/*/.bashrc /home/*/.profile 2>/dev/null
cat /etc/ld.so.preload 2>/dev/null; ls -la /etc/ld.so.conf.d/
grep -rn 'pam_exec\|pam_script' /etc/pam.d/ 2>/dev/null
ls -la --time-style=full-iso /lib/x86_64-linux-gnu/security/ /usr/lib64/security/ 2>/dev/null | sort -k6,7
ls -la --time-style=full-iso /etc/update-motd.d/ /etc/rc.local /etc/init.d/ 2>/dev/null
grep -rn 'RUN+=' /etc/udev/rules.d/ 2>/dev/null
grep -rn 'Invoke' /etc/apt/apt.conf.d/ 2>/dev/nullLa vérification des paquets est le moyen le plus rapide de repérer des fichiers système modifiés, comme un pam_unix.so, un sshd ou un ps piégé. Elle compare les fichiers installés aux sommes de contrôle enregistrées à l’installation. Un attaquant root peut aussi falsifier la base de paquets : voyez un résultat propre comme un indice plutôt qu’une preuve, et vérifiez les binaires critiques sur un système sain.
dpkg --verify 2>/dev/null | grep -v ' c /etc/' > "$OUT/dpkg-verify.txt" # Debian and Ubuntu
debsums -c 2>/dev/null >> "$OUT/dpkg-verify.txt"
rpm -Va 2>/dev/null | grep -E '^..5' > "$OUT/rpm-verify.txt" # RHEL: changed digest
# Who owns a suspicious file? Nothing means it did not come from a package
dpkg -S /usr/lib/x86_64-linux-gnu/security/pam_unix.so
rpm -qf /usr/lib64/security/pam_unix.solsmod > "$OUT/lsmod.txt"
cat /proc/sys/kernel/tainted # 0 is clean; non-zero needs explaining
diff <(lsmod | awk 'NR>1 {print $1}' | sort) <(ls /sys/module | sort) | grep '^>' # names in /sys only
for m in $(lsmod | awk 'NR>1 {print $1}'); do modinfo -F filename "$m"; done | grep -v '^/lib/modules'
dmesg -T | grep -Ei 'taint|module verification|loading out-of-tree'
bpftool prog list 2>/dev/null > "$OUT/bpf-progs.txt" # eBPF programs; compare with expected agents
bpftool link list 2>/dev/null >> "$OUT/bpf-progs.txt"
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -newermt "2026-10-01" -ls 2>/dev/null
getcap -r / 2>/dev/nullDe nombreuses entrées de /sys/module sont des composants intégrés au noyau et non des modules chargeables : la comparaison ci-dessus demande donc du discernement, mais un module chargé (avec un fichier initstate) absent de lsmod a été caché volontairement. Les rootkits soigneux disparaissent des deux vues, d’où l’importance de l’analyse mémoire avec linux.check_modules. Les programmes eBPF sont normaux pour les agents EDR, Cilium, systemd et les outils d’observabilité ; un programme eBPF que vous ne pouvez attribuer à aucun logiciel installé mérite une escalade, car les rootkits eBPF peuvent cacher des processus, des fichiers et du trafic réseau.
find /var/www /srv /usr/share/nginx /opt -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.jsp' -o -name '*.jspx' \) \
-newermt "2026-10-01" -printf '%TY-%Tm-%Td %TT %u %p\n' 2>/dev/null | sort
grep -rlE 'eval\s*\(\s*(base64_decode|gzinflate|str_rot13|\$_(POST|GET|REQUEST|COOKIE))|assert\s*\(\s*\$_|system\s*\(\s*\$_|passthru|shell_exec|Runtime\.getRuntime\(\)\.exec' \
/var/www 2>/dev/null
# PHP files in upload folders should not exist at all
find /var/www -path '*upload*' -name '*.php*' 2>/dev/nullTriage du système de fichiers
Les systèmes de fichiers Linux gardent jusqu’à quatre dates par fichier : modification (mtime), accès (atime, souvent peu fiable à cause de relatime), changement d’inode (ctime) et, sur ext4, XFS et Btrfs, création (birth time). Les attaquants réinitialisent souvent mtime avec touch, mais ctime ne peut pas être défini depuis l’espace utilisateur sans changer l’horloge système : un fichier dont le ctime est bien plus récent que le mtime a donc été modifié récemment.
# Content modified in the window
find / -xdev -type f -newermt "2026-10-20" ! -newermt "2026-10-28" \
-printf '%TY-%Tm-%Td %TT %u:%g %m %s %p\n' 2>/dev/null | sort > "$OUT/mtime-window.txt"
# Inode changed in the window, which catches timestomped files
find / -xdev -type f -newerct "2026-10-20" ! -newerct "2026-10-28" \
-printf '%CY-%Cm-%Cd %CT %p\n' 2>/dev/null | sort > "$OUT/ctime-window.txt"
# All four timestamps for one file, including birth time on current coreutils
stat --format='%n birth=%w modify=%y change=%z access=%x' /usr/local/bin/kswapd1ls -laR --time-style=full-iso /tmp /var/tmp /dev/shm /run/user 2>/dev/null > "$OUT/tmp-listing.txt"
# ELF binaries where software should not live
find /tmp /var/tmp /dev/shm /run /var/www /home -type f -size -20M -exec file {} + 2>/dev/null | grep -i 'ELF'
# Hidden names designed to be overlooked: "...", ". ", names starting with a dot in system folders
find / -xdev \( -name '...' -o -name '.. ' -o -name '. ' -o -name ' ' \) 2>/dev/null
find /usr /etc /var/lib /lib -xdev -name '.*' -type f 2>/dev/null | grep -v -E '\.(placeholder|keep|gitignore|build-id)'
# Immutable or append-only flags used to block cleanup
lsattr -R /etc /root /home /var/spool/cron 2>/dev/null | grep -E '^[^ ]*[ia][^ ]* 'sha256sum /tmp/.x /dev/shm/kworker /usr/local/bin/kswapd1 | tee -a "$OUT/hashes.txt"
file /tmp/.x; strings -n 8 /tmp/.x | head -100 # mining pool URLs, C2 addresses, wallet strings
yara -r -s -w /mnt/ir/rules/linux.yar /tmp /var/tmp /dev/shm /home /var/www /usr/local/bin 2>/dev/null
yara -s /mnt/ir/rules/linux.yar 4127 # scan a running process by PID
/mnt/ir/thor-lite/thor-lite-linux-64 -p /tmpCopiez les binaires suspects dans votre dossier de preuves avant toute autre action, et soumettez aux services publics les empreintes plutôt que les fichiers : envoyer un échantillon à VirusTotal le rend accessible à tous les abonnés, y compris l’attaquant, qui apprend ainsi que vous avez trouvé son outil.
Collecte avec UAC, Velociraptor et images disque
UAC réunit tout ce qui précède en une seule commande reproductible. Il s’exécute depuis un dossier du support d’outils, utilise les outils présents sur la cible, collecte les sorties de réponse à chaud, les journaux, la configuration, les historiques et un fichier body pour la chronologie, et produit une seule archive avec une empreinte et un journal d’acquisition.
cd /mnt/ir/uac
sudo ./uac -p ir_triage /mnt/evidence # standard triage profile
sudo ./uac -a ./artifacts/memory_dump/avml.yaml -p ir_triage /mnt/evidence # plus memory
sudo ./uac -p full /mnt/evidence # broader, slower
# Stream the output when there is no local disk to write to
sudo ./uac -p ir_triage --sftp analyst@10.0.0.50:/cases/host01 /tmpPour un parc, Velociraptor offre la même capacité à distance, avec des chasses qui exécutent un artefact sur tous les serveurs Linux à la fois. Parmi les artefacts utiles figurent Linux.Sys.Pslist, Linux.Ssh.AuthorizedKeys, Linux.Sys.Crontab, Linux.Sys.Users et Linux.Search.FileFinder pour la recherche d’indicateurs, ainsi que les artefacts d’analyse YARA des fichiers et de la mémoire des processus.
sudo tar -czf - --ignore-failed-read \
/etc /var/log /var/spool/cron /root/.ssh /root/.bash_history \
/home/*/.ssh /home/*/.bash_history /tmp /var/tmp /dev/shm 2>/dev/null |
ssh analyst@10.0.0.50 "tee /cases/host01/host01-triage.tgz | sha256sum > /cases/host01/host01-triage.tgz.sha256"Faites une image complète du disque si vous avez besoin de récupérer des fichiers supprimés, de l’espace résiduel ou de preuves de niveau judiciaire. Sur une machine physique, créez l’image avec dc3dd ou ewfacquire sur un stockage externe. Sur une VM cloud, faites un instantané du volume via le fournisseur, créez un nouveau volume à partir de cet instantané et attachez-le à une instance forensique dédiée dans un réseau isolé. Montez-le en lecture seule et avec noload sur ext4, ce qui empêche le noyau de rejouer le journal et de modifier la preuve.
# Physical or local virtual disk
sudo dc3dd if=/dev/sda hof=/mnt/evidence/host01-sda.dd hash=sha256 log=/mnt/evidence/host01-sda.log
# AWS
aws ec2 create-snapshot --volume-id vol-0abc123 --description "IR-2026-031 host01 root" \
--tag-specifications 'ResourceType=snapshot,Tags=[{Key=case,Value=IR-2026-031}]'
# On the forensic instance, with the copied volume attached as /dev/nvme1n1
sudo mount -o ro,noload,noexec,nodev /dev/nvme1n1p1 /mnt/case
sudo fls -r -m / /dev/nvme1n1p1 > host01.body
sudo mactime -b host01.body -d -z UTC 2026-10-20..2026-10-28 > host01-timeline.csvConteneurs et Kubernetes
Dans les environnements de conteneurs, la charge compromise peut disparaître en quelques minutes lorsque l’orchestrateur remplace les pods défaillants. Capturez d’abord, puis contenez : mettez le conteneur en pause au lieu de l’arrêter, exportez son système de fichiers et notez sa configuration, car les preuves disparaissent avec le conteneur.
docker ps -a --no-trunc
docker inspect web-1 > "$OUT/web-1-inspect.json" # privileged? host mounts? capabilities?
docker top web-1
docker diff web-1 > "$OUT/web-1-diff.txt" # files added (A), changed (C), deleted (D)
docker logs --timestamps web-1 > "$OUT/web-1.log" 2>&1
docker pause web-1 # freeze processes, keep memory and state
docker export web-1 -o "$OUT/web-1-fs.tar"
# Kubernetes
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl describe pod web-7d9f -n prod
kubectl logs web-7d9f -n prod --all-containers --timestamps --previous
kubectl get clusterrolebindings -o wide | grep -v system: # unexpected cluster-admin grantsdocker diff est l’une des commandes de triage les plus rapides : une image de conteneur doit être immuable, donc chaque fichier ajouté ou modifié dans un conteneur actif est soit un état normal de l’application, soit une activité de l’attaquant. Vérifiez avec docker inspect le mode privilégié, les espaces de noms PID ou réseau de l’hôte et les montages de /, /var/run/docker.sock ou /etc, qui transforment chacun une compromission de conteneur en compromission de l’hôte. Dans le cloud, examinez aussi les journaux d’audit du fournisseur (CloudTrail, Azure Activity Log, Google Cloud Audit Logs) pour repérer l’usage des identifiants du rôle d’instance depuis l’extérieur.
Contrôle des rootkits
Les rootkits Linux existent sous trois formes : en espace utilisateur, remplaçant des binaires ou préchargeant des bibliothèques ; sous forme de modules du noyau, comme Diamorphine et Reptile ; et, plus récemment, fondés sur eBPF. Chacun cache des processus, des fichiers et des connexions aux outils ordinaires : la détection repose donc sur la comparaison de deux vues d’une même chose et sur leurs désaccords.
- Processus : la sortie de ps, un parcours brut de /proc et les vues mémoire de Volatility de type linux.pslist et linux.psscan.
- Ports : la sortie de ss, /proc/net/tcp et unhide-tcp, qui teste chaque port en essayant de s’y lier.
- Modules : lsmod, /sys/module et linux.check_modules en mémoire, ainsi que l’indicateur taint du noyau.
- Appels système : linux.check_syscall et linux.check_idt en mémoire révèlent les points d’entrée du noyau détournés.
- Fichiers : une liste de répertoire sur l’hôte actif et le même répertoire sur un instantané de disque monté, que le noyau actif ne contrôle pas.
/mnt/ir/bin/chkrootkit -q
rkhunter --check --sk --rwo --nocolors
unhide proc; unhide sys; unhide brute
unhide-tcpSchémas d’intrusion Linux courants
Mineur de cryptomonnaie sur un service exposé
La compromission Linux la plus courante. L’entrée est généralement un mot de passe SSH faible, un Redis, une API Docker ou un tableau de bord Kubernetes exposés, ou une application web non corrigée. Attendez-vous à un binaire comme XMRig renommé en thread du noyau dans /tmp, /dev/shm ou /var/tmp, à une tâche cron ou une unité systemd qui le retélécharge toutes les quelques minutes, à une nouvelle clé autorisée, à des mineurs concurrents arrêtés par un script et à des connexions vers des ports de pool. Récupérez le script de dépôt : il documente tout ce que l’attaquant a changé. Supprimez toute la persistance avant d’arrêter le processus, sinon il reviendra.
Web shell via une application vulnérable
Recherchez un script dans la racine de documents ou le dossier d’envoi, des requêtes POST vers celui-ci dans le journal d’accès et des processus enfants de l’utilisateur du serveur web. Ensuite, l’attaquant lit généralement les fichiers de configuration pour obtenir les identifiants de bases de données et de cloud : supposez donc que tout secret lisible par cet utilisateur est compromis et changez-le. Vérifiez s’il est passé root via une vulnérabilité locale du noyau ou de sudo, en recherchant des fichiers appartenant à root créés peu après l’apparition du web shell.
Identifiants ou clés SSH volés
Une connexion acceptée depuis une adresse inconnue à une heure inhabituelle, avec un nom d’utilisateur connu et une empreinte correspondant à la clé d’un vrai employé, indique le vol d’une clé sur un poste ou un système CI plutôt qu’une force brute. Examinez la source de la clé autant que le serveur. Vérifiez si l’attaquant a utilisé le transfert d’agent SSH ou trouvé des clés privées sur ce serveur pour aller plus loin ; recherchez la même adresse source dans les historiques de shell et les journaux d’authentification des hôtes voisins.
Vol d’identifiants cloud
Sur les VM cloud, la cible la plus précieuse est souvent le rôle d’instance. Recherchez les requêtes vers le service de métadonnées à 169.254.169.254 dans les journaux web (falsification de requête côté serveur), les fichiers d’identifiants dans ~/.aws, ~/.azure, ~/.config/gcloud et les variables d’environnement des processus applicatifs, puis vérifiez dans le journal d’audit du fournisseur si ces identifiants ont été utilisés depuis des adresses extérieures à votre environnement.
Confinement et reprise
- Isolez d’abord au niveau du réseau : remplacez les règles du groupe de sécurité ou du pare-feu du fournisseur par une autorisation pour votre seul hôte d’analyse. Cela fonctionne même si l’attaquant est root sur la VM et peut modifier le pare-feu de l’hôte.
- Figez plutôt que d’arrêter. kill -STOP sur un processus malveillant le stoppe en gardant sa mémoire pour la collecte ; mettre un conteneur en pause fait de même. N’arrêtez qu’après avoir collecté la mémoire et les binaires.
- Faites un instantané des disques avant l’éradication, pour que les preuves survivent à la reconstruction.
- Changez tout ce que l’hôte pouvait atteindre : clés SSH d’hôte et d’utilisateur, jetons d’API, mots de passe de bases de données, clés d’accès cloud et sessions de rôle d’instance, clés privées TLS et secrets dans les variables d’environnement ou les fichiers de configuration.
- Reconstruisez à partir d’une image saine ou de la gestion de configuration plutôt que de nettoyer sur place. Une compromission root d’un hôte Linux ne peut pas être nettoyée de façon fiable.
- Corrigez le point d’entrée avant la remise en service : corrigez l’application, fermez le service exposé, imposez SSH par clés uniquement et la MFA sur la console cloud.
- Balayez le parc avec les indicateurs trouvés (empreintes, noms de fichiers, clés, adresses, entrées cron) et surveillez de près pendant au moins 30 jours.
Liste de contrôle rapide
- Faites un instantané des disques cloud ; montez une boîte à outils de confiance en lecture seule ; démarrez un shell enregistré en UTC avec l’historique désactivé.
- Vérifiez /etc/ld.so.preload et LD_PRELOAD avant de vous fier à un binaire dynamique.
- Notez l’identité de l’hôte, la durée de fonctionnement, les montages, les interfaces, les routes et les utilisateurs connectés.
- Capturez la mémoire avec AVML ou LiME et calculez son empreinte.
- Notez les processus depuis ps et /proc ; récupérez les binaires supprimés depuis /proc/PID/exe.
- Notez les sockets, les règles de pare-feu et l’ARP ; comparez ss à /proc/net et à unhide-tcp.
- Examinez les comptes, sudoers, les groupes à privilèges et chaque fichier authorized_keys.
- Récupérez les journaux d’authentification, wtmp, btmp, le journal systemd, le journal d’audit et les historiques de shell.
- Recherchez la persistance : cron, systemd, profils de shell, éditeur de liens, PAM, modules du noyau, eBPF, MOTD, udev, hooks de paquets, web shells, setuid.
- Vérifiez les paquets, trouvez les fichiers modifiés dans la fenêtre par mtime et ctime, analysez avec YARA.
- Lancez UAC ou Velociraptor pour une collecte complète et hachée.
- Pour les conteneurs : inspect, diff, logs, pause et export avant que l’orchestrateur ne les remplace.
- Isolez au niveau du fournisseur, figez les processus, changez chaque secret accessible, reconstruisez, balayez et surveillez.
Conclusion
Sur un hôte Linux compromis, commencez par vérifier si vous pouvez vous fier aux outils du système. Capturez ensuite la mémoire et l’état en direct, examinez les comptes, les journaux et la persistance, et collectez les preuves avant de contenir ou de reconstruire. Gardez le même ordre sur les serveurs et les conteneurs, et entraînez-vous sur un système de test pour que les commandes vous soient familières le jour d’un incident.
- Linux
- DFIR
- Réponse à incident
- Triage
- Forensique
- UAC
- Volatility
- Rootkits
- Conteneurs
- Chasse aux menaces