Ir al contenido

EjecuciónActividad del usuarioAntiforense

Historial de la shell: comandos de bash, zsh y fish

Historial de comandos por usuario de bash, zsh y fish en Linux: qué registra cada archivo, cuándo hay marcas de tiempo y cómo se delata la evasión.

Ubicación
~/.bash_history
Prueba
Qué comandos se teclearon en una shell interactiva que se ejecutaba con una cuenta determinada, y a veces cuándo
Marcas de tiempo
Segundos de época Unix (UTC) cuando se registran: bash solo con HISTTIMEFORMAT, zsh con EXTENDED_HISTORY, fish siempre
Acceso
Cualquier usuario para sus propios archivos; root para los directorios personales de otros usuarios
Retención
Hasta que HISTFILESIZE/SAVEHIST lo recortan o se borra
Adquisición
UAC, Velociraptor, cp -a, tar

Qué es

Las shells interactivas mantienen una lista de las líneas de comando que teclea el usuario y la guardan en un archivo por usuario para poder recuperarla en sesiones posteriores. Bash, zsh y fish usan cada una su propio archivo y formato. Los archivos son texto plano (fish usa una estructura parecida a YAML), pertenecen al usuario, que puede escribir en ellos, y se escriben según el calendario de la propia shell, no a medida que se ejecuta cada comando.

Para el investigador suele ser el registro más directo de la actividad manual tras el compromiso de una cuenta. También es incompleto por diseño: lo que la shell nunca volcó a disco, o lo que el usuario decidió suprimir, simplemente no está.

Dónde se encuentra

ShellArchivo por defectoNotas
bash~/.bash_historyRuta definida por HISTFILE; si HISTFILE no está definida o es nula, no se guarda nada al salir
bash (root)/root/.bash_historyDonde acaban los comandos tras sudo -i, sudo su - o su -
zshSin valor por defecto; habitualmente ~/.zsh_history o ~/.zhistorySolo se escribe si HISTFILE está definida (y SAVEHIST no es cero) en un archivo de arranque
fish~/.local/share/fish/fish_history$XDG_DATA_HOME/fish/ si está definida; la variable fish_history cambia el nombre de la sesión a <name>_history, y un valor vacío desactiva el guardado

Las rutas son las mismas en Debian/Ubuntu, RHEL/Fedora y otras familias. Lo que cambia es la configuración por defecto: por ejemplo, el ~/.bashrc de esqueleto de Debian y Ubuntu fija HISTCONTROL=ignoreboth y activa histappend, mientras que otras distribuciones pueden definir las variables del historial en /etc/profile, /etc/bashrc o /etc/profile.d/. Lea siempre los archivos de arranque reales de la imagen (ver archivos de arranque de la shell) antes de interpretar huecos.

Las cuentas de servicio también tienen directorio personal: revise /var/www, /var/lib/postgresql, /var/lib/mysql, /opt/* y cualquier directorio personal que figure en /etc/passwd en busca de archivos de historial sueltos.

Qué prueba

  • Qué líneas de comando se introdujeron en una shell interactiva que se ejecutaba con esa cuenta.
  • El orden relativo en que una shell escribió esas líneas (no siempre el orden de ejecución entre sesiones paralelas).
  • Con marcas de tiempo: cuándo se introdujo cada comando (zsh y fish permiten además acotar la actividad de la sesión).
  • Uso de herramientas, rutas de archivos, hosts remotos, URL y credenciales en argumentos que apuntan a otros artefactos.

No prueba:

  • Quién estaba al teclado (las cuentas compartidas, robadas o cambiadas con su rompen la atribución).
  • Que un comando tuviera éxito, ni siquiera que llegara a ejecutarse si se tecleó y se abortó.
  • Nada sobre la ejecución no interactiva: scripts, tareas cron, web shells y ssh host 'cmd' normalmente no dejan nada aquí.

Campos clave

FormatoEstructura del registroSignificado
bash, planocommand lineUna entrada por línea, sin hora
bash, con HISTTIMEFORMAT definida#1727512345 y después command lineEl carácter de comentario seguido de dígitos marca la hora de la entrada siguiente; también delimita las entradas multilínea
zsh EXTENDED_HISTORY: <start>:<elapsed>;commandHora de inicio en segundos de época, segundos transcurridos y después el comando
fish- cmd: ... / when: ... / lista opcional paths:when son segundos de época; paths enumera los argumentos que fish reconoció como rutas existentes

Zsh guarda los bytes no ASCII en una codificación "metafied", por lo que algunos caracteres parecen corruptos en un visor de texto plano.

Marcas de tiempo

Los tres formatos usan segundos de época Unix, que están en UTC. El mtime del propio archivo indica la última vez que una shell escribió en él, y el ctime el último cambio de metadatos (útil cuando un archivo se truncó o se sustituyó por un enlace simbólico).

# bash with HISTTIMEFORMAT
grep -E '^#[0-9]{9,}$' .bash_history | head -1 | cut -c2- | xargs -I{} date -u -d @{}   # first timed entry
# zsh extended history
awk -F'[:;]' '/^: [0-9]+:/ {cmd="date -u -d @" ($2+0) " +%FT%TZ"; cmd | getline t; close(cmd); print t, $0}' .zsh_history

Retención

  • bash: al salir, las últimas HISTSIZE entradas (500 por defecto) se añaden al archivo (con histappend) o lo sobrescriben, y después el archivo se recorta a HISTFILESIZE líneas. La actividad antigua va desapareciendo por arriba.
  • zsh: SAVEHIST limita el archivo. APPEND_HISTORY está activada por defecto; INC_APPEND_HISTORY o SHARE_HISTORY escriben cada comando de inmediato en lugar de al salir.
  • fish: history clear e history delete eliminan entradas; history merge incorpora los cambios de otras sesiones.
  • Las sesiones terminadas con SIGKILL, las que fallaron o las que seguían abiertas en el momento de la adquisición pueden no llegar nunca al disco. Una bash en ejecución las conserva en la memoria del proceso.

Adquisición

Recoja de todos los directorios personales y de /root, incluidas las copias rotadas o de respaldo (.bash_history~, .bash_history.*). Los artefactos files/shell/* de UAC (parte de ir_triage) cubren bash, zsh, fish y otras shells, y además analizan los archivos de arranque para encontrar un HISTFILE no estándar.

sudo ./uac -p ir_triage /mnt/usb/case42
# dead box, read-only mount
find /mnt/evidence/root /mnt/evidence/home -maxdepth 4 \
  \( -name '.*history*' -o -path '*/fish/fish_history' \) -print0 | tar --null -T - -czf history.tgz

En Velociraptor, Linux.Sys.BashHistory busca con grep en /{root,home/*}/.*_history en todos los endpoints. Si la shell sospechosa puede seguir en ejecución, capture primero la memoria (LiME o AVML): adquirir el disco no vuelca el historial en memoria, pero un cierre de sesión posterior sobrescribirá lo que vio.

Análisis

  • Plaso: los plugins de texto bash_history y zsh_extended_history forman parte del preset linux (selecciónelos como text/bash_history y text/zsh_extended_history); fish_history es un parser aparte. Solo las entradas con marca de tiempo se convierten en eventos.
  • Volatility 3: linux.bash.Bash recupera el historial de los procesos bash en ejecución, incluidos comandos que nunca se escribieron en disco.
  • Para la revisión bastan las herramientas básicas:
log2timeline.py --parsers 'text/bash_history,text/zsh_extended_history,fish_history' \
  --storage-file hist.plaso /mnt/evidence/home
psort.py -o dynamic --output_time_zone UTC -w hist.csv hist.plaso

Consejos para la investigación

  • Trate como una pista un historial vacío, de cero bytes o enlazado a /dev/null en una cuenta activa. Revise el ctime del enlace simbólico y compare el mtime del archivo con los inicios de sesión de wtmp y lastlog.
  • Busque cadenas de evasión en los historiales de otras shells, en los archivos de arranque y en memoria: unset HISTFILE, HISTFILE=/dev/null, HISTSIZE=0, set +o history, history -c, history -d, kill -9 $$.
  • Es normal que falten los comandos tecleados con un espacio inicial cuando están definidos ignorespace/ignoreboth o HIST_IGNORE_SPACE de zsh, y fish descarta esas líneas por diseño. Es un comportamiento por defecto, no una prueba de intención.
  • Una cobertura parcial de #epoch en un archivo de bash significa que HISTTIMEFORMAT se activó en algún momento; las líneas sin hora son anteriores o proceden de una shell sin esa variable.
  • Tras sudo -i o su -, continúe en /root/.bash_history y correlacione con los logs de sudo.
  • Cuando auditd registra EXECVE, aporta registros de ejecución independientes y con marca de tiempo para confirmar las entradas del historial (ver auditd).
  • Los editores, paginadores y REPL mantienen sus propios historiales, que los atacantes rara vez limpian: ver viminfo y lesshst.

Ver también