Artigo

CVE-2024-29041 só funciona no Safari?

Indo a fundo no código-fonte do Chromium e do Firefox para entender por que o payload enquanto o Safari navega para o host malicioso.

m4z4r0p3, do nosso grupo de pesquisas de CVEs da Sunsec, me apontou um problema: ao testar o exploit do artigo anterior, ele notou que o payload não se comportava da mesma forma em todos os browsers. No Chrome e Firefox, a navegação para o host malicioso não acontecia. No Safari, sim.

Fomos investigar a fundo e encontramos os pontos exatos nos repositórios open source onde isso é implementado.

O ponto de partida

O artigo anterior mostrou que o Express envia o header:

Location: http://google.com%5C@evil.com

A partir deste momento, cada browser tem um pipeline diferente para processar esse header. Mas há algo que todos fazem igual antes de divergir.

O que é igual em todos os browsers

Todos os browsers decodificam %5C\. Ficam com:

http://google.com\@evil.com

É a partir daqui que os caminhos divergem.

Chrome e Firefox: normalização antes de interpretar

Chrome e Firefox implementam o WHATWG URL Standard de forma muito rigorosa no processamento de redirects. O pipeline deles é:

  1. Recebem http://google.com\@evil.com
  2. Normalizam \/ durante o parse, antes de interpretar a estrutura
  3. Ficam com http://google.com/@evil.com
  4. Agora a estrutura é clara: google.com é o host, @evil.com é parte do path
  5. Navegam para google.com

O ponto-chave: eles normalizam no momento em que processam o header Location, antes de decidir quem é o host.

Safari: interpretação antes de normalizar

O Safari também implementa WHATWG, mas com uma diferença sutil na ordem em que aplica as normalizações durante redirects:

  1. Recebe http://google.com\@evil.com
  2. Interpreta a estrutura antes de normalizar completamente o \
  3. Nesse momento, \ ainda não foi tratado como /
  4. google.com como userinfo (antes do @) e evil.com como host
  5. Normaliza o \/ depois — mas o host já foi decidido
  6. Navega para evil.com ☠️
BrowserOrdem das operaçõesHost decidido
Chrome/FirefoxNormaliza \/ → interpreta estruturagoogle.com
SafariInterpreta estrutura → normaliza \evil.com

Nenhum está “errado”, a spec não cobre esse caso de borda explicitamente para o fluxo de redirect HTTP. O Safari é simplesmente menos preventivo na sanitização.

Evidência no código-fonte

Encontramos os pontos exatos onde isso é implementado nos repositórios open source do Chrome e do Firefox.

Chromium — url/url_parse_internal.h

O Chromium define a função IsSlashOrBackslash() com um comentário direto sobre a motivação: é a própria URL Standard que diz tratar \ como / em URLs especiais.

// A helper function to handle a URL separator, which is '/' or '\'.
//
// The motivation: There are many condition checks in URL Standard like the
// following:
//
// > If url is special and c is U+002F (/) or U+005C (\), ...
inline bool IsSlashOrBackslash(char16_t ch) {
  return ch == '/' || ch == '\\';
}

Arquivo: url/url_parse_internal.h

Essa função é chamada durante o parse, antes de qualquer decisão sobre o host. O \ entra como equivalente a / desde o início do processamento.

Chromium — url/url_canon_host.cc

No arquivo url_canon_host.cc, existe uma tabela kHostCharacterTable onde \ é marcado como kForbiddenHost, um forbidden host code point segundo a spec WHATWG:

kForbiddenHost, // '\\'   ← \ é forbidden host code point

Arquivo: url/url_canon_host.cc

Quando o Chromium encontra \ durante o parse do host, ele rejeita o caractere imediatamente. A URL google.com\@evil.com nunca chega a ser interpretada com evil.com como host.

Firefox — rust-url/url/src/parser.rs

O Firefox usa a crate servo/rust-url como seu parser WHATWG. Ela define explicitamente que \ é tratado como separador de path em URLs especiais:

// The backslash (\) character is treated as a path separator in special URLs
// so it needs to be additionally escaped in that case.
pub(crate) const SPECIAL_PATH_SEGMENT: &AsciiSet = &PATH_SEGMENT.add(b'\\');

E na função parse_path_start():

if scheme_type.is_special() {
    if maybe_c == Some('\\') {
        // If c is U+005C (\), validation error.
        self.log_violation(SyntaxViolation::Backslash);
    }
    if !self.serialization.ends_with('/') {
        self.serialization.push('/');
        if maybe_c == Some('/') || maybe_c == Some('\\') {
            return self.parse_path(...);
        }
    }
}

Arquivo: url/src/parser.rs — servo/rust-url

O \ é normalizado para / durante o parse do path, antes que qualquer lógica de host possa ser confundida.

Por que isso é difícil de perceber

O artigo anterior mostrou que o new URL() do Node.js (que segue WHATWG) já resolve corretamente para evil.com:

new URL('http://google.com\\@evil.com').hostname
// → 'evil.com'

Ou seja, o parser WHATWG puro concorda com o Safari. Chrome e Firefox fazem uma etapa extra de sanitização especificamente no fluxo de redirect HTTP, um comportamento defensivo adicional, não parte explícita da spec.

O CVE-2024-29041 vai funcionar como um exploit específico para Safari, o que não diminui sua seriedade, especialmente em contextos de phishing onde o atacante pode direcionar vítimas com qualquer browser, mas é importante saber ao testar e reportar, visto que isso pode mudar e muito o risco e impacto.