Security · 60 min

Faça o Hardening de um Servidor Linux

Suba sua própria máquina Linux descartável (Docker ou Vagrant) e leve-a de uma instalação padrão a uma base "hardened": usuário não-root com sudo, SSH só por chave, firewall travado, superfície de serviços enxuta e fail2ban vigiando a porta.

Problema

Um servidor Linux recém-provisionado não é seguro para expor à internet. De fábrica ele normalmente vem com: uma conta `root` acessível via SSH, autenticação por senha habilitada, todos os serviços padrão rodando e escutando em todas as interfaces, nenhuma regra de firewall e nenhuma proteção contra tentativas de login por força bruta. Scanners automatizados encontram hosts novos minutos depois de ficarem públicos — você não tem a opção de "endurecer depois". Neste lab você vai provisionar uma VM ou container Linux descartável que você controla totalmente, e então percorrer um checklist real de hardening contra ela: criar um usuário administrativo não-root, desabilitar o login root via SSH, trocar para autenticação só por chave, configurar um firewall que nega por padrão, desabilitar ou remover serviços que você não usa, e instalar o fail2ban para bloquear automaticamente hosts que martelam sua porta SSH. Cada passo termina com um comando que você roda para **provar** que o controle está no lugar — hardening que você não consegue verificar é hardening no qual você não pode confiar. > Pratique só contra uma VM/container descartável que você controla (Docker, > Vagrant, ou uma instância de cloud descartável sua). Nunca rode estes passos > exploratórios — especialmente as checagens de "antes" — contra um servidor que > você não é dono ou administrador.

Objetivos

Ao final deste lab você será capaz de:

Pré-requisitos

Para completar este lab você vai precisar de:

O modelo mental: negar por padrão, superfície mínima

Hardening não é um único interruptor — é uma pilha de controles independentes,
cada um fechando uma porta diferente. A filosofia que amarra tudo isso é
negar por padrão, superfície mínima: nada é permitido a menos que você
permita explicitamente, e nada roda a menos que você realmente precise que rode.

Visão do atacante sobre uma máquina "hardened"
  │
  ▼  sem login root via SSH              (Passo 2)
  ▼  sem autenticação por senha, só chave (Passo 3)
  ▼  firewall nega tudo o mais            (Passo 4)
  ▼  menos serviços escutando             (Passo 5)
  ▼  fail2ban bane IPs de força bruta      (Passo 6)
  ▼
um alvo bem menor e bem mais lento

Nenhum desses controles sozinho torna um servidor "seguro" — segurança é em
camadas (defense in depth). Uma chave SSH vazada ainda entra se você não tiver
firewall; uma porta de firewall aberta para um serviço vulnerável ainda é
explorada se o login root estiver desabilitado. Você endurece cada camada porque
não sabe qual delas o atacante vai atingir primeiro.

Por que praticar numa máquina descartável

Errar um passo durante o hardening (ex.: desabilitar autenticação por senha
antes da sua chave funcionar) pode te trancar para fora de um servidor real. Um
container ou VM descartável que você pode destruir e recriar em segundos remove
esse medo — quebre, aprenda, derrube, comece de novo. É exatamente para isso que
este lab serve.

Como trabalhar neste lab

Faça os passos em ordem — passos posteriores assumem que os anteriores estão
feitos (ex.: você precisa do seu usuário não-root e das chaves SSH funcionando
antes de desabilitar o login root/senha, ou você vai se trancar para fora).
Cada passo termina com um comando de verificação; não avance até ele passar.

