Saltar al contenido

EjecuciónActividad del usuario

Historial de shell en macOS: zsh, bash y sesiones Terminal

Historial de shell de macOS en .zsh_history, .bash_history y las sesiones de Terminal por ventana: qué comandos tecleó un usuario y cuándo, aproximadamente.

Ubicación
~/.zsh_history
Prueba
Qué comandos escribió un usuario en una shell interactiva, en qué orden y en qué ventana de Terminal
Marcas de tiempo
Ninguna por defecto; segundos de época Unix solo si EXTENDED_HISTORY (zsh) o HISTTIMEFORMAT (bash) está activo
Acceso
Propietario del archivo o root; archivos normales en la carpeta personal
Retención
zsh por defecto SAVEHIST=1000 líneas; los archivos de sesión de Terminal se borran tras unas dos semanas
Adquisición
UAC, Aftermath, mac_apt

Qué es

Las shells interactivas escriben los comandos que teclea un usuario en un archivo de historial de texto plano dentro de su carpeta personal. Apple convirtió zsh en la shell de inicio de sesión e interactiva por defecto para las cuentas nuevas a partir de macOS Catalina, de modo que ~/.zsh_history es el archivo principal en los sistemas actuales. Las cuentas creadas en Mojave o antes y actualizadas después pueden seguir usando bash (~/.bash_history), y los usuarios pueden cambiar de shell con chsh en cualquier momento.

El Terminal de Apple añade una segunda capa. Los scripts de inicio que proporciona Apple, /etc/zshrc_Apple_Terminal y /etc/bashrc_Apple_Terminal, implementan la función "Reanudar": cada ventana o pestaña de Terminal recibe un TERM_SESSION_ID, y sus comandos se guardan también en un archivo de historial por sesión en ~/.zsh_sessions o ~/.bash_sessions. Esto permite atribuir comandos a una ventana de Terminal concreta y, a veces, recuperar comandos recortados del archivo principal.

Dónde se encuentra

ArchivoRutaNotas
Historial de zsh~/.zsh_historyDefinido por HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history en /etc/zshrc; zsh por defecto desde 10.15 Catalina para cuentas nuevas
Sesiones de Terminal con zsh~/.zsh_sessions/<TERM_SESSION_ID>.history, .historynew, .sessionCreados por /etc/zshrc_Apple_Terminal (carpeta con modo 700)
Historial de bash~/.bash_historyPor defecto en cuentas de 10.14 y anteriores; macOS incluye bash 3.2
Sesiones de Terminal con bash~/.bash_sessions/<TERM_SESSION_ID>.history, .historynew, .sessionCreados por /etc/bashrc_Apple_Terminal
Historial de root/private/var/root/.zsh_history (o .bash_history)Comandos tecleados en una shell de root, como sudo -i
Archivos de inicio/etc/zshrc, /etc/zprofile, ~/.zshrc, ~/.zprofile, ~/.zshenv, ~/.zloginBusca un HISTFILE, HISTSIZE o SAVEHIST modificado o un historial desactivado

Si ZDOTDIR está definido, zsh busca sus archivos en ese directorio en lugar de $HOME. Otras shells (fish, ksh, tcsh) mantienen sus propios archivos de historial. Las carpetas personales están en el volumen Data (/System/Volumes/Data/Users/<name>). Los dotfiles no están en una ubicación protegida por TCC, pero la herramienta de adquisición debería ejecutarse igualmente con Full Disk Access.

Qué prueba

  • Los comandos tecleados en shells interactivas, en el orden en que se guardaron.
  • Patrones de descarga y ejecución (curl ... | sh, chmod +x, xattr -d com.apple.quarantine), configuración de persistencia (launchctl load, edición de LaunchAgents), reconocimiento, creación de archivos comprimidos y comandos de exfiltración.
  • Con los archivos de sesión de Terminal: qué comandos pertenecen a qué ventana de Terminal y cuándo se guardó una sesión por última vez.

No prueba:

  • Que un comando tuviera éxito, ni lo que mostró.
  • Cuándo se ejecutó un comando, salvo que estén activadas las marcas de tiempo extendidas.
  • Nada sobre la ejecución no interactiva: los scripts, osascript, los trabajos de launchd y las herramientas remotas que lanzan sh -c no escriben historial. Para eso, usa los Unified Logs y la telemetría de procesos.

Campos clave

Formato por defecto de zsh: un comando por línea, sin marcas de tiempo.

zsh con setopt EXTENDED_HISTORY:

: 1790000000:4;curl -fsSL https://example.test/a.sh -o /tmp/a.sh
ParteSignificado
: Marcador (zsh escribe un comando : que no hace nada)
1790000000Hora de inicio del comando, segundos de época Unix
4Tiempo transcurrido en segundos
tras ;La línea de comandos

