Aikido

XSS no Roundcube combinado com a manipulação de cookies para obter acesso total à caixa de entrada

Escrito por
Jorian Woltjer

O Roundcube é o cliente de webmail de código aberto mais utilizado no mundo. Descobrimos recentemente uma vulnerabilidade perigosa na aplicação! Trata-se de um XSS armazenado que, combinado com uma técnica de troca de cookies, permite a um atacante obter acesso total à caixa de entrada da vítima e, a partir daí, a qualquer conta que utilize esse endereço de e-mail para autenticação ou recuperação de palavra-passe.

Descobrimos isto ao executar o nosso pentest de IA contra uma instância local do Roundcube. Todas as descobertas foram comunicadas de forma responsável à Nextcloud, os mantenedores do Roundcube, através do HackerOne (XSS divulgado em #3594137) e corrigidas na versão 1.6.14. 

Neste artigo, vamos explicar o que fizemos, como os nossos agentes descobriram a vulnerabilidade e como uma simples injeção de HTML pode comprometer totalmente a caixa de entrada de um utilizador.

O ponto de injeção

Todo o ataque tem um ponto de partida. Vejamos um caso que um dos nossos agentes selecionou para auditar.

O Roundcube lida com o conteúdo controlado pelo utilizador de várias formas. O corpo das mensagens de e-mail é a área mais rigorosamente analisada. Este conteúdo é submetido a uma limpeza exaustiva, uma vez que é apresentado em linha com o resto da aplicação.

Os anexos HTML são processados através de um ponto de extremidade separado em mail/get.php, com uma Política de Segurança de Conteúdo definida como script-src 'none' para bloquear a execução de JavaScript.

Um terceiro ponto de extremidade, mais oculto, trata dos anexos incorporados que ainda não foram enviados, visíveis temporariamente durante a redação de um e-mail. É este o ponto de extremidade que iremos analisar. A ação «display-attachment» trata deste tipo de pedidos:

class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload {
    ...
    public function run($args = []) {
        self::init();

        $rcmail = rcmail::get_instance();
        $file = $rcmail->get_uploaded_file(self::$file_id);

        self::display_uploaded_file($file);

A função self::display_uploaded_file() é onde a magia acontece. Como rcube_uploads.php Como se pode ver, este tipo de anexos é devolvido diretamente com o seu tipo de conteúdo e corpo originais, sem qualquer sanitização, sem ser submetido a sandboxing e sem que seja aplicada qualquer Política de Segurança de Conteúdo.

header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);

if (isset($file['data']) && is_string($file['data'])) {
    echo $file['data'];
} elseif (!empty($file['path'])) {
    readfile($file['path']);
}

Como é que chegamos a este ponto final, perguntar-se-á? Ao redigir um novo e-mail no Roundcube, pode anexar um ficheiro ao e-mail temporário, mais concretamente um ficheiro HTML. Vamos inserir nele algum conteúdo malicioso:

<script>alert(origin)</script>

Após o upload, ao clicar no anexo, abre-se uma janela pop-up que o apresenta utilizando a ação «get», protegida por uma CSP rigorosa. Este é o caminho seguro. O interessante é o que acontece quando se swap _action=get por _action=exibir-anexo:

Captura de ecrã da janela de composição do Roundcube com um ficheiro xss.html anexado. Uma janela pop-up mostra o anexo renderizado através da ação get, com uma área branca em branco onde o script seria executado, bloqueada pela Política de Segurança de Conteúdo.
Ao visualizar o anexo através da ação «get», este é apresentado numa janela pop-up isolada com uma política de segurança de conteúdo (CSP) rigorosa, bloqueando a execução de scripts.

Utilize o URL original desta página:

/?_task=mail&_frame=1&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=get&_extwin=1

Troca _action=get por _action=exibir-anexo e, ao eliminar alguns parâmetros desnecessários, obtém-se:

/?_task=mail&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=display-attachment

Este URL apresenta o mesmo conteúdo, mas sem o CSP! Assim, o JavaScript dentro do nosso HTML é executado, exibindo um alerta sobre a origem atual e confirmando o XSS:

A captura de ecrã mostra uma janela pop-up com o ataque, com o texto «mail.target.local:19002 diz http://mail.target.local:19002»
O ataque funciona!

Trata-se de um Self-XSS interessante, mas será que isto constitui realmente um problema? Se formos realistas, um utilizador comum não vai carregar a nossa carga XSS por conta própria e continuar a visualizá-la desta forma específica…

Olhando para attachment_upload.php, vais ver que um compilar_dados_ A chave de sessão deve estar definida para o utilizador atual, e apenas esse utilizador pode recuperar o anexo.

public static function init()
{
    self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
    self::$COMPOSE = null;
    self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;

    if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
        self::$COMPOSE = &$_SESSION[self::$SESSION_KEY];
    }

    if (!self::$COMPOSE) {
        exit('Invalid session var!');

Uma vez que se trata de uma ligação temporária associada apenas à sua sessão atual, não há forma de preparar uma carga maliciosa e fazer com que a vítima a acione ao iniciar sessão na conta do atacante, tal como vimos com Mailcow. Todo o roundcube_sessid O cookie teria de ser copiado para o computador da vítima. Outra tarefa que parece impossível.

Exploração de Self-XSS com Cookie Tossing

O Self-XSS que descobrimos parece, à primeira vista, impossível de explorar. O anexo está vinculado à sessão, pelo que não há forma de preparar uma carga útil para a vítima sem lhe fornecer também o cookie de sessão do atacante. Mas, na verdade, o problema ainda não acabou. É aí que entra o «cookie tossing».

Nos navegadores, os cookies têm algumas peculiaridades interessantes, uma das quais é a Domínio=atributo.

> Apenas o domínio atual pode ser definido como valor, ou um domínio de nível superior, a menos que se trate de um sufixo público. Ao definir o domínio, o cookie ficará disponível para esse domínio, bem como para todos os seus subdomínios.

Os cookies podem ser definidos não só para o domínio atual, mas também para um domínio pai. Um cookie definido por sub.example.com com Domínio=example.com fica disponível para todos os subdomínios sob example.com, incluindo coisas como outro.exemplo.com que não teve nada a ver com a sua configuração. Este tipo de ataque é chamado Lançamento de bolachas, onde um subdomínio grava cookies que outro subdomínio irá ler.

Isto significa que tudo o que precisamos para explorar a nossa vulnerabilidade é controlo sobre um subdomínio no mesmo domínio que o domínio Roundcube de destino. A partir daí, uma vulnerabilidade XSS distinta em algo como xss.target.local pode definir o document.cookie propriedade para gravar cookies com o Domínio=target.local atributo. Assim que esses cookies forem definidos, o navegador da vítima irá enviá-los para mail.target.local, onde o Roundcube carrega a sessão do atacante em vez da da vítima.

Essa sessão tem o anexo HTML malicioso pronto e à espera. Ao direcionar a vítima para o URL do anexo, a carga XSS é acionada no servidor de origem do Roundcube, no navegador da vítima, sem que seja necessária qualquer outra interação.

Em resumo, o que um atacante tem de fazer para explorar essa vulnerabilidade é:

  1. Iniciar sessão na sua própria conta, criar um novo e-mail e anexar um ficheiro HTML malicioso
  2. Copie o link para visualizar (exibir) o anexo e os cookies
  3. A partir de um subdomínio vulnerável a XSS, defina os valores dos cookies utilizando document.cookie com o Domínio=atributo que aponta para o domínio de destino.
  4. Redirecione a vítima para o link do anexo. O XSS é acionado na origem do Roundcube.

Eis como se apresenta, na prática, a carga útil do XSS de subdomínio, definindo os cookies de sessão e redirecionando a vítima para o URL do anexo

documento.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domínio=target.local'
location.href = 'http://mail.target.local/?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';

A partir do subdomínio XSS, o resto da exploração do Roundcube não requer mais nenhuma interação do utilizador. Toda a segurança do Roundcube depende agora de todos os subdomínios do mesmo site.

Captura de ecrã da instância local e da janela pop-up, proveniente do código de ataque

Acesso total

A janela pop-up de alerta na captura de ecrã acima confirma que está a ocorrer um ataque XSS na origem do Roundcube, mas, por si só, não demonstra um impacto real. Continua a haver um problema. Nesta altura, carregámos a sessão do atacante no navegador da vítima, pelo que qualquer ação realizada será atribuída à conta do atacante e não à da vítima. É ótimo para entregar a nossa carga maliciosa, mas não é tão bom para aceder a coisas a que normalmente não teríamos acesso.

Se olharmos para o navegador, os cookies não são, na verdade, substituído quando definimos um valor diferente Domínio=. Já foram enviadas as duas!

Cookie: roundcube_sessauth=VÍTIMA; roundcube_sessauth=ATACANTE

Quando ambos os cookies estão presentes, o servidor seleciona o do atacante, uma vez que este aparece em último lugar no cabeçalho. Na carga útil do XSS, queremos que sejam selecionados os cookies do atacante, enquanto em todos os outros pontos finais posteriores, queremos os cookies da vítima. Felizmente, os cookies têm outro atributo que resolve perfeitamente este problema: Caminho=.

Ao definir um caminho exclusivo como Path=/index.php/xss, que continua a redirecionar para a página inicial, os cookies irão só podem ser enviadas quando esse caminho corresponder ao destino da solicitação. Assim, para as nossas solicitações:

  1. /index.php/xss envia roundcube_sessauth=VÍTIMA; roundcube_sessauth=ATACANTE -> a carga útil do atacante é devolvida
  2. / envia roundcube_sessauth=VÍTIMA -> os e-mails da vítima são devolvidos

Basta alterarmos o código JavaScript para definir este novo atributo e navegarmos até /index.php/xss posteriormente, para garantir que os cookies do atacante sejam enviados nesta solicitação da nossa carga útil, mas, depois disso, o nosso XSS fica livre para aceder à conta da vítima.

documento.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domínio=target.local; Caminho=/index.php/xss'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domínio=target.local; Caminho=/index.php/xss'
location.href = 'http://mail.target.local/index.php/xss?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';

Nas DevTools, podemos ver como os cookies duplicados estão agora configurados:

Captura de ecrã do separador «Aplicação» das Ferramentas de Programador do Chrome, mostrando quatro cookies para mail.target.local. Dois têm o âmbito do caminho /index.php/xss em .target.local (os cookies do atacante) e dois têm o âmbito do caminho raiz em mail.target.local (os cookies da vítima).

Embora as condições necessárias para explorar esta vulnerabilidade (conta no Roundcube + XSS no subdomínio) sejam um pouco complexas, o impacto potencial de uma exploração bem-sucedida é enorme.

O e-mail é a forma de autenticação que inspira maior confiança, uma vez que muitos sites recorrem a «links mágicos» para o início de sessão ou a recuperação de palavras-passe. Quando um invasor tem acesso aos seus e-mails, pode desencadear este tipo de redefinições de palavra-passe em todos os sites aos quais tenha associado esse endereço de e-mail. Depois, pode ler o e-mail que esse serviço envia para confirmação e obter acesso a muitas mais contas.

Remediação

Atualize o Roundcube para a versão 1.6.14 ou posterior (1.6.x), ou para a versão 1.5.14 ou posterior (1.5.x LTS). Todas as vulnerabilidades relatadas foram corrigidas nesta versão. Se utilizar Aikido, as instâncias vulneráveis do Roundcube serão automaticamente sinalizadas no seu feed de monitorização de superfície como uma detecção de risco médio.

Aikido não está no Aikido ? Crie uma conta gratuita para começar, sem necessidade de cartão de crédito.

Conclusão

Embora à primeira vista parecesse uma vulnerabilidade XSS trivial, a sua exploração exigia um pouco mais de conhecimento sobre cookies e sessões. Os subdomínios do mesmo site recebem frequentemente do navegador um pouco mais de permissões do que sites completamente distintos, o que constitui mais um motivo para garantir que todos os seus recursos estão seguros.

Esta é uma tarefa difícil, mas, como se pode ver aqui, Aikido consegue realizar testes de penetração de forma autónoma em aplicações web para identificar essas vulnerabilidades em toda a sua infraestrutura.

Os responsáveis pela manutenção do Roundcube corregiu esta vulnerabilidade na versão 1.6.14, adicionando um script-src 'none' Política de Segurança de Conteúdo, tal como já acontecia com os restantes anexos. Isto torna impossível a execução de JavaScript ao devolver HTML arbitrário.

Bónus: Injeção de CRLF no IMAP através de CSRF

Durante o mesmo teste de penetração, outro agente identificou uma segunda vulnerabilidade que considerámos particularmente interessante. Após a sua comunicação, verificou-se que se tratava de uma duplicado que a Equipa de Investigação de Segurança da Martila também tinha identificado. Ainda assim, considerámos que valia a pena explicar brevemente os detalhes desta vulnerabilidade aqui.

O /?_task=mail&_action=search o endpoint passa o valor fornecido pelo cliente _filtro parâmetro diretamente no IMAP PESQUISAR comando em rcube_imap_generic.php:

if (!empty($criteria)) {
    $params .= ($params ? ' ' : '') . $criteria;
} else {
    $params .= 'ALL';
}
[$code, $response] = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', [$params]);

Embora a função pareça separar o comando dos seus argumentos, a implementação de executar() simplesmente concatena-os sem qualquer tratamento de segurança:

foreach ($arguments as $arg) {
    $query .= ' ' . self::r_implode($arg);
}

Ao inserir um Carriage Return & Line Feed (CRLF, %0D%0A) caracteres, um atacante pode contornar os parâmetros SEARCH e injetar comandos IMAP adicionais na sessão IMAP do utilizador autenticado.

Com isto, não só pode pesquisar e-mails como também criar pastas, mover e-mails ou eliminar toda a caixa de entrada, uma vez que se trata de comandos IMAP básicos.

Segue-se um exemplo de filtro definido para ALL%0D%0AX007%20CREATE%20EvilFolder:

http://mail.target.tld/?_task=mail&_action=search&_interval=&_q=imap-inject-test&_headers=subject%2Cfrom&_layout=widescreen&_filter=ALL%0D%0AX007%20CREATE%20EvilFolder&_scope=base&_mbox=INBOX&_remote=1 

Ao aceder a essa página, são enviados os seguintes dados para o IMAP, com o comando CREATE injetado que é executado após a pesquisa:

X006 PROCURAR EM TODOS
X007 CRIAR EvilFolder

O efeito disso pode então ser observado na interface do Roundcube:

A interface do Roundcube no separador «Pastas», com «EvilFolder» na parte inferior

Como se trata de uma simples solicitação GET, basta aceder ao link acima para executar os comandos. Assim, um único clique num link poderia eliminar permanentemente todos os e-mails (X001 UID LOJA 1:* SINALIZADORES \Excluído seguido de X002 ELIMINAR), o que resultou numa grande perda de dados.

Esta vulnerabilidade está agora corrigido ao remover \r\n caracteres das consultas de pesquisa.

Compartilhar:

https://www.aikido.dev/blog/roundcube-xss-cookie-tossing

Comece hoje, gratuitamente.

Comece Gratuitamente
Não é necessário cc

Assine para receber notícias sobre ameaças.

4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.