Análise forense de um comprometimento no CloudLinux e cPanel: da armadilha do sshd_config à persistência no PAM e IPMI
Voltar para blog

Análise forense de um comprometimento no CloudLinux e cPanel: da armadilha do sshd_config à persistência no PAM e IPMI

21/10/2026 · 6 min · Cibersegurança

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#

  1. 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.
  2. Criação de contas fantasmas: inserção de um usuário administrativo na tabela interna da controladora:
   ipmitool user list 1
  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:

  1. Quarentena de rede: isole o servidor da rede corporativa ou da malha VPN para impedir movimentação lateral para outros nós.
  2. 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.
  3. Reinstalação limpa (Rebuild): faça o provisionamento de um novo sistema operacional do zero em discos formatados.
  4. 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:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários