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
Herramientas
Comparar todas las herramientasQué 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
| Shell | Archivo por defecto | Notas |
|---|---|---|
| bash | ~/.bash_history | Ruta definida por HISTFILE; si HISTFILE no está definida o es nula, no se guarda nada al salir |
| bash (root) | /root/.bash_history | Donde acaban los comandos tras sudo -i, sudo su - o su - |
| zsh | Sin valor por defecto; habitualmente ~/.zsh_history o ~/.zhistory | Solo 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
surompen 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
| Formato | Estructura del registro | Significado |
|---|---|---|
| bash, plano | command line | Una entrada por línea, sin hora |
bash, con HISTTIMEFORMAT definida | #1727512345 y después command line | El 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>;command | Hora 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
HISTSIZEentradas (500 por defecto) se añaden al archivo (conhistappend) o lo sobrescriben, y después el archivo se recorta aHISTFILESIZElíneas. La actividad antigua va desapareciendo por arriba. - zsh:
SAVEHISTlimita el archivo.APPEND_HISTORYestá activada por defecto;INC_APPEND_HISTORYoSHARE_HISTORYescriben cada comando de inmediato en lugar de al salir. - fish:
history clearehistory deleteeliminan entradas;history mergeincorpora 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_historyyzsh_extended_historyforman parte del presetlinux(selecciónelos comotext/bash_historyytext/zsh_extended_history);fish_historyes un parser aparte. Solo las entradas con marca de tiempo se convierten en eventos. - Volatility 3:
linux.bash.Bashrecupera 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/nullen una cuenta activa. Revise elctimedel enlace simbólico y compare elmtimedel 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/ignorebothoHIST_IGNORE_SPACEde 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
#epochen un archivo de bash significa queHISTTIMEFORMATse activó en algún momento; las líneas sin hora son anteriores o proceden de una shell sin esa variable. - Tras
sudo -iosu -, continúe en/root/.bash_historyy 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
Artefactos relacionados
Guías detalladas
Glosario
- Volatility 3Inglés
- Super TimelineInglés
- UAC (Unix-like Artifacts Collector)Inglés