Historique du shell sous macOS : zsh, bash et Terminal
L'historique du shell macOS dans .zsh_history, .bash_history et les fichiers de session du Terminal : quelles commandes ont été tapées et vers quand.
- Emplacement
- ~/.zsh_history
- Prouve
- Quelles commandes un utilisateur a tapées dans un shell interactif, dans quel ordre, et dans quelle fenêtre du Terminal
- Horodatages
- Aucun par défaut ; secondes d'époque Unix uniquement si EXTENDED_HISTORY (zsh) ou HISTTIMEFORMAT (bash) est défini
- Accès
- Propriétaire du fichier ou root ; fichiers texte dans le dossier personnel
- Rétention
- zsh : SAVEHIST=1000 lignes par défaut ; fichiers de session du Terminal supprimés après environ deux semaines
- Collecte
- UAC, Aftermath, mac_apt
Outils
Comparer tous les outils- mac_aptCLI · open source
- grepCLI · intégré à macOS
- sedCLI · intégré à macOS
Ce que c'est
Les shells interactifs écrivent les commandes tapées par l'utilisateur dans un fichier d'historique en texte brut, dans le dossier personnel. Apple a fait de zsh le shell de connexion et interactif par défaut des nouveaux comptes à partir de macOS Catalina : ~/.zsh_history est donc le fichier principal sur les systèmes actuels. Les comptes créés sous Mojave ou avant puis mis à niveau peuvent encore utiliser bash (~/.bash_history), et les utilisateurs peuvent changer de shell à tout moment avec chsh.
Le Terminal d'Apple ajoute une seconde couche. Les scripts de démarrage fournis par Apple, /etc/zshrc_Apple_Terminal et /etc/bashrc_Apple_Terminal, implémentent la fonction « Reprise » : chaque fenêtre ou onglet du Terminal reçoit un TERM_SESSION_ID, et ses commandes sont aussi enregistrées dans un fichier d'historique par session dans ~/.zsh_sessions ou ~/.bash_sessions. Cela permet d'attribuer des commandes à une fenêtre précise du Terminal, et parfois de récupérer des commandes élaguées du fichier principal.
Où il se trouve
| Fichier | Chemin | Remarques |
|---|---|---|
| Historique zsh | ~/.zsh_history | Défini par HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history dans /etc/zshrc ; zsh par défaut depuis 10.15 Catalina pour les nouveaux comptes |
| Sessions Terminal zsh | ~/.zsh_sessions/<TERM_SESSION_ID>.history, .historynew, .session | Créés par /etc/zshrc_Apple_Terminal (dossier en mode 700) |
| Historique bash | ~/.bash_history | Par défaut pour les comptes créés sous 10.14 et avant ; macOS fournit bash 3.2 |
| Sessions Terminal bash | ~/.bash_sessions/<TERM_SESSION_ID>.history, .historynew, .session | Créés par /etc/bashrc_Apple_Terminal |
| Historique de root | /private/var/root/.zsh_history (ou .bash_history) | Commandes tapées dans un shell de connexion root, comme sudo -i |
| Fichiers de démarrage | /etc/zshrc, /etc/zprofile, ~/.zshrc, ~/.zprofile, ~/.zshenv, ~/.zlogin | Cherchez un HISTFILE, HISTSIZE ou SAVEHIST modifié, ou un historique désactivé |
Si ZDOTDIR est défini, zsh cherche ses fichiers dans ce répertoire au lieu de $HOME. Les autres shells (fish, ksh, tcsh) ont leurs propres fichiers d'historique. Les dossiers personnels se trouvent sur le volume Data (/System/Volumes/Data/Users/<name>). Les dotfiles eux-mêmes ne sont pas dans un emplacement protégé par TCC, mais l'outil de collecte devrait tout de même disposer de l'accès complet au disque.
Ce qu'il prouve
- Les commandes tapées dans des shells interactifs, dans l'ordre de leur enregistrement.
- Des schémas de téléchargement et exécution (
curl ... | sh,chmod +x,xattr -d com.apple.quarantine), la mise en place d'une persistance (launchctl load, modification de LaunchAgents), de la reconnaissance, la création d'archives et des commandes d'exfiltration. - Avec les fichiers de session du Terminal : quelles commandes appartiennent à quelle fenêtre, et quand une session a été enregistrée pour la dernière fois.
Il ne prouve pas :
- Qu'une commande a réussi, ni ce qu'elle a affiché.
- Quand une commande s'est exécutée, sauf si les horodatages étendus sont activés.
- Quoi que ce soit sur l'exécution non interactive : les scripts,
osascript, les tâches launchd et les outils distants qui lancentsh -cn'écrivent pas d'historique. Utilisez pour cela les Unified Logs et la télémétrie des processus.
Champs clés
Format zsh par défaut : une commande par ligne, sans horodatage.
zsh avec setopt EXTENDED_HISTORY :
: 1790000000:4;curl -fsSL https://example.test/a.sh -o /tmp/a.sh
| Partie | Signification |
|---|---|
: | Marqueur (zsh écrit une commande : sans effet) |
1790000000 | Heure de début de la commande, secondes d'époque Unix |
4 | Durée écoulée en secondes |
après ; | La ligne de commande |
bash avec HISTTIMEFORMAT défini écrit une ligne de commentaire #<epoch> avant chaque commande. Les fichiers .session du Terminal contiennent une ligne qui affiche Restored session: suivi de date -r <epoch>, où l'époque correspond au moment où l'état de la session a été enregistré.
Horodatages
- Configuration par défaut : aucune heure par commande. Seules les dates du fichier d'historique dans le système de fichiers aident : sa date de modification correspond approximativement à la dernière fois qu'un shell s'est terminé et a enregistré l'historique (voir horodatages APFS).
EXTENDED_HISTORYde zsh etHISTTIMEFORMATde bash stockent des secondes d'époque Unix, en UTC. Aucun des deux n'est activé dans les/etc/zshrcou/etc/bashrcpar défaut d'Apple.- Fichiers de session du Terminal : l'époque contenue dans
.session, plus les dates de modification de chaque fichier.history, donnent une heure de « dernier enregistrement » par fenêtre.
# Convert zsh extended history to readable UTC
sed -nE 's/^: ([0-9]+):([0-9]+);(.*)$/\1 \2 \3/p' .zsh_history |
while read -r ts dur cmd; do printf '%s %ss %s\n' "$(date -u -r "$ts" '+%F %T')" "$dur" "$cmd"; done
Rétention
- Le
/etc/zshrcd'Apple définitHISTSIZE=2000(en mémoire) etSAVEHIST=1000(lignes conservées dans le fichier). Les lignes les plus anciennes sont élaguées à mesure que de nouvelles sont enregistrées. - zsh ajoute l'historique au fichier à la fermeture du shell (
APPEND_HISTORYest actif par défaut). Une fenêtre tuée brutalement, ou un plantage, peut faire perdre les commandes de cette session. Des options commeINC_APPEND_HISTORYouSHARE_HISTORYécrivent chaque commande immédiatement. - La logique de session du Terminal supprime les fichiers de
~/.zsh_sessions/~/.bash_sessionsde plus de deux semaines, en vérifiant au plus une fois par jour (_expiration_check_timestamp). - Les utilisateurs peuvent désactiver ou effacer l'historique :
unset HISTFILE,HISTSIZE=0,SHELL_SESSIONS_DISABLE=1(désactive seulement les fichiers de session de Terminal, pas l'historique), une espace en début de ligne avecHIST_IGNORE_SPACE,history -csous bash, ou simplement la suppression du fichier.
Collecte
# Per user, preserving timestamps (run with Full Disk Access)
for h in /Users/*; do
ditto "$h/.zsh_history" "/cases/host01/$(basename "$h")/zsh_history" 2>/dev/null
ditto "$h/.zsh_sessions" "/cases/host01/$(basename "$h")/zsh_sessions" 2>/dev/null
ditto "$h/.bash_history" "/cases/host01/$(basename "$h")/bash_history" 2>/dev/null
ditto "$h/.bash_sessions" "/cases/host01/$(basename "$h")/bash_sessions" 2>/dev/null
done
sudo ditto /private/var/root /cases/host01/root_home
- UAC :
zsh.yamletbash.yamlcollectent les fichiers d'historique, les dossiers de session, les fichiers de démarrage et toutHISTFILEpersonnalisé qui y figure. - Aftermath : collecte l'historique de bash et de zsh,
.sh_historyet les fichiers de démarrage du shell, dontconfig.fishpour fish ; aucun historique csh, ksh ou fish (root plus accès complet au disque). - mac_apt : le plugin
TERMSESSIONSlit les dossiers de session et l'historique bash et zsh de chaque utilisateur.
Si possible, collectez en live avant que l'utilisateur ne se déconnecte : les shells ouverts ne conservent les commandes non enregistrées qu'en mémoire.
Analyse
Les fichiers d'historique sont du texte : cat, grep et sed font l'essentiel du travail. Points d'attention :
- zsh écrit le fichier dans son encodage interne « metafied » : les octets de la plage meta sont stockés sous la forme
0x83suivi de l'octet XOR0x20. Les commandes contenant des caractères accentués ou non latins semblent corrompues tant que l'on n'inverse pas cette transformation (supprimer0x83, appliquer un XOR0x20à l'octet suivant). - Le plugin
TERMSESSIONSde mac_apt fusionne les fichiers de session.historyet.historynewparTERM_SESSION_ID. - Tri rapide des commandes d'attaquant courantes :
grep -nE 'curl|wget|base64|osascript|xattr|launchctl|chmod \+x|nc |python3? -c|/tmp/|/Users/Shared/' .zsh_history
Conseils d'investigation
- Comparez
.zsh_historyavec chaque~/.zsh_sessions/*.history: les fichiers de session sont élagués indépendamment et peuvent contenir des commandes absentes du fichier principal. - Un fichier d'historique vide ou de taille nulle avec une date de modification récente, ou un lien symbolique vers
/dev/null, est un signe classique d'anti-forensique. Cherchezunset HISTFILEouSAVEHIST=0dans les fichiers de démarrage. - Les shells root écrivent dans
/private/var/root; associez-les aux journaux sudo pour savoir qui a élevé ses privilèges. - Les commandes qui touchent LaunchAgents ou LaunchDaemons doivent être recoupées avec les plists launchd et FSEvents pour retrouver la création du fichier correspondant.
- Les snapshots APFS locaux peuvent contenir des versions plus anciennes et plus longues du fichier d'historique.
/etc/zshrcne charge la logique de session que lorsqueTERM_PROGRAMvautApple_Terminal: iTerm2, les terminaux de VS Code et les émulateurs similaires n'écrivent que dans le fichier d'historique principal, jamais dans~/.zsh_sessions.