Back-End

10 mar, 2017

Mitigando o antigo problema do PHP com o OPCache vazando dados confidenciais

Publicidade

Uma vulnerabilidade de segurança muito antiga foi corrigida no PHP em relação à maneira como ele lida com seus OPCaches em ambientes onde um único processo mestre compartilha vários pools PHP-FPM. Essa é a maneira mais comum de executar o PHP hoje e pode afetar você também.

A vulnerabilidade

O PHP tem um método para acelerar a natureza dinâmica do seu interpretador, chamado caching bytecode. O PHP é interpretado em cada carregamento de página, o que significa que o PHP é traduzido para bytecode, que o servidor entende e pode executar. Como a maioria das páginas PHP não muda a cada segundo, o PHP armazena em cache esse bytecode na memória e pode servi-lo como resposta em vez de ter de compilar (ou “interpretar”) os scripts PHP toda vez.

Em uma configuração padrão do PHP-FPM, a árvore de processo se parece com isto:

php-fpm: master process (/etc/php-fpm.conf)
\_ php-fpm: pool site1 (uid: 701)
\_ php-fpm: pool site2 (uid: 702)
\_ php-fpm: pool site3 (uid: 703)

Existe um processo mestre PHP-FPM único que é iniciado como o usuário root. Em seguida, gera pools FPM adicionais, que podem ser executados como seu próprio usuário, para servir sites. O PHP OPCache (esse cache bytecode) é mantido no processo mestre.

Então, aqui está o problema: o PHP não valida o userid quando ele pega um script de memória, armazenado em seu bytecode. O conceito de “memória compartilhada” em PHP significa que tudo é armazenado no mesmo segmento de memória, e incluir um arquivo apenas verifica se uma versão já existe em bytecode.

Se uma versão do script existir em bytecode, ela será exibida sem verificação adicional.

Se uma versão do script não existir em bytecode, o pool FPM (executado como um uid não privilegiado) tentará fazer a leitura a partir do disco, e o Linux impedirá leituras de arquivos aos quais o processo não tem acesso.

Essa é uma das principais razões pelas quais eu defendi a execução de vários mestres, em vez de vários pools durante anos.

A solução: atualizar o PHP e definir configurações adicionais

Depois de um período muito longo, este bug está agora resolvido e você pode corrigi-lo com configurações mestres adicionais.

$ cat /etc/php-fpm.conf
...
opcache.validate_permission (default "0")
       Leads OPcache to check file readability on each access to cached file.
       This directive should be enabled in shared hosting environment, when few
       users (PHP-FPM pools) reuse the common OPcache shared memory.

opcache.validate_root (default "0")
       This directive prevents file name collisions in different "chroot"
       environments. It should be enabled for sites that may serve requests in
       different "chroot" environments.

A introdução do opcache.validate_permission e do opcache.validate_root significa que agora você pode forçar o OPCache do PHP para também verificar as permissões do arquivo e forçar uma validação do caminho raiz do arquivo, para evitar que ambientes chrooted leiam arquivos uns dos outros (mais sobre isso no relatório de erro original).

Os valores padrão, entretanto, são inseguros.

Eu entendo por que eles são assim, para manter a compatibilidade com versões anteriores e evitar quebrar as alterações em versões menores, mas você tem que explicitamente habilitá-los para evitar esse comportamento.

Versões mínimas: PHP 5.6.29, 7.0.14, 7.1.0

Essa correção não recebeu muita atenção, e eu só notei isso depois que alguém postou na lista de discussão oss-security. Para mitigar isso corretamente, você precisará pelo menos de

Qualquer coisa menor do que 5.6 já está no fim da vida e não conseguirá fazer essa correção, mesmo que o OPCache tenha sido introduzido no PHP 5.5. Você precisará de uma 5.6 ou mais recente para mitigar isso.

Se você ainda roda o PHP 5.5 (o que muitos fazem) e quer estar seguro, sua melhor aposta é executar vários mestres PHP (um único mestre por pool) ou desativar completamente OPCache.

Escalonamento de privilégios locais indiretos para root

Esse tem sido um problema de longa data com o PHP que é realmente mais grave do que parece à primeira vista.

Se você conseguir comprometer um único site (o que, para a maioria dos CMSs em PHP que não são atualizados, não é muito difícil), essa memória compartilhada permite que você acesse todos os outros sites, se suas páginas tiverem sido armazenadas em cache no OPCache.

Você pode efetivamente usar esse buffer de memória compartilhada como uma maneira de passagem para ler arquivos de outros sites PHP, ler seus configs, obter acesso ao seu banco de dados e roubar todos os seus dados. Para fazer isso, você normalmente precisa de acesso root para um servidor, mas o OPCache do PHP oferece um atalho conveniente para realizar coisas semelhantes.

Tudo o que você precisa é do caminho para seus arquivos, que podem ser recuperados via  opcache_get_status() (se ativado, o que é por padrão) ou você pode adivinhar os caminhos, o que em ambientes de hospedagem compartilhada geralmente também não é difícil.

Isso realmente torna essa arquitetura de memória compartilhada quase tão má quanto uma vulnerabilidade de escalação de privilégios local, dependendo do que você deseja realizar. Se o seu objetivo é roubar dados de um servidor que normalmente requer privilégios de root, você conseguiu.

Se o seu objetivo é realmente obter root e executar scripts apenas de root (aka: bind em portas inferiores etc.), isso não será possível.

A parte boa é que agora há uma correção com as novas configurações do PHP. A má notícia é que você precisa atualizar para as versões mais recentes para obtê-la e ativá-la explicitamente.

Fontes adicionais

Para o seu prazer de leitura:

***

Mattias Geniar faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://ma.ttias.be/mitigating-phps-long-standing-issue-opcache-leaking-sensitive-data/.