Journal de systemd: archivos, huecos de seqnum y .journal~
Análisis forense del journal de systemd sin journalctl: formato, campos de confianza, compresión, huecos de seqnum, archivos .journal~ y campos de hora.
En resumen. Un archivo de journal es una base de datos binaria de solo anexado con la firma LPKSHHRH. Cada entrada lleva un número de secuencia que journald incrementa en todos los archivos de una máquina, así que un rango de números que falta es visible incluso después de que las entradas hayan desaparecido. Lee los propios archivos, no solo una exportación de journalctl: las cabeceras, los números de secuencia, los archivos .journal~ y los journals de usuario son donde se ven los borrados.
Dónde están los archivos
| Ruta | Contenido |
|---|---|
/var/log/journal/<machine-id>/system.journal | Journal del sistema activo (almacenamiento persistente) |
/var/log/journal/<machine-id>/system@<id>-<seqnum>-<time>.journal | Journals del sistema archivados (rotados) |
/var/log/journal/<machine-id>/user-<UID>.journal | Journal por usuario para los UID normales (por defecto SplitMode=uid) |
*.journal~ | Archivos renombrados tras una parada incorrecta de journald o una corrupción detectada |
/run/log/journal/<machine-id>/ | Journal volátil, se pierde al reiniciar |
<machine-id> coincide con /etc/machine-id. Que se escriba algo en /var depende de Storage= en journald.conf: lee la configuración de la imagen en lugar de suponerla.
Por qué merece la pena el journal
El journal de systemd guarda cada entrada como pares FIELD=value. Los campos que empiezan por guion bajo los añade journald a partir de lo que el kernel sabe del remitente, por lo que el proceso que envía el mensaje no puede falsificarlos:
_PID,_UID,_GID,_COMM,_EXE,_CMDLINE: quién envió realmente el mensaje._SYSTEMD_UNIT,_SYSTEMD_USER_UNIT: de qué servicio o servicio de usuario procede._BOOT_ID: en qué arranque; los reinicios se convierten en límites explícitos._AUDIT_SESSION,_AUDIT_LOGINUID: la sesión de auditoría y el UID de inicio de sesión, que vinculan la entrada con elsesy el auid de auditd._TRANSPORT:syslog,journal,stdout,kernel,audit,driver.
MESSAGE, SYSLOG_IDENTIFIER y PRIORITY los proporciona el cliente. Cualquiera puede escribir sshd: Accepted password ... con logger; los campos con guion bajo mostrarán que procede de logger con el UID de ese usuario.
El formato de archivo en resumen
- Cabecera. Firma
LPKSHHRH, indicadores de funciones compatibles e incompatibles,state(offline, online, archived),machine_id,seqnum_id,head_entry_seqnumytail_entry_seqnum, primera y última marca de tiempo realtime, ID del último arranque y recuentos de objetos. El systemd actual escribe una cabecera de 272 bytes; los lectores deben usar el tamaño de cabecera guardado en el archivo, ya que los archivos antiguos tienen cabeceras más cortas. - Objetos. Tras la cabecera vienen objetos con tipo:
DATA(un contenido campo=valor, deduplicado por hash),FIELD,ENTRY(la lista de objetos DATA que forman una entrada, con seqnum, realtime, monotonic e ID de arranque),ENTRY_ARRAY, tablas hash y, con Forward Secure Sealing,TAG. - Compresión. Los contenidos
DATAgrandes pueden comprimirse con XZ, LZ4 o ZSTD, indicado en cada objeto. La variante LZ4 guarda el tamaño sin comprimir como un entero de 64 bits little-endian antes del bloque LZ4. - Modo compacto. Desde systemd 252, los archivos pueden usar la estructura compacta, con desplazamientos de 32 bits en lugar de 64. Las versiones antiguas de journalctl rechazan esos archivos, lo que es un motivo para usar un lector reciente.
Un analizador que recorre los objetos directamente puede leer entradas que journalctl omitiría, por ejemplo en un archivo cuya cabecera no se actualizó tras un fallo.
Números de secuencia: la prueba de manipulación integrada
Cada entrada tiene un seqnum. journald incrementa un único contador por máquina (identificado por seqnum_id) y lo usa en todos los archivos que escribe, tanto del sistema como de usuario. Ordena por seqnum todas las entradas de todos los archivos con un mismo seqnum_id, y los números deberían ser continuos. Un hueco en el seqnum del journal significa que entradas que existieron no están en los archivos que tienes.
Primero, las causas inocentes:
- Journals de usuario no recopilados. Las entradas escritas en
user-1001.journaltambién consumen números. Si solo se recopilósystem.journal, cada entrada de usuario aparece como un hueco. - Archivos eliminados por vacuum.
journalctl --vacuum-*y la rotación por tamaño borran los archivos archivados antiguos; en ese caso el hueco está al principio del rango conservado. - Límites de rotación entre un archivo archivado que tienes y otro que no.
El caso interesante es un hueco dentro de la ventana del incidente, en un conjunto donde están todos los journals de usuario, o un hueco que coincide con un journal de usuario borrado a mano. En el ejemplo de la herramienta, el atacante elimina user-1001.journal: el journal del sistema conserva sus números, pero faltan las entradas que fueron al archivo de usuario, y las líneas del gestor de usuario que contenían solo sobreviven en /var/log/syslog (rsyslog también las recibió).
Archivos sucios y en línea
- Un archivo
.journal~se renombró porque journald no lo cerró correctamente o lo encontró corrupto. A menudo contiene los últimos minutos antes de un fallo o de un apagado brusco. Analízalo; el final puede estar escrito a medias. - Un
stateonline en la cabecera de un archivo recopilado significa que journald lo tenía abierto. Es normal en unsystem.journalcopiado de un host en vivo, pero las últimas entradas pueden estar incompletas. - Un archivo cuya cabecera dice contener más entradas de las que se pueden leer, o cuyo final está dañado, merece una nota en el informe; no lo descartes.
Campos de hora
| Campo | Significado |
|---|---|
__REALTIME_TIMESTAMP | Cuándo recibió journald la entrada, en microsegundos desde la época Unix (UTC) |
_SOURCE_REALTIME_TIMESTAMP | La hora de confianza más temprana del mensaje en su origen, cuando difiere de la recepción; journalctl muestra esta si existe |
__MONOTONIC_TIMESTAMP | Microsegundos desde el arranque, con sentido junto a _BOOT_ID |
Los valores realtime siguen el reloj del sistema. Un reloj atrasado a mano se ve como un realtime que baja mientras el seqnum sube; dentro de un mismo arranque, el reloj monotónico da el orden real.
Leer exportaciones
Si no dispones de los archivos en bruto, journalctl -o export no pierde información por entrada (los valores binarios llevan un prefijo de longitud) y -o json es cómodo. Ambos pierden las cabeceras de los archivos y se limitan a lo que journalctl pudo leer. Consulta la guía de recopilación.
journalctl -D /mnt/evidence/var/log/journal --header # rangos de archivos, estado, ids de seqnum
journalctl -D /mnt/evidence/var/log/journal --list-boots --utc
journalctl -D /mnt/evidence/var/log/journal -o export > journal.export
En el navegador
Linux Log Parser analiza archivos de journal normales y compactos, campos comprimidos con XZ/LZ4/ZSTD, archivos .journal~ y exportaciones, informa de los huecos en los números de secuencia (y avisa cuando no se cargó ningún journal de usuario), señala los archivos sucios y en línea, y marca las líneas de auth.log que también contiene el journal. Las líneas de autenticación presentes en el journal pero ausentes de auth.log se notifican como un hallazgo aparte, tratado en la guía sobre manipulación.
La ficha del journal de systemd en linuxforensics.app detalla los ajustes de retención y los comandos de journalctl.