bash con HISTTIMEFORMAT definido escribe una línea de comentario #<epoch> antes de cada comando. Los archivos .session de Terminal contienen una línea que muestra Restored session: seguido de date -r <epoch>, donde la época es la hora a la que se guardó el estado de la sesión.

Marcas de tiempo

  • Configuración por defecto: no hay ninguna hora por comando. Solo ayudan las horas del sistema de archivos del archivo de historial: su hora de modificación indica aproximadamente cuándo una shell salió y guardó el historial por última vez (consulta marcas de tiempo APFS).
  • EXTENDED_HISTORY de zsh y HISTTIMEFORMAT de bash guardan segundos de época Unix, UTC. Ninguno está activado en los /etc/zshrc o /etc/bashrc por defecto de Apple.
  • Archivos de sesión de Terminal: la época del .session, más las horas de modificación de cada archivo .history, dan una hora de "último guardado" por ventana.
# 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

Retención

  • El /etc/zshrc de Apple define HISTSIZE=2000 (en memoria) y SAVEHIST=1000 (líneas que se conservan en el archivo). Las líneas más antiguas se recortan a medida que se guardan otras nuevas.
  • zsh añade el historial al salir la shell (APPEND_HISTORY está activo por defecto). Una ventana cerrada a la fuerza, o un fallo, puede perder los comandos de esa sesión. Opciones como INC_APPEND_HISTORY o SHARE_HISTORY escriben cada comando inmediatamente.
  • La lógica de sesiones de Terminal borra los archivos de ~/.zsh_sessions / ~/.bash_sessions con más de dos semanas, comprobándolo como máximo una vez al día (_expiration_check_timestamp).
  • Los usuarios pueden desactivar o borrar el historial: unset HISTFILE, HISTSIZE=0, SHELL_SESSIONS_DISABLE=1 (solo desactiva los archivos de sesión de Terminal, no el historial), un espacio inicial con HIST_IGNORE_SPACE, history -c en bash o, simplemente, borrar el archivo.

Adquisición

# 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.yaml y bash.yaml recopilan los archivos de historial, las carpetas de sesión, los archivos de inicio y cualquier HISTFILE personalizado que aparezca en ellos.
  • Aftermath: recopila el historial de bash y zsh, .sh_history y los archivos de inicio del shell, incluido config.fish de fish; no recoge historial de csh, ksh ni fish (root más Full Disk Access).
  • mac_apt: el plugin TERMSESSIONS lee las carpetas de sesión y el historial de bash y zsh de todos los usuarios.

Si es posible, adquiere en vivo antes de que el usuario cierre sesión: las shells abiertas guardan en memoria los comandos aún no escritos.

Análisis

Los archivos de historial son texto, así que cat, grep y sed hacen la mayor parte del trabajo. Aspectos a vigilar:

  • zsh escribe el archivo en su codificación interna "metafied": los bytes del rango meta se guardan como 0x83 seguido del byte con XOR 0x20. Los comandos con caracteres acentuados o no latinos aparecen corruptos hasta que se revierte (elimina 0x83 y aplica XOR 0x20 al byte siguiente).
  • El plugin TERMSESSIONS de mac_apt fusiona los archivos .history y .historynew de cada sesión por TERM_SESSION_ID.
  • Triaje rápido de comandos habituales de atacantes:
grep -nE 'curl|wget|base64|osascript|xattr|launchctl|chmod \+x|nc |python3? -c|/tmp/|/Users/Shared/' .zsh_history

Consejos para la investigación

  • Compara .zsh_history con cada ~/.zsh_sessions/*.history: los archivos de sesión se recortan por separado y pueden contener comandos que faltan en el archivo principal.
  • Un archivo de historial vacío o de cero bytes con una hora de modificación reciente, o un enlace simbólico a /dev/null, es una señal antiforense clásica. Busca unset HISTFILE o SAVEHIST=0 en los archivos de inicio.
  • Las shells de root escriben en /private/var/root; combínalas con los logs de sudo para ver quién elevó privilegios.
  • Los comandos que tocan LaunchAgents o LaunchDaemons deben contrastarse con los plists de launchd y con FSEvents para localizar la creación del archivo correspondiente.
  • Los snapshots locales de APFS pueden contener versiones más antiguas y más largas del archivo de historial.
  • /etc/zshrc solo carga la lógica de sesiones cuando TERM_PROGRAM vale Apple_Terminal, así que iTerm2, los terminales de VS Code y emuladores similares solo escriben en el archivo de historial principal, nunca en ~/.zsh_sessions.

Ver también