Tenho um servidor Apache/2.4.6 (CentOS) com vários subdomínios como ServerAlias no Apache VirtualHost.
algo como:
<VirtualHost *:443>
ServerName mydomain.com
ServerAlias a.mydomain.com
ServerAlias b.mydomain.com
Cada empresa cliente deve acessar através de seu subdomínio e há bancos de dados diferentes para cada empresa cliente, por questões de segurança, há uma separação de dados.
Fui alertado por um especialista em segurança cibernética de que há uma vulnerabilidade em que um usuário de um subdomínio 'a.mydomain.com' pode acessar outro subdomínio 'b.mydomain.com' adicionando um cabeçalho de Host às chamadas do cliente para o servidor web.
No começo, tentei obter as informações em PHP, mas falhei, o PHP não obtém as informações dos cabeçalhos. Então, mudei para procurar uma solução para essa situação no nível do servidor web - Apache.
Quero detectar e rejeitar quando um usuário malicioso tenta enganar o servidor e enviar a solicitação para outro subdomínio usando um Host Header. Neste exemplo, o usuário deve ser atendido por a.mydomain.com e não b.mydomain.com:
curl 'https://a.mydomain.com/users/login' \
-H 'Host: b.mydomain.com' \
--data-raw $'{"email":"[email protected]","password":"*****"}'
Uma chamada normal do aplicativo do lado do cliente se parece com isto:
curl 'https://a.mydomain.com/users/login' \
--data-raw $'{"email":"[email protected]","password":"*****"}'
Eu tentei RequestHeader unset host
, mas não funciona como eu esperava.
Minha expectativa era que se o usuário malicioso enviasse um cabeçalho "Host", o servidor deveria ignorá-lo. Isso faria com que ambas as chamadas culr acima fossem efetivamente as mesmas.
Acho que o que acontece é que o Apache está usando a URL na chamada, mas se houver um cabeçalho "Host", ele tem precedência e é isso que é usado e o domínio original da URL é descartado.
Se for esse o caso, então RequestHeader unset host
não envie nenhum host para meu código PHP, o que faz com que meu código seja interrompido, pois ele precisa saber qual empresa cliente o está chamando.
Isso não faz sentido, porque o
Host
cabeçalho é como o Apache conhece a URL em primeiro lugar.Requisições HTTP regulares não fornecem a URL literal para um servidor web. Se você executar curl com a
-v
opção, verá que a requisição se parece com , ou seja, apenas o caminho (e a string de consulta) está presente – nãoGET /users/login
é como você provavelmente imagina. (Proxies usam o último, requisições normais não.)GET https://a.mydomain.com/users/login
Então, para onde vai a parte do nome do host da URL quando uma solicitação HTTP é feita? O único lugar para onde vai, no que diz respeito ao HTTP, é o
Host:
cabeçalho. Portanto, é impossível que o cabeçalho seja "diferente da URL da solicitação", porque é a URL da solicitação.Em outras palavras, alterar o cabeçalho do Host não é realmente "enganar o servidor" mais do que apenas alterar a URL; apenas fazer isso
https://b.mydomain.com/users/login
teria o mesmo efeito.Pode haver algum outro problema sobre o qual o "especialista" estava tentando alertá-lo (por exemplo, talvez ele estivesse falando sobre uma solicitação que tenta enviar dois cabeçalhos de Host, contra os quais acredito que tanto o Apache quanto o PHP já se protegem; ou talvez ele quisesse dizer que o servidor aceitou TLS SNI e HTTP Host incompatíveis, o que também não é um grande problema em si), mas você precisará pedir esclarecimentos a ele.
Meu palpite é que seu aplicativo não isola sessões PHP (ou tokens de autenticação) por domínio, de modo que se um usuário fizer login e receber um cookie de sessão (ou token de acesso) para o domínio A, mas tentar enviar o mesmo token para o domínio B, o aplicativo o aceitará como autenticação válida para o domínio B. Isso seria um grande problema de segurança, mas nada a ver com falsificar o cabeçalho do Host como tal.
Agora, o PHP sabe para qual domínio ele foi chamado (
$_SERVER["SERVER_NAME"]
e/ou$_SERVER["HTTP_HOST"]
— o primeiro vem do Apache, o último é literalmente oHost:
cabeçalho; o PHP, na verdade, recebe todos os cabeçalhos por meioHTTP_*
de campos), então, se eu acertei, pode ser que esses campos precisem ser armazenados e comparados com a sessão do PHP (ou com o público de qualquer JWT que você esteja emitindo, ou qualquer outra coisa que você use para autenticação).Mas, novamente, isso é apenas um palpite, e você deve pedir ao especialista para lhe dar uma explicação mais específica sobre o que eles realmente queriam dizer.
Você está entendendo mal a vulnerabilidade aqui, tomando como exemplo o post anterior:
Isso autentica no servidor via SNI como
a.mydomain.com
, mas então envia o cabeçalho do hostb.mydomain.com
.Por padrão, o Apache detecta isso, registra um erro no arquivo de log e retorna um erro 400.
Veja também: https://security.stackexchange.com/questions/134021/what-kind-of-attack-is-prevented-by-apache2s-error-code-ah02032-hostname-prov
O Apache expõe os resultados do SNI detectado dentro da variável
SSL_TLS_SNI
Note que você está usando
ServerAlias
em vez de fazer blocos separados por servidor. Se o SNI informar que ele se conectoua.example.com
e então enviar um cabeçalho de Host deb.example.com
, porque da perspectiva do apache, é o mesmo servidor que permite a solicitação.Você quer dividir cada um dos seus servidores para garantir que o apache detecte a incompatibilidade do SNI e do cabeçalho do host. Você pode usar um
Include
bloco para carregar um arquivo de configuração comum para não ter que se repetir