Passos

  1. Provisione um host Linux descartável

    Você precisa de uma máquina Linux real e descartável com acesso SSH. O
    caminho mais rápido é um container Docker rodando sshd; o caminho mais
    realista é uma VM completa via Vagrant. Escolha um.

    Opção A — Docker (mais rápido)

    # Constrói uma imagem Ubuntu mínima com servidor SSH e uma senha root inicial
    cat > Dockerfile <<'EOF'
    FROM ubuntu:22.04
    RUN apt-get update && apt-get install -y openssh-server sudo && \
        mkdir /var/run/sshd && \
        echo 'root:changeme123' | chpasswd && \
        sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
        sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
    EXPOSE 22
    CMD ["/usr/sbin/sshd", "-D"]
    EOF
    
    docker build -t hardening-lab .
    docker run -d --name hardening-lab -p 2222:22 hardening-lab
    
    # Confirme que consegue acessar (senha: changeme123)
    ssh -p 2222 root@localhost
    

    Opção B — Vagrant (mais perto de uma VM real)

    vagrant init ubuntu/jammy64
    vagrant up
    vagrant ssh   # confirme que você entra na máquina
    

    Qualquer que seja sua escolha, ao final deste passo você deve estar
    logado como root (ou via vagrant ssh com sudo sem senha), com uma
    forma conhecida de voltar a entrar caso algo dê errado: no Docker,
    docker exec -it hardening-lab bash sempre te dá um shell root mesmo que o
    SSH quebre; no Vagrant, vagrant ssh da mesma forma contorna suas
    mudanças de hardening.

    Checkpoint

    Rode whoami e cat /etc/os-release dentro da máquina e confirme que você
    é root numa máquina Ubuntu/Debian nova. Anote em algum lugar o comando de
    "saída de emergência" (docker exec ou vagrant ssh) — você vai querer se
    um passo posterior der errado.

    Entregável deste passo: um host Linux descartável rodando, no qual você
    consegue entrar via SSH, mais uma saída de emergência documentada que não
    depende do SSH.

  2. Crie um usuário não-root com sudo

    Rodar tudo como root significa que qualquer processo comprometido — um bug
    na sua aplicação, uma dependência maliciosa, um script sequestrado — tem
    controle total da máquina instantaneamente. Um usuário não-root com sudo
    te dá o mesmo poder administrativo sob demanda, com uma trilha de
    auditoria e um raio de impacto menor para qualquer coisa que rode sem sudo.

    # Como root:
    adduser --disabled-password --gecos "" opslab
    usermod -aG sudo opslab      # Debian/Ubuntu; use o grupo 'wheel' no RHEL/Fedora
    
    # Dê uma senha ao novo usuário por enquanto (você vai desabilitar senha via SSH depois)
    passwd opslab
    
    # Confirme que o grupo sudo concede o que você espera
    cat /etc/sudoers.d/* /etc/sudoers 2>/dev/null | grep -i sudo
    

    Troque para ele e confirme que o sudo funciona antes de mexer na
    configuração do SSH:

    su - opslab
    sudo whoami        # deve imprimir: root
    

    Por que essa ordem importa

    Você cria e verifica o usuário não-root antes de travar o acesso root
    via SSH no Passo 3. Se você desabilitar o login root primeiro e o novo
    usuário ainda não estiver funcionando, você pode se trancar completamente
    para fora (num servidor real — nesta máquina descartável você só usaria a
    saída de emergência e começaria de novo, mas construa o hábito agora).

    Checkpoint

    De um terminal novo (não a sessão root), rode:

    ssh -p 2222 opslab@localhost   # ou o equivalente `vagrant ssh`
    sudo whoami                    # deve imprimir "root" sem erro
    

    Entregável deste passo: um usuário não-root que consegue autenticar e
    rodar sudo whoami com sucesso, confirmado a partir de um login separado,
    não só na sessão em que você o criou.

  3. SSH só por chave, sem login root

    Senhas são adivinháveis e phishable; pares de chave SSH não são. E mesmo uma
    senha forte no root está a uma credencial de distância do comprometimento
    total — remover a capacidade do root de logar via SSH inteiramente significa
    que um atacante precisa primeiro comprometer um usuário nomeado e auditável
    e escalar via sudo.

    Gere e instale uma chave

    Na sua máquina host (não dentro da máquina):

    ssh-keygen -t ed25519 -f ~/.ssh/hardening_lab -C "hardening-lab"
    ssh-copy-id -p 2222 -i ~/.ssh/hardening_lab.pub opslab@localhost
    # ou manualmente: acrescente o conteúdo do .pub em
    # /home/opslab/.ssh/authorized_keys dentro da máquina (modo 600, dono opslab)
    

    Confirme que o login por chave funciona antes de desabilitar senhas:

    ssh -p 2222 -i ~/.ssh/hardening_lab opslab@localhost 'whoami'
    

    Trave o sshd_config

    Dentro da máquina, edite /etc/ssh/sshd_config:

    PermitRootLogin no
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    PubkeyAuthentication yes
    AllowUsers opslab
    

    Aplique e reinicie:

    sudo sshd -t                       # valide a sintaxe antes de reiniciar
    sudo systemctl restart sshd
    

    Checkpoint — prove as duas metades

    # 1) Login por chave do opslab ainda funciona
    ssh -p 2222 -i ~/.ssh/hardening_lab opslab@localhost 'whoami'
    
    # 2) Login por senha é recusado (deve falhar / ser rejeitado, não pedir com sucesso)
    ssh -p 2222 -o PubkeyAuthentication=no opslab@localhost
    
    # 3) Login root é recusado por completo, mesmo com a senha certa
    ssh -p 2222 root@localhost
    

    Se os passos 2 ou 3 acima ainda deixarem você entrar, o sshd_config não
    recarregou — reveja a saída do sudo sshd -t e o systemctl status sshd.

    Entregável deste passo: o opslab só loga com a chave,
    PasswordAuthentication e PermitRootLogin estão confirmados desligados
    por uma tentativa de login real que falhou, não só lendo o arquivo de config.

  4. Trave o firewall com ufw

    Mesmo com o SSH endurecido, todo outro serviço na máquina ainda é alcançável
    de qualquer lugar a menos que um firewall diga o contrário. O ufw
    (Uncomplicated Firewall) te dá uma política de negar-por-padrão em poucos
    comandos.

    sudo apt-get update && sudo apt-get install -y ufw
    
    # Nega tudo de entrada por padrão, permite toda saída
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    
    # Permita só o que você realmente precisa. Limite a taxa do SSH contra força bruta.
    sudo ufw limit 22/tcp comment 'SSH, com rate-limit'
    # Adicione mais só quando precisar, ex.:
    # sudo ufw allow 80/tcp comment 'HTTP'
    # sudo ufw allow 443/tcp comment 'HTTPS'
    
    sudo ufw enable
    

    A ordem importa dentro de um container. Rodar sudo ufw enable antes
    de confirmar que sua regra de SSH está exatamente certa pode derrubar sua
    própria conexão. No Docker especificamente, o ufw interage de forma
    estranha com o mapeamento de portas baseado em iptables — se você estiver
    no Docker, trate este passo como aprender o fluxo de trabalho do ufw e
    verifique com ufw status; para um firewall totalmente imposto na frente
    de mapeamentos de porta do Docker, um alvo Vagrant/VM é mais realista.

    Por que limit em vez de allow para o SSH

    ufw limit 22/tcp nega um IP que faz mais de 6 tentativas de conexão em 30
    segundos — uma primeira linha de defesa barata contra força bruta,
    complementar ao (não substituta do) fail2ban no Passo 6.

    Checkpoint

    sudo ufw status verbose
    

    Confirme que a saída mostra:

    • Padrão: deny (incoming), allow (outgoing)
    • Só as portas que você permitiu explicitamente aparecem como ALLOW/LIMIT
    • Nada mais está aberto

    Depois, da sua máquina host, tente alcançar uma porta que você não
    abriu (ex.: nc -zv localhost 2222 deve funcionar, mas uma porta que você
    nunca permitiu, como 9999, deve dar timeout ou ser recusada).

    Entregável deste passo: a saída de ufw status verbose mostrando uma
    política de entrada negar-por-padrão com uma allowlist explícita e mínima —
    e uma tentativa de conexão falha a uma porta que você não abriu, provando
    que a negação realmente vale.

  5. Audite e desabilite serviços desnecessários

    Todo serviço em execução é uma vulnerabilidade em potencial, mesmo atrás de
    um firewall — um bug local de escalação de privilégio num daemon que você
    nunca usa ainda é um risco, e uma regra de firewall mal configurada depois
    pode expor acidentalmente algo que você esqueceu que estava rodando. A
    regra: se você não precisa, não rode.

    Faça o inventário do que está escutando e do que está habilitado

    # O que está de fato escutando num socket de rede agora
    sudo ss -tulpn
    
    # Quais serviços estão habilitados para iniciar no boot
    systemctl list-unit-files --type=service --state=enabled
    

    Numa imagem Ubuntu server mínima, você tipicamente vai ver coisas como
    cron, rsyslog e sshd — mantenha essas. Fique atento a qualquer coisa
    que você não instalou conscientemente: um agente de transporte de e-mail
    não usado (exim4, postfix), um serviço de impressão (cups), ou um
    banco de dados/serviço ligado a 0.0.0.0 que você nunca pretendeu expor
    além do localhost.

    Desabilite o que você não precisa

    # Exemplo: desabilita e para um serviço que você identificou como desnecessário
    sudo systemctl disable --now <nome-do-servico>
    
    # Confirme que sumiu de "enabled" e de "listening"
    systemctl is-enabled <nome-do-servico>     # deve imprimir "disabled" ou erro
    sudo ss -tulpn | grep <nome-do-servico>    # não deve imprimir nada
    

    Para qualquer coisa ligada a 0.0.0.0 que você precisa, mas só para uso
    local (ex.: um banco de dados que você só consulta da mesma máquina),
    religue-o para 127.0.0.1 na configuração dele em vez de depender só do
    firewall — isso é defesa em profundidade: mesmo que uma regra de firewall
    seja afrouxada por engano depois, o serviço ainda não vai aceitar conexões
    remotas.

    Checkpoint

    sudo ss -tulpn
    

    Toda linha na saída deve ser algo que você consegue nomear e justificar.
    Se você não consegue dizer por que um socket escutando existe, isso é a
    próxima coisa a investigar e provavelmente desabilitar.

    Entregável deste passo: uma saída de ss -tulpn onde todo socket
    escutando é justificado, mais a lista de serviços que você desabilitou e o
    motivo.

  6. Instale o fail2ban e verifique um banimento (submissão)

    O ufw limit desacelera a força bruta; o fail2ban bane ativamente um IP
    infrator após falhas repetidas, observando seus logs e atualizando o
    firewall por você. Esta é a última camada deste lab, e a que você vai provar
    de ponta a ponta.

    Instale e habilite o jail de SSH

    sudo apt-get install -y fail2ban
    
    sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
    [sshd]
    enabled  = true
    port     = 22
    filter   = sshd
    logpath  = /var/log/auth.log
    maxretry = 3
    findtime = 5m
    bantime  = 15m
    EOF
    
    sudo systemctl enable --now fail2ban
    sudo systemctl status fail2ban --no-pager
    

    Dispare um banimento de verdade

    Da sua máquina host, falhe deliberadamente o login SSH mais vezes que
    maxretry contra sua própria máquina descartável:

    for i in 1 2 3 4; do
      ssh -p 2222 -o PubkeyAuthentication=no -o PreferredAuthentications=password \
          -o ConnectTimeout=3 opslab@localhost 'echo nao-deveria-chegar' || true
    done
    

    Verifique que o banimento realmente entrou em vigor

    sudo fail2ban-client status sshd
    

    Você deve ver o IP da sua máquina (provavelmente 127.0.0.1 ou o endereço
    do gateway do Docker) listado em "Banned IP list". Confirme que uma
    tentativa de conexão subsequente é recusada de cara (connection
    refused/timeout, nem chega a pedir senha) até o bantime passar, ou desbaneie
    manualmente para continuar trabalhando:

    sudo fail2ban-client set sshd unbanip <o-ip>
    

    Critério de submissão

    Submeta quando tudo o que segue for verdade, cada item respaldado pela
    saída de comando que o comprova:

    1. Usuário sudo não-root — sudo whoami funciona como opslab a partir
      de um login novo (Passo 2).
    2. SSH só por chave, sem root — login por senha e login root ambos
      falham; login por chave do opslab funciona (Passo 3).
    3. Firewall negar-por-padrão — sudo ufw status verbose mostra deny (incoming) com uma allowlist explícita e mínima (Passo 4).
    4. Superfície de serviços mínima — saída de sudo ss -tulpn com todo
      socket escutando justificado, e a lista de serviços que você desabilitou
      (Passo 5).
    5. fail2ban funcionando — sudo fail2ban-client status sshd mostrando
      pelo menos um IP banido pelo seu próprio teste deliberado de login
      falho (este passo).

    Escreva uma nota curta sobre o que você adicionaria em seguida para uma
    máquina de produção (ex.: atualizações automáticas de segurança via
    unattended-upgrades, envio centralizado de logs, um agente de detecção de
    intrusão) — você não precisa implementar, só nomear a próxima camada.