Como auditar comandos executados no servidor
O historico da shell nao e auditoria. Compare logs do sudo, gravacao de sessoes, hooks do bash e regras auditd execve, enviando os registros para fora do servidor.
O que realmente regista os comandos executados pelos utilizadores no seu servidor
Para auditar os comandos executados pelos utilizadores no seu servidor, precisa de um registo que o utilizador não possa editar. O histórico da shell não é esse registo. É um ficheiro de conveniência, pertencente à conta que o escreveu, e qualquer pessoa que possa introduzir comandos nessa shell pode desativá-lo ou eliminá-lo.
Quatro camadas mantêm um registo real, e cada uma tem um custo. O sudo escreve uma linha por comando no syslog. O registo de E/S do sudo captura uma sessão completa de uma conta. Um hook da shell, como PROMPT_COMMAND, regista o que um utilizador interativo do bash introduziu. O subsistema de auditoria do kernel regista a própria chamada de sistema execve, razão pela qual é a única camada que vê todos os processos. Este guia sobe essa hierarquia, explica onde cada camada deixa de funcionar e termina com o fator que determina se tudo isto tem algum valor: retirar os registos da máquina antes de a pessoa que está a auditar conseguir aceder-lhes.
Um aviso antes de começar. O subsistema de auditoria é executado pelo kernel, por isso nada disto pode ser testado dentro de um contentor que partilhe o kernel do host. Execute estes comandos numa VPS KVM cujo kernel esteja sob o seu controlo.
Por que o histórico do shell não é uma trilha de auditoria
~/.bash_history falha como evidência por quatro motivos comuns, e nenhum deles exige um atacante habilidoso.
Pertence ao utilizador. O ficheiro tem o modo 600 e pertence a essa conta, portanto rm ~/.bash_history não requer qualquer privilégio. Abrir o ficheiro num editor e remover as vinte linhas relevantes também não requer privilégios.
É gravado quando o shell termina. Uma sessão que termina com kill -9 $$ ou com uma ligação interrompida não grava nada. history -c antes de exit produz o mesmo efeito e dá a impressão de que nada aconteceu.
Pode ser desativado com uma palavra. unset HISTFILE impede a gravação do ficheiro nessa sessão. set +o history interrompe imediatamente o registo. HISTCONTROL=ignorespace oculta todos os comandos introduzidos com um espaço inicial. Tudo isto está em man bash, porque foi concebido para estar sob o controlo do utilizador.
Regista o que foi introduzido, não o que foi executado. Um alias ou uma função do shell significa que o texto no ficheiro não corresponde ao programa que o kernel executou.
Também não há marcas temporais, a menos que HISTTIMEFORMAT estivesse definida no momento em que a entrada foi gravada, porque o bash escreve as linhas de marcação #1755043200 apenas quando essa variável está definida.
Numa conta de acesso partilhada, o histórico também não permite saber quem executou o comando. Três pessoas que utilizem a mesma conta deploy produzem um único ficheiro intercalado sob um único uid. Nenhuma camada de registo consegue atribuir uma ação a uma pessoa quando duas pessoas partilham um uid. Este é o argumento prático para uma conta sem privilégios por pessoa em vez de um acesso partilhado.
O histórico do shell é útil para a sua finalidade real: ajudar a introduzir novamente o comando de ontem. Use-o como indicação. Nunca o apresente como prova.
O que o sudo regista e onde o registo termina
O sudo envia uma linha para cada comando que executa para a facilidade de syslog authpriv.
sudo grep 'sudo:' /var/log/auth.log | tail -5
journalctl -t sudo -n 5Cada linha identifica o utilizador, o terminal, o diretório de trabalho, o utilizador de destino e o comando:
sudo: alice : TTY=pts/0 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/apt updateSe /var/log/auth.log não existir, o rsyslog não está instalado nessa imagem e os mesmos registos estão apenas no journal. Confirme que o journal não é volátil antes de depender dele:
journalctl --list-bootsA existência apenas do boot atual significa que /var/log/journal não existe. Nesse caso, o journal fica em /run e todas as linhas desaparecem no reboot seguinte. Torne-o persistente:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldAgora, o limite. O sudo regista o comando que lhe foi pedido para executar. Não regista o que esse comando faz depois. Por isso, uma linha interrompe o rastreamento:
sudo -iO log recebe um único registo para o shell. Todos os comandos introduzidos nesse root shell ficam invisíveis para o sudo, porque o sudo já não está no caminho de execução. sudo su -, sudo bash e sudo vim /etc/shadow seguidos de :!bash têm todos o mesmo formato. Uma regra do sudoers que permita qualquer programa com escape para um shell, como vim ou find, concede acesso root sem registo. Leia o que uma conta pode realmente executar antes de confiar nas respetivas linhas de log:
sudo -l -U aliceRegistar uma sessão completa de uma conta
Primeiro, descubra qual sudo está instalado, porque este recurso não existe na reescrita em Rust:
sudo --version | head -1Se a saída indicar sudo-rs, ignore esta seção e use o subsistema de auditoria. A documentação oficial do Ubuntu para as versões 25.10 e 26.04 indica o registro de E/S e sudoreplay como não suportados. Isso continuava válido em agosto de 2026. Esse detalhe é importante porque sudo-rs é o sudo padrão nessas versões. Portanto, uma atualização pode remover um controle que você considerava disponível. A lista completa das alterações de comportamento do sudo-rs deve ser consultada antes de planejar qualquer registro baseado em sudo.
Com o sudo original, que o Ubuntu 24.04 LTS ainda fornece, ative o registro de E/S para uma conta:
sudo visudo -f /etc/sudoers.d/iologDefaults:deploy log_output
Defaults!/usr/bin/sudoreplay !log_outputUse visudo em vez de um editor, porque o comando se recusa a salvar um arquivo com sintaxe inválida. Um arquivo sudoers inválido impede todos os utilizadores de usar sudo. Depois, reproduza uma sessão:
sudo sudoreplay -l user=deploy
sudo sudoreplay 000001sudoreplay -l lista as sessões com os respetivos IDs e não imprime nada se log_output nunca tiver sido aplicado a esse utilizador. O custo é o seguinte: cada byte que passa pelo terminal é armazenado em /var/log/sudo-io, portanto uma sessão detalhada pode ocupar muito espaço. A segunda linha do sudoers impede que uma reprodução seja registrada novamente. O custo mais importante são os segredos, porque um log de E/S contém tudo o que foi digitado e exibido, incluindo uma senha digitada em um prompt dentro da sessão. Por isso, ele precisa da mesma proteção que um armazenamento de senhas. A cobertura também é limitada. O recurso registra os comandos executados por meio de sudo. Alguém que faça login e trabalhe inteiramente como a própria conta não será registrado.
Hooks do shell e exatamente como são contornados
A receita que circula para "registar todos os comandos" é um hook PROMPT_COMMAND colocado em /etc/profile.d/:
# /etc/profile.d/00-cmdlog.sh
PROMPT_COMMAND='logger -p local6.info -t cmdlog "$(whoami) $$ $(history 1 | sed "s/^ *[0-9]* *//")"'O bash executa PROMPT_COMMAND antes de apresentar cada prompt. Assim, a linha chega ao syslog enquanto é introduzida, e não no encerramento da sessão. logger escreve através do daemon de logs do sistema, pelo que as permissões do ficheiro do próprio utilizador não interferem. Abra uma nova shell de login e verifique com sudo tail -f /var/log/syslog, ou com journalctl -t cmdlog -f numa imagem sem rsyslog.
Depois, deixa de funcionar de cinco formas, que pode reproduzir individualmente em menos de um minuto.
- As shells não interativas nunca apresentam um prompt.
ssh you@server 'id'executa o comando e termina, e nada é registado porquePROMPT_COMMANDnunca foi avaliado. - É uma variável.
unset PROMPT_COMMANDdesativa-a durante o resto da sessão e não requer privilégios. - O ficheiro é lido pelas shells de login.
bash --noprofile --norcnunca carrega/etc/profile.d/. - É específico do bash.
zsh,sh,python3 -c 'import os; os.system("id")'e:!iddentro devimexecutam programas que nenhum hook de prompt do bash irá observar. - Regista a linha tal como foi introduzida. Por isso, um alias ou uma função continua a ocultar o comando que foi realmente executado.
Use um hook de shell como conveniência. Ele responde à pergunta "o que executei na terça-feira passada" para utilizadores cooperantes. Não permita que uma lista de verificação o considere um controlo.
The kernel audit subsystem sees every execve
The Linux audit subsystem, driven by the auditd daemon, is the only layer here that a user cannot step around, because the record is made inside the kernel at the moment the syscall runs. If a process executes a program, there is an event. The shell, the language and the presence of a terminal make no difference.
sudo apt update && sudo apt install -y auditd audispd-plugins
sudo systemctl enable --now auditd
sudo auditctl -sauditctl -s prints the daemon state. enabled 1 with a non-zero pid means it is running, and lost 0 means no records have been dropped yet. Remember that lost counter, it comes back later.
auid is the field that makes audit worth the trouble. PAM sets a login uid when a session starts, and the kernel carries it on every child process from then on. Check yours:
cat /proc/self/loginuidAn interactive SSH session prints your uid, because /etc/pam.d/sshd includes pam_loginuid.so. A value of 4294967295 means the loginuid was never set, which is normal for a process started by a system daemon at boot. The important part is that sudo -i does not change it: a root shell opened by alice still carries auid 1000, so every command inside it is attributable to alice. That is exactly the gap sudo leaves open. Changing a loginuid once set needs CAP_AUDIT_CONTROL, which ordinary users do not have, and sudo auditctl --loginuid-immutable closes it for root as well until the next reboot.
Check that /etc/pam.d/sshd, /etc/pam.d/login and /etc/pam.d/cron each include pam_loginuid.so, or events will arrive with nobody attached to them. That is the same file list you touch when hardening SSH access on a VPS, so do the two jobs together.
Um conjunto inicial de regras para auditd
As regras ficam em /etc/audit/rules.d/*.rules. augenrules concatena-as pela ordem dos nomes dos ficheiros numa única lista, e a ordem determina o comportamento, porque o kernel para na primeira regra correspondente. Leia primeiro o que já existe antes de adicionar qualquer regra, porque um -D num ficheiro posterior elimina tudo o que foi carregado antes.
ls /etc/audit/rules.d/
cat /etc/audit/rules.d/audit.rulesDepois, escreva /etc/audit/rules.d/50-exec.rules:
## Suppressions first: the kernel takes the first matching rule.
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-deb
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-query
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-split
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-trigger
## Every program started by a logged-in human.
-a always,exit -F arch=b64 -S execve,execveat -F auid>=1000 -F auid!=unset -k exec
-a always,exit -F arch=b32 -S execve,execveat -F auid>=1000 -F auid!=unset -k exec
## Changes to who may become root.
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
## Changes to the audit configuration itself.
-w /etc/audit/ -p wa -k auditconfigCarregue as regras e confirme:
sudo augenrules --load
sudo auditctl -lauditctl -l imprimir novamente as regras significa que estão ativas. No rules significa que o carregamento falhou, e journalctl -u auditd -n 20 identifica o ficheiro e a linha que o analisador rejeitou. Versões mais antigas do espaço de utilizador do audit não compreendem a palavra-chave unset. Se o carregador se queixar desse campo, escreva -F auid!=4294967295, que representa o mesmo valor por extenso.
Agora leia novamente os eventos:
sudo ausearch -k exec -ts recent -i | tail -40
sudo ausearch -ul 1000 -ts today -i
sudo aureport -k --summary -i-i converte uids e números de chamadas de sistema em nomes, e na prática não é opcional. -ts recent abrange os últimos dez minutos. Cada execução chega como um grupo de registos: um registo SYSCALL que contém uid, auid, estado de saída e chave; um registo EXECVE com a lista completa de argumentos; além dos registos CWD e PATH relativos ao contexto.
Há uma limitação importante, porque costuma causar confusão. O audit regista chamadas de sistema, e um builtin da shell não faz nenhuma chamada de sistema própria. cd /root não executa nenhum programa. echo evil >> /etc/passwd escrito numa prompt do bash também não executa nenhum programa, porque tanto o echo como o redirecionamento ocorrem dentro do processo da shell que já está em execução. Por isso, as regras execve detetam programas e as regras -w detetam as escritas. Nenhum dos dois conjuntos é suficiente por si só.
Por fim, bloqueie a configuração:
## /etc/audit/rules.d/99-finalize.rules
-e 2-e 2 torna o conjunto de regras imutável até ao próximo reboot. Depois do carregamento, auditctl -s devolve enabled 2, e qualquer tentativa de adicionar ou eliminar uma regra falha com Operation not permitted, incluindo para root. Adicione este ficheiro por último e conte com um reboot sempre que quiser alterar uma regra. Esse compromisso é o objetivo: um conjunto de regras que qualquer pessoa pode desativar silenciosamente não constitui evidência.
Um log de auditoria que ninguém lê é um artefacto de conformidade
O modo de falha do auditd não é deixar passar eventos. É registar tantos eventos que ninguém os consulta. Depois, o log existe para satisfazer uma lista de verificação, em vez de responder a uma pergunta.
Faça os cálculos no seu próprio servidor antes de ajustar qualquer configuração:
sudo aureport -k --summary -i
sudo du -sh /var/log/auditUm único sudo apt upgrade executa milhares de processos de curta duração, e cada um deles transporta o seu auid. Por isso, uma atualização de pacotes pode gerar mais eventos do que uma semana de introdução manual de comandos. É por isso que as exclusões acima identificam dpkg e os respetivos auxiliares. Faça exclusões por executável, nunca por utilizador. Uma exclusão para /usr/bin/dpkg é uma lacuna que pode descrever numa frase. Uma exclusão para uma conta é uma lacuna com exatamente o formato daquilo que tentava detetar.
A chave -k em cada regra é o que torna o log pesquisável um mês depois. ausearch -k sudoers é uma pergunta com uma resposta. ausearch sem filtro é uma parede de texto que o habitua a deixar de ler. Se o seu coletor exigir JSON em vez do formato nativo, laurel é um plugin do auditd que reescreve cada evento como um objeto JSON, com os argumentos descodificados. É registado em /etc/audit/plugins.d/ como qualquer outro plugin, e o auditd aplica as alterações aos plugins após sudo pkill -HUP auditd.
O custo do auditd, de forma realista
Cada chamada de sistema correspondente torna-se um registo que o kernel formata e entrega ao userspace. O custo aparece em dois pontos, e ambos podem ser medidos na sua própria carga de trabalho, em vez de serem estimados a partir de um valor publicado por terceiros.
- CPU e latência. Uma máquina que cria processos continuamente, um host de compilação ou um executor de CI gera um registo por execução. Quando o backlog do kernel fica cheio,
--backlog_wait_timefaz o kernel suspender o processo que gerou o evento até haver espaço. Assim, o audit aparece como compilações lentas, e não como uma percentagem de CPU. Monitorizebacklogelostemsudo auditctl -ssob carga real. Umlostcrescente significa que foram descartados registos. Um log com lacunas silenciosas é pior do que não ter log, porque continuará a confiar nele. - Disco. Leia
/etc/audit/auditd.confe decida deliberadamente o que deve acontecer quando o disco ficar cheio, porque os valores fornecidos são apenas opções predefinidas.max_log_file,num_logsemax_log_file_actioncontrolam a rotação.space_left_action,admin_space_left_actionedisk_full_actioncontrolam a emergência. Algumas ações disponíveis, incluindohaltesingle, desligam a máquina em vez de perder um registo.
A linha -f em /etc/audit/rules.d/audit.rules representa a mesma decisão ao nível do kernel: -f 1 comunica uma falha de auditoria ao syslog, e -f 2 provoca um panic no kernel. Escolha 2 apenas se preferir genuinamente perder o servidor a perder um registo. Numa VPS que executa um serviço de que outras pessoas dependem, prefira a rotação e transfira o problema de armazenamento para fora da máquina.
Enviar os logs para fora do servidor, quase em tempo real
Esta é a conclusão que os relatórios de incidentes continuam a confirmar. Os logs que permanecem no host comprometido podem ser alterados por quem o comprometeu. O root pode reescrever /var/log/auth.log, eliminar /var/log/audit/audit.log e parar o daemon. -e 2 impede que as regras sejam descarregadas. Não faz nada contra rm. Todas as camadas acima só produzem evidências se uma cópia sair primeiro da máquina.
O transporte próprio do Audit é o plugin audisp-remote de audispd-plugins. Ative-o em /etc/audit/plugins.d/au-remote.conf:
active = yes
direction = out
path = /usr/sbin/audisp-remote
type = always
format = stringVerifique path em relação a command -v audisp-remote antes de recarregar, porque um caminho incorreto não produz nada além de uma linha no journal. Defina remote_server e port em /etc/audit/audisp-remote.conf e, no coletor, defina tcp_listen_port = 60 no respetivo auditd.conf. Recarregue com sudo pkill -HUP auditd. Em muitas imagens, systemctl restart auditd é recusado porque o ficheiro da unidade define RefuseManualStop=yes. Por isso, o sinal é o método fiável.
A outra opção coloca o Audit no fluxo syslog que já encaminha. /etc/audit/plugins.d/syslog.conf é fornecido com active = no. Defina-o como yes, recarregue e os eventos do Audit serão acrescentados às linhas do sudo e a todo o restante. Em seguida, encaminhe tudo com rsyslog sobre TLS (Transport Layer Security), que requer o pacote rsyslog-gnutls:
# /etc/rsyslog.d/60-forward.conf
*.* action(type="omfwd"
target="logs.example.net" port="6514" protocol="tcp"
StreamDriver="gtls" StreamDriverMode="1"
StreamDriverAuthMode="x509/name"
StreamDriverPermittedPeers="logs.example.net"
action.resumeRetryCount="-1"
queue.type="linkedList" queue.filename="fwd" queue.saveOnShutdown="on")As definições da fila são a parte importante. action.resumeRetryCount="-1" tenta novamente indefinidamente, e a fila assistida por disco com queue.saveOnShutdown="on" mantém os registos enquanto o coletor está inacessível e envia-os quando volta a estar disponível. Sem essas duas definições, um reboot do coletor cria uma lacuna nas evidências sem qualquer indicação de que ela existe. Aplique com sudo systemctl restart rsyslog e confirme depois que os registos chegam efetivamente ao coletor, antes de confiar no sistema.
Falta fechar um ponto: o coletor tem de ser uma máquina na qual as pessoas auditadas não possam iniciar sessão. Se o mesmo grupo de administradores tiver acesso root ao servidor de logs, copiou o ficheiro, mas não o protegeu. Use credenciais separadas, chaves separadas e, idealmente, uma conta de fornecedor separada. Este é o mesmo princípio que torna uma forma centralizada de gerir muitos servidores Linux útil para implementar antes de ser necessária, e é o que distingue uma primeira hora útil de uma primeira hora inútil quando está a lidar com um VPS comprometido.
Verifique que um utilizador normal não pode reescrever o registo
Teste a afirmação em vez de partir do princípio de que é verdadeira. A partir de uma conta normal, sem sudo:
echo test >> /var/log/auth.log
cat /var/log/audit/audit.log
auditctl -D
id -nGEspere, pela ordem: Permission denied, porque auth.log pertence a syslog, com o grupo adm e o modo 640; Permission denied novamente, porque o log de auditoria tem o modo 600 e pertence a root; um erro a recusar a execução, porque a alteração das regras de auditoria requer CAP_AUDIT_CONTROL; e uma lista de grupos que não contém adm nem systemd-journal.
Esta última verificação é a que costuma falhar. A pertença a adm concede acesso de leitura a /var/log/auth.log, e a pertença a systemd-journal concede acesso de leitura ao journal completo. Nenhuma concede acesso de escrita, portanto nenhuma permite adulterações. Ambas permitem que uma pessoa leia todas as linhas de autenticação do servidor. Essa decisão deve ser tomada deliberadamente, em vez de copiar uma linha usermod -aG de uma resposta num fórum.
Por fim, confirme os dois elementos que têm de sobreviver a um reboot:
sudo auditctl -s
systemctl is-enabled auditdenabled 2 significa que o conjunto de regras fica bloqueado até ao próximo boot. enabled na segunda execução significa que o auditd volta a iniciar depois desse boot. Um conjunto de regras que dura apenas até à próxima atualização do kernel também não é uma trilha de auditoria.
FAQ
Como vejo todos os comandos executados por um utilizador específico?
Descubra o uid com id -u alice e pesquise o log de auditoria pelo uid de início de sessão: sudo ausearch -ul 1000 -ts today -i. Adicione -k exec para limitar a pesquisa à regra execve. O uid de início de sessão é definido no início de sessão e mantém-se através de su e sudo -i, pelo que isto também deteta comandos executados numa shell root aberta por essa conta. Só funciona para comandos executados depois de as regras serem carregadas, porque o audit não mantém um histórico de eventos que não estava configurado para registar. sudo aureport -k --summary -i mostra as contagens por regra, se quiser verificar primeiro a distribuição dos dados.
Um utilizador pode eliminar o histórico do bash para ocultar o que executou?
Sim, e não precisa de privilégios. ~/.bash_history pertence a esse utilizador e tem o modo 600, pelo que o utilizador pode editá-lo, truncá-lo ou removê-lo. Também pode impedir que seja escrito com unset HISTFILE, parar o registo a meio da sessão com set +o history ou ocultar comandos individuais escrevendo-os com um espaço inicial quando HISTCONTROL=ignorespace está definido. O Bash escreve o ficheiro quando a shell termina, pelo que uma sessão terminada com kill -9 $$ não regista nada. Trate o histórico da shell como uma indicação, nunca como prova.
O sudo regista o que acontece dentro de sudo -i?
Não. O sudo regista o comando que lhe foi pedido para executar, pelo que sudo -i produz uma linha para a shell, mas nada do que acontece depois. Todos os comandos introduzidos nessa shell root ficam invisíveis para o sudo, porque o sudo deixa de estar envolvido. sudo su -, sudo bash e qualquer programa permitido que disponibilize uma saída para uma shell comportam-se da mesma forma. Duas medidas fecham esta lacuna: regras de auditoria em execve, que registam todos os programas com o uid de início de sessão original associado, e regras sudoers que não disponibilizam uma shell.
O auditd vai tornar o meu servidor mais lento?
Depende inteiramente do número de processos que a carga de trabalho inicia. Meça o impacto em vez de confiar num valor de referência. Um servidor que responde sobretudo a pedidos executa poucos processos e não deverá notar o impacto. Um host de compilação ou runner de CI executa processos constantemente e pode notar um impacto significativo, porque, quando o backlog de auditoria do kernel fica cheio, o processo que gerou o evento é pausado até haver espaço. Execute sudo auditctl -s sob carga real e monitorize backlog e lost. Qualquer lost acima de zero significa que foram descartados registos. Esse é o pior resultado, porque o log passa a ter lacunas invisíveis.
Onde devem ser armazenados os logs de auditoria?
Noutro sistema, com um atraso medido em segundos. Qualquer pessoa que obtenha root no host auditado pode eliminar /var/log/audit/audit.log e reescrever /var/log/auth.log, pelo que as cópias locais só respondem a perguntas sobre incidentes que ninguém tentou ocultar. Encaminhe os dados com o plugin audisp-remote para um auditd central ou ative o plugin syslog do audit e encaminhe todo o fluxo syslog com o rsyslog através de TLS. Dê ao coletor credenciais próprias e confirme que as contas auditadas não têm acesso a ele.