Article

CVE-2024-29041 only works on Safari?

Digging into the Chromium and Firefox source code to understand why the payload navigates to the malicious host on Safari but not on other browsers.

m4z4r0p3, from our CVE research group at Sunsec, pointed me to a problem: while testing the exploit from the previous article, he noticed the payload didn’t behave the same way across all browsers. In Chrome and Firefox, the navigation to the malicious host never happened. In Safari, it did.

We went to investigate and found the exact points in the open-source repositories where this is implemented.

The starting point

The previous article showed that Express sends the header:

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

From this point, each browser has a different pipeline for processing that header. But there is something every browser does the same before they diverge.

What all browsers do the same

Every browser decodes %5C\. They all end up with:

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

This is where the pipelines diverge.

Chrome and Firefox: normalize before interpreting

Chrome and Firefox implement the WHATWG URL Standard very strictly when processing redirects. Their pipeline:

  1. Receive http://google.com\@evil.com
  2. Normalize \/ during parsing, before interpreting the URL structure
  3. End up with http://google.com/@evil.com
  4. Structure is now unambiguous: google.com is the host, @evil.com is part of the path
  5. Navigate to google.com

The key point: they normalize at the moment they process the Location header, before deciding who the host is.

Safari: interpret before normalizing

Safari also implements WHATWG, but with a subtle difference in the order in which it applies normalizations during redirects:

  1. Receives http://google.com\@evil.com
  2. Interprets the URL structure before fully normalizing the \
  3. At this point, \ has not yet been treated as /
  4. Sees google.com as userinfo (before the @) and evil.com as the host
  5. Normalizes \/ afterwards — but the host has already been decided
  6. Navigates to evil.com ☠️
BrowserOrder of operationsHost decided
Chrome/FirefoxNormalize \/ → interpret structuregoogle.com
SafariInterpret structure → normalize \evil.com

Neither is “wrong”, the spec does not explicitly cover this edge case for the HTTP redirect flow. Safari is simply less aggressive in its preventive sanitization.

Evidence in the source code

We found the exact points where this is implemented in the open-source repositories of Chrome and Firefox.

Chromium — url/url_parse_internal.h

Chromium defines the IsSlashOrBackslash() function with a direct comment about its motivation: the URL Standard itself says to treat \ as / in special URLs.

// 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 == '\\';
}

File: url/url_parse_internal.h

This function is called during parsing, before any decision about the host is made. The \ is equivalent to / from the very start of processing.

Chromium — url/url_canon_host.cc

In url_canon_host.cc, there is a kHostCharacterTable where \ is marked as kForbiddenHost, a forbidden host code point per the WHATWG spec:

kForbiddenHost, // '\\'   ← \ is a forbidden host code point

File: url/url_canon_host.cc

When Chromium encounters \ during host parsing, it rejects the character immediately. The URL google.com\@evil.com never gets a chance to be interpreted with evil.com as the host.

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

Firefox uses the servo/rust-url crate as its WHATWG parser. It explicitly defines that \ is treated as a path separator in special URLs:

// 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'\\');

And in the parse_path_start() function:

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(...);
        }
    }
}

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

The \ is normalized to / during path parsing, before any host logic can be confused.

Why this is hard to notice

The previous article showed that Node.js’s new URL() (which follows WHATWG) already resolves to evil.com:

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

That means the pure WHATWG parser agrees with Safari. Chrome and Firefox apply an extra sanitization step specifically in the HTTP redirect flow, a defensive behavior on their part, not an explicit requirement of the spec.

CVE-2024-29041 is, in practice, a Safari-specific exploit — which doesn’t diminish its severity, especially in phishing scenarios where an attacker can direct victims using any browser, but it is important to know when testing and reporting, since this can significantly change the risk and impact assessment.