Durante uma auditoria operacional no ambiente de desenvolvimento de um projeto, identifiquei comportamento compatível com exploração RCE em dependência legada (React2Shell). O incidente não ficou na camada de aplicação: houve indícios de persistência em shell startup e tentativa de execução recorrente em /tmp, com padrão típico de dropper para escalada posterior.
Este artigo documenta exatamente o que executei, em ordem cronológica, comandos incluídos, critérios de decisão e validações de saída. O objetivo foi restaurar disponibilidade com rastreabilidade técnica e reduzir risco residual para nível aceitável de produção.
1) Detecção inicial, triagem e ativação do protocolo
O primeiro alerta não veio de dashboard bonito: veio de comportamento anômalo de processos de diagnóstico e ruído incomum em sessão shell.
Sinais observados na triagem inicial:
- interrupções intermitentes durante inspeção com
psetop. - artefatos executáveis e ocultos em
/tmp. - entradas suspeitas em inicialização de shell (
/etc/profile). - conexões de saída sem vínculo com o fluxo normal da aplicação.
Comandos executados na triagem quente:
ps auxf
ps -eo pid,ppid,user,cmd --sort=-%cpu | head -n 40
top -b -n 1 | head -n 60
ss -lntup
ss -plant
journalctl -xe --no-pager | tail -n 200
last -a | head -n 30
Com base nesses indicadores, ativei resposta a incidente em modo contenção.
2) Contenção imediata (janela de impacto)
A contenção teve três objetivos: parar propagação, preservar evidência e evitar perda de disponibilidade em sistemas adjacentes.
Ações executadas:
- congelamento de mudanças (deploy e automações de CI/CD do projeto afetado).
- bloqueio de tráfego externo não essencial no host comprometido.
- isolamento de credenciais de operação usadas naquele host.
- preservação de artefatos para análise posterior.
Exemplo de bloqueio emergencial aplicado no host:
# Política restritiva temporária
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
# Exceções mínimas para administração segura durante resposta
iptables -A INPUT -p tcp --dport 22 -s <SEU_IP_ADMIN>/32 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 22 -d <SEU_IP_ADMIN>/32 -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
Nota operacional: em resposta a incidente, eu privilegio isolamento primeiro, limpeza depois. Sem isolamento, qualquer correção vira corrida contra processo ativo do atacante.
3) Coleta forense mínima viável (antes de tocar no disco)
Antes de remover qualquer arquivo, executei coleta de evidências para manter cronologia dos eventos e sustentar análise técnica posterior.
Itens coletados:
- processos e árvores de execução (
ps,top,pstree). - sockets e conexões (
ss -plant,ss -lntup). - logs críticos (
journalctl, auth logs, logs de aplicação). - hashes de arquivos suspeitos em
/tmpe startup scripts.
Exemplo de coleta aplicada:
mkdir -p /root/ir-evidence/{logs,proc,net,fs}
date -u > /root/ir-evidence/timestamp_utc.txt
ps auxf > /root/ir-evidence/proc/ps_auxf.txt
ss -plant > /root/ir-evidence/net/ss_plant.txt
ss -lntup > /root/ir-evidence/net/ss_lntup.txt
journalctl -b --no-pager > /root/ir-evidence/logs/journal_current_boot.log
find /tmp -maxdepth 2 -type f -printf "%TY-%Tm-%Td %TT %p\n" \
> /root/ir-evidence/fs/tmp_file_timeline.txt
find /etc/profile* -maxdepth 1 -type f -exec sha256sum {} \; \
> /root/ir-evidence/fs/profile_hashes.txt
Compactei evidências para retenção offline:
tar -czf /root/ir-evidence-$(date +%F-%H%M).tar.gz /root/ir-evidence
sha256sum /root/ir-evidence-*.tar.gz > /root/ir-evidence.sha256
4) Recuperação forense em Rescue Mode (sem SO comprometido em execução)
Para eliminar qualquer interferência do ambiente contaminado, migrei o host para Rescue Mode e trabalhei com montagem passiva.
Fluxo executado:
- reboot em modo rescue via painel do provedor.
- identificação de partições e montagem em leitura.
- extração seletiva de dados confiáveis.
- revisão de persistência SSH e shell startup fora do runtime comprometido.
Sequência base utilizada:
lsblk
blkid
mount /dev/vda2 /mnt/sysroot
mount -o remount,ro /mnt/sysroot
4.1) Backup cirúrgico (somente o que é confiável)
Copiei apenas o que era necessário para continuidade de negócio:
- código-fonte versionado do projeto.
- dumps de banco e arquivos de configuração essenciais.
- artefatos de deploy reconstruíveis por pipeline foram descartados.
Itens explicitamente excluídos:
node_modules, caches de pacote e binários transitórios.- qualquer executável fora da cadeia conhecida do projeto.
Exemplo de extração seletiva:
rsync -aHAX --numeric-ids \
--exclude='node_modules' \
--exclude='.cache' \
--exclude='tmp' \
/mnt/sysroot/home/domain_user/app/ /mnt/backup/app/
4.2) Auditoria de chaves e persistência
Revisei e saneei todos os pontos de acesso persistente:
cat /mnt/sysroot/root/.ssh/authorized_keys
cat /mnt/sysroot/home/*/.ssh/authorized_keys
grep -R "ssh-rsa\|ssh-ed25519" /mnt/sysroot/home -n
find /mnt/sysroot/etc -type f -name "*profile*" -o -name "*rc" | sort
Entradas não reconhecidas foram removidas, registradas e correlacionadas com o horário dos eventos no relatório de incidente.
5) Decisão técnica: rebuild completo (clean slate)
Com evidência de tentativa de persistência e vetor com potencial de escalada, optei por rebuild completo do host.
Critério usado para descartar "limpeza parcial":
- confiança comprometida em integridade de bibliotecas e binários.
- alto custo de validar 100% de um sistema possivelmente adulterado.
- risco residual inaceitável para retorno de carga em produção.
Resultado: formatação e reinstalação completa do SO como única opção com rastreabilidade e compliance defensável.
6) Hardening pós-reinstalação (Rocky Linux baseline)
Após reinstalar Rocky Linux, apliquei uma baseline prática de endurecimento em camadas.
6.1) Segregação de privilégios e runtime sem root
Criei usuário de sistema dedicado para execução da aplicação, sem shell de administração para tarefas comuns.
sudo adduser --system --group --home /home/domain_user domain_user
id domain_user
Ajustei ownership da aplicação para o usuário de runtime e removi permissões excessivas de escrita em diretórios sensíveis.
6.2) Endurecimento de filesystem temporário
Configurei /tmp com noexec,nosuid,nodev para reduzir vetor de execução de payload em diretório temporário.
echo "tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0" >> /etc/fstab
mount -o remount /tmp
findmnt /tmp
6.3) Hardening de SSH
No /etc/ssh/sshd_config, apliquei:
PasswordAuthentication noPermitRootLogin no- porta administrativa customizada
- allowlist de usuários autorizados
Validação e aplicação:
sshd -t
systemctl restart sshd
systemctl status sshd --no-pager
6.4) Perímetro com firewalld (deny by default)
Substituí regra aberta por política de menor privilégio:
firewall-cmd --permanent --set-default-zone=drop
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --reload
firewall-cmd --list-all
6.5) Isolamento lógico de dados
Mantive dados operacionais em /home e isolei caminho de aplicação do core do SO para simplificar backup, restore e governança de permissões.
mkdir -p /home/www
ln -s /home/www /www
ls -la /
6.6) Regras de kernel e hygiene básica
Apliquei parâmetros de rede conservadores para reduzir superfícies conhecidas:
cat >/etc/sysctl.d/99-hardening.conf <<'SYSCTL'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.tcp_syncookies = 1
SYSCTL
sysctl --system
7) Restauração de aplicação e deploy controlado
Com host endurecido, executei restauração com foco em previsibilidade:
- restauração de código limpo e configuração auditada.
- reinstalação de dependências a partir de lockfile.
- ajuste de runtime RHEL-like (incluindo
xdg-utilspara tooling de dev). - subida com gerência de processo e observabilidade mínima.
Stack operacional aplicada:
- Bun para build e execução.
- PM2 para supervisão, restart automático e controle de processo.
Validações de pós-deploy:
pm2 status
pm2 logs --lines 100
ss -lntup | grep -E ':80|:443|:3000'
systemctl status sshd firewalld --no-pager
Critério de saída do incidente:
- serviço estável por janela contínua sem IOC novo.
- logs sem tentativa de reexecução do vetor anterior.
- acesso administrativo restrito e auditável.
8) Checklist operacional executado
- [x] detecção e triagem com evidência técnica coletada.
- [x] contenção de rede e congelamento de mudanças.
- [x] coleta forense mínima viável com hash e empacotamento.
- [x] recuperação em rescue mode com cópia seletiva confiável.
- [x] rebuild completo do host por perda de confiança.
- [x] hardening de SSH, firewall,
/tmp, sysctl e privilégios. - [x] restauração da aplicação com runtime supervisionado.
- [x] validação de estabilidade e encerramento formal do incidente.
A principal lição operacional foi objetiva: em cenário com tentativa de escalada, rapidez sem método aumenta risco. O protocolo que funcionou foi sequencial e disciplinado: detectar, conter, preservar evidência, reconstruir, endurecer e só então restaurar carga.
Esse tipo de resposta não é teoria. É execução técnica orientada a continuidade, auditoria e redução real de superfície de ataque em ambiente Linux.
Este post está licenciado sob CC BY-NC.
Comentários
Participe da discussão abaixo.
Comentários ainda não configurados. Adicione as opções do Cusdis em /assets/json/config/blog-comments-config.json.