diff --git a/src/browser/URL.zig b/src/browser/URL.zig index c3bfbde7..620e63a2 100644 --- a/src/browser/URL.zig +++ b/src/browser/URL.zig @@ -883,8 +883,9 @@ fn parseAuthority(raw: []const u8) ?AuthorityInfo { const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null; const authority_start = scheme_end + 3; - // Find end of authority FIRST (start of path/query/fragment or end of string) - const authority_end = if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#")) |end| + // Find end of authority FIRST (start of path/query/fragment, + // a NUL/CR/LF/TAB, or end of string). + const authority_end = if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#\x00\r\n\t")) |end| authority_start + end else raw.len; @@ -1579,6 +1580,12 @@ test "URL: getHost" { try testing.expectEqualSlices(u8, "evil.example.com", getHost("http://evil.example.com/@victim.example.com/")); try testing.expectEqualSlices(u8, "evil.example.com", getHost("https://evil.example.com/path/@victim.example.com")); + try testing.expectEqual("evil.example.com:8521", getHost("http://evil.example.com:8521\x00@victim.example.com:8520/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\x00@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\r@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\n@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\t@victim.example.com/")); + // IPv6 addresses try testing.expectEqualSlices(u8, "[::1]:8080", getHost("http://[::1]:8080/path")); try testing.expectEqualSlices(u8, "[::1]", getHost("http://[::1]/path")); @@ -1669,6 +1676,17 @@ test "URL: getOrigin" { .{ .url = "https://evil.example.com/path/@victim.example.com/steal", .expected = "https://evil.example.com" }, .{ .url = "http://evil.example.com/@victim.example.com:443/", .expected = "http://evil.example.com" }, + // SECURITY: Null byte injection. + .{ .url = "http://attacker:8521\x00@victim:8520/", .expected = "http://attacker:8521" }, + .{ .url = "http://attacker.com\x00@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com/\x00@victim.com/", .expected = "http://attacker.com" }, + + // SECURITY: CR / LF / TAB are stripped by the WHATWG URL parser, so a + // userinfo "@" hidden behind one must not change the origin here either. + .{ .url = "http://attacker.com\r@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com\n@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com\t@victim.com/", .expected = "http://attacker.com" }, + // @ in query/fragment must also not affect origin .{ .url = "https://example.com/path?user=foo@bar.com", .expected = "https://example.com" }, .{ .url = "https://example.com/path#user@host", .expected = "https://example.com" },