Na administração de servidores Linux, confiar cegamente em arquivos de texto de configuração é um caminho fácil para falsas sensações de segurança. Recentemente, ao analisar um servidor com CloudLinux e cPanel que deveria estar acessível apenas via VPN e com login por senha desativado, os registros de auditoria revelaram um cenário crítico: a máquina havia sido completamente comprometida.
Neste artigo, vamos dissecar como a invasão aconteceu e como os invasores mantiveram o acesso: a armadilha da regra first-match nos arquivos de configuração do OpenSSH, a injeção de bibliotecas permissivas no PAM, o escape de namespaces do CageFS por contas com shells comuns e a persistência em baixo nível no controlador BMC/IPMI da placa-mãe.
Ao final, compartilhamos dois scripts completos para executar varreduras forenses em múltiplos servidores simultaneamente.
1. A armadilha do sshd_config e a regra first-match#
O primeiro erro em uma verificação de segurança é abrir o arquivo /etc/ssh/sshd_config e assumir que o que está escrito ali reflete o estado real do serviço.
No servidor analisado, as linhas do arquivo principal pareciam seguras:
#PermitRootLogin prohibit-password
#PasswordAuthentication yes
O valor padrão compilado no binário#
Uma linha comentada no arquivo de configuração do OpenSSH não significa que o recurso está desativado. Significa que o daemon adotará o valor padrão compilado no próprio executável. No ecossistema RHEL, CentOS e CloudLinux, o padrão histórico para PasswordAuthentication é yes.
Para consultar os valores que o daemon realmente carregou na memória, precisamos usar a flag de teste estendido:
sshd -T | grep -iE "(passwordauthentication|permitrootlogin)"
A ordem dos arquivos drop-in: quem chega primeiro vence#
A brecha de entrada ocorreu pela diretiva Include /etc/ssh/sshd_config.d/.conf. Diferente de servidores web como Apache ou Nginx, onde configurações carregadas por último costumam sobrescrever as anteriores, o OpenSSH segue estritamente a regra de primeira correspondência (first-match wins*). Assim que o parser encontra a primeira declaração de um parâmetro, ele descarta qualquer repetição futura.
Na pasta de drop-ins, havia dois arquivos:
/etc/ssh/sshd_config.d/01-permitrootlogin.conf: PermitRootLogin yes
/etc/ssh/sshd_config.d/90-root-vpn-corp.conf: PermitRootLogin no
Como o arquivo 01- é lido antes do 90- pela ordem alfabética, o daemon assumiu PermitRootLogin yes globalmente, ignorando as restrições criadas no arquivo posterior. O servidor ficou ouvindo em todas as interfaces de rede (0.0.0.0) e aceitando autenticação de root por senha.
2. A camada PAM: quando o sistema ignora o bloqueio do SSHD#
Mesmo quando o OpenSSH está configurado com PasswordAuthentication no, a presença da diretiva UsePAM yes faz com que o fluxo final de autenticação seja delegado aos módulos do sistema (Pluggable Authentication Modules).
Durante a checagem de integridade dos pacotes do sistema operacional via RPM:
rpm -V openssh-server pam
A saída acusou adulteração no arquivo de controle do serviço:
S.5....T. c /etc/pam.d/sshd
A flag 5 (divergência de hash MD5/SHA256), S (tamanho modificado) e T (data de modificação alterada) confirmaram a alteração indevida.
Como a adulteração no PAM funciona#
O invasor inseriu uma regra com a flag de controle sufficient associada ao módulo pam_permit.so logo no topo da pilha de autenticação:
auth sufficient pam_permit.so
Na arquitetura do PAM, quando um módulo marcado como sufficient retorna sucesso, toda a cadeia de autenticação é considerada válida imediatamente, sem consultar os módulos subsequentes. Com isso, qualquer senha enviada pelo invasor era aceita pelo processo filho do SSHD.
Para investigar quais bibliotecas o daemon abre durante uma tentativa de login:
strace -f -p $(pgrep -o sshd) -e trace=open,openat,read 2>&1 | grep pam
3. CloudLinux, CageFS e a fuga do isolamento via /bin/Bash#
Em servidores cPanel com CloudLinux, a proteção entre usuários depende do CageFS (baseado no módulo LVE do kernel), que cria um sistema de arquivos virtual isolado (chroot/namespaces) para cada conta. Para que essa jaula funcione, o shell cadastrado no /etc/passwd deve apontar para /usr/local/cpanel/bin/jailshell ou /bin/false.
A auditoria revelou diversas contas configuradas com /bin/bash tradicional:
usuario1:x:1005:1005::/home/usuario1:/bin/bash
usuario2:x:1008:1008::/home/usuario2:/bin/bash
O impacto no kernel#
Quando um usuário com jailshell se autentica, o binário monta namespaces virtuais e mascara o diretório /proc, escondendo processos de outros clientes e binários administrativos do host.
Quando a conta tem o /bin/bash padrão, o kernel entrega uma sessão TTY sem o invólucro do CageFS. Se uma aplicação web (como um WordPress) for invadida nessa conta, o invasor ganha acesso direto aos cabeçalhos do kernel e compiladores do sistema base, facilitando a exploração de vulnerabilidades para escalação local de privilégios (LPE).
4. Persistência em hardware: o ponto cego do IPMI e BMC#
Invasores experientes sabem que administradores podem formatar discos ou reinstalar o sistema operacional após detectarem um incidente. Para manter a persistência fora do alcance do disco, eles buscam se mover lateralmente para o controlador de gerenciamento da placa-mãe (BMC/IPMI).
Se os módulos de kernel ipmi_si e ipmi_devintf estiverem carregados, o usuário root consegue enviar comandos diretamente ao barramento de hardware através do arquivo de dispositivo /dev/ipmi0.
Vetores comuns de persistência no BMC#
- Ativação do Cipher 0: reconfigura o canal de rede do IPMI para aceitar conexões sem senha. Qualquer pacote enviado à porta UDP 623 do IPMI é aceito com privilégios de administrador.
- Criação de contas fantasmas: inserção de um usuário administrativo na tabela interna da controladora:
ipmitool user list 1
- Regravação de firmware (Firmware Reflash): gravação de imagens adulteradas diretamente na memória flash da placa-mãe, sobrevivendo à substituição completa de discos rígidos e SSDs.
5. Ferramentas de auditoria forense#
Para investigar múltiplos servidores sem depender de verificações manuais pontuais, estruturamos duas ferramentas em Bash: um orquestrador paralelo para conexões simultâneas e um script coletor que audita o estado de memória, arquivos de autorização e o hardware.
Ferramenta 1: Orquestrador paralelo (corp-coleta.sh)#
Este script lê uma lista de servidores e dispara comandos em paralelo, utilizando ServerAliveInterval para evitar processos travados e limpando códigos de cor ANSI dos arquivos de log gravados:
#!/usr/bin/env bash
# corp-coleta.sh - Execucao paralela de auditoria forense em multiplos hosts
USUARIO="root"
HOSTS_DEFAULT="$HOME/.corp-hosts"
SSH_OPTS="-o ConnectTimeout=10 -o ServerAliveInterval=15 -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR"
PARALELO=20
if [[ $# -eq 0 ]]; then
echo "Uso: $0 \"comando\" [arquivo_hosts]"; exit 1
fi
COMANDO="$1"
HOSTS_FILE="${2:-$HOSTS_DEFAULT}"
if [ ! -f "$HOSTS_FILE" ]; then
echo "Arquivo de hosts nao encontrado: $HOSTS_FILE"; exit 1
fi
ENTRIES=(); while IFS= read -r line; do ENTRIES+=("$line"); done < <(grep -v '^\s*#' "$HOSTS_FILE" | grep -v '^\s*$')
TOTAL=${#ENTRIES[@]}
TMPDIR_EXEC=$(mktemp -d)
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
LOG_DIR="$HOME/corp-coletas"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/coleta_${TIMESTAMP}.log"
echo "Iniciando conexoes para $TOTAL servidores..."
echo "# Auditoria - $TIMESTAMP | Comando: $COMANDO" >> "$LOG_FILE"
strip_colors() { sed -r 's/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g'; }
ativo=0
for entry in "${ENTRIES[@]}"; do
host="${entry%%:*}"
porta="${entry##*:}"
[[ "$porta" == "$host" ]] && porta=22
outfile="$TMPDIR_EXEC/${host}"
ssh $SSH_OPTS -p "$porta" "$USUARIO@$host" "$COMANDO" > "$outfile" 2>&1 &
(( ativo++ ))
if (( ativo >= PARALELO )); then
wait -n 2>/dev/null || wait; (( ativo-- ))
fi
done
wait
OK=0; FAIL=0
while IFS= read -r entry; do
host="${entry%%:*}"
outfile="$TMPDIR_EXEC/${host}"
if [[ -s "$outfile" ]]; then
echo "=== Conectado: $host ==="
cat "$outfile"
{ echo -e "\n=== $host ==="; cat "$outfile" | strip_colors; } >> "$LOG_FILE"
(( OK++ ))
else
echo "Falha de conexao: $host"
echo -e "\nFalha de conexao: $host" >> "$LOG_FILE"
(( FAIL++ ))
fi
done < <(printf '%s\n' "${ENTRIES[@]}" | sort)
echo "Auditoria concluida. Registros salvos em: $LOG_FILE"
rm -rf "$TMPDIR_EXEC"
Ferramenta 2: Coletor forense unificado (corp_audit.sh)#
Este script é executado no host para inspecionar a memória do OpenSSH, verificar atributos imutáveis (chattr), auditar shells de contas do cPanel e checar o estado do IPMI:
#!/bin/bash
# corp_audit.sh - Coleta forense de configuracoes SSH, PAM, cPanel e IPMI
export LANG=C
TRUSTED_IPS="100\.|203\.0\.113\.|198\.51\.100\."
echo "================================================================"
echo " AUDITORIA FORENSE DE CONFIGURACOES E HARDWARE "
echo "================================================================"
# 1. Estado efetivo em memoria do SSHD
echo -e "\n[1] Verificando parametros em memoria (sshd -T):"
SSHD_VARS=$(sshd -T 2>/dev/null)
eval_param() {
local param=$1; local expected=$2
local current=$(echo "$SSHD_VARS" | grep -i "^$param " | awk '{print $2}')
printf " %-30s : %-15s " "$param" "$current"
if [ "$current" = "$expected" ]; then
echo "[OK]"
else
echo "[ATENCAO]"
fi
}
eval_param "permitrootlogin" "prohibit-password"
eval_param "passwordauthentication" "no"
echo -e "\nOrdem de carregamento dos arquivos de configuracao:"
grep -rE "Include|PermitRootLogin|PasswordAuthentication" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ 2>/dev/null | awk -F':' '{printf " - %-45s : %s\n", $1, $2}'
# 2. Logins recentes de root fora das redes conhecidas
echo -e "\n[2] Verificando sessoes root recentes:"
last -i | grep "^root " | grep -vE "($TRUSTED_IPS|0\.0\.0\.0|127\.0\.0\.1)" | head -n 5 | awk '{printf " %-10s | %-18s | %s %s %s\n", $1, $3, $4, $5, $6}'
# 3. Chaves autorizadas e atributos estendidos
echo -e "\n[3] Inspecionando /root/.ssh/authorized_keys:"
if [ -f /root/.ssh/authorized_keys ]; then
ATTRS=$(lsattr /root/.ssh/authorized_keys 2>/dev/null)
if [[ "$ATTRS" == *"-i-"* ]]; then
echo " [ALERTA] Arquivo authorized_keys com bit imutavel (+i) ativo!"
fi
echo " Comentarios das chaves presentes:"
awk '{print " -> " $NF}' /root/.ssh/authorized_keys
fi
# 4. Auditoria de shells no cPanel / CloudLinux
echo -e "\n[4] Verificando contas com shell interativo sem jailshell:"
SAFE_SHELLS=("jailshell" "noshell" "/bin/false" "/sbin/nologin")
RISK_FOUND=0
while IFS=: read -r user pass uid gid info home shell; do
if [ "$uid" -ge 1000 ] && [ "$uid" -ne 65534 ]; then
IS_SAFE=false
for safe in "${SAFE_SHELLS[@]}"; do [[ "$shell" == *"$safe"* ]] && IS_SAFE=true && break; done
if [ "$IS_SAFE" = false ]; then
echo " [RISCO] Usuario: $user | Shell: $shell"
RISK_FOUND=1
fi
fi
done < /etc/passwd
[ "$RISK_FOUND" -eq 0 ] && echo " Todas as contas utilizam shells restritos compativeis."
# 5. Auditoria de hardware via IPMI
echo -e "\n[5] Verificando configuracoes do BMC/IPMI:"
if command -v ipmitool >/dev/null 2>&1; then
modprobe ipmi_devintf ipmi_si 2>/dev/null
if [ -c /dev/ipmi0 ]; then
echo " Usuarios cadastrados no BMC:"
ipmitool user list 1 2>/dev/null | awk '{print " -> " $0}'
LAN_INFO=$(ipmitool lan print 1 2>/dev/null)
if echo "$LAN_INFO" | grep "Auth Type Enable" | grep -q "NONE"; then
echo " [ALERTA CRITICO] O IPMI aceita autenticacao nula (Cipher 0 / NONE)!"
fi
else
echo " Dispositivo /dev/ipmi0 nao disponivel."
fi
else
echo " Utilitario ipmitool nao instalado."
fi
Plano de ação e resposta a incidentes para ambientes comprometidos#
Quando um servidor atinge esse nível de violação (módulos PAM adulterados, SSH escutando externamente por sobreposição de includes, contas sem jaula de isolamento e alterações no BMC), tentar "limpar" o sistema operacional manualmente quase sempre deixa pontas soltas.
A conduta mais segura envolve quatro passos essenciais:
- Quarentena de rede: isole o servidor da rede corporativa ou da malha VPN para impedir movimentação lateral para outros nós.
- Cold reset e regravação do BMC: efetue o desligamento elétrico completo da máquina, redefina a controladora de gerenciamento para os padrões de fábrica e atualize a imagem de firmware via canal limpo do fabricante.
- Reinstalação limpa (Rebuild): faça o provisionamento de um novo sistema operacional do zero em discos formatados.
- Migração segura de dados: restaure apenas as pastas de conteúdo dos clientes (
/home/), nunca copiando binários de sistema ou diretórios como/etc/e/usr/. Após a restauração, valide se as contas foram criadas com o shell restrito (jailshell) ativo.
No gerenciamento de servidores Linux, o que importa não é apenas o que está digitado nos arquivos de texto, mas sim o que o kernel e os processos mantêm em execução na memória.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários