Skip to content

Commit 3ca1a90

Browse files
Reject URLs with text before bracket in host (#1654)
1 parent 2f180d1 commit 3ca1a90

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

CHANGES/1654.bugfix.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed a parsing issue where URLs containing text before an opening bracket
2+
in the host component (e.g. ``http://127.0.0.1[aa::ff]``) were silently accepted
3+
instead of being rejected as strictly invalid per RFC 3986
4+
-- by :user:`rodrigobnogueira`.

tests/test_url.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,13 +344,11 @@ def test_ipv6_missing_right_bracket() -> None:
344344
"http://[]/",
345345
"http://[1]/",
346346
"http://[127.0.0.1]/",
347-
"http://]1dec:0:0:0::1[/",
348347
),
349348
ids=(
350349
"empty-IPv6-like-URL",
351350
"no-colons-in-IPv6",
352351
"IPv4-inside-brackets",
353-
"brackets-in-reversed-order",
354352
),
355353
)
356354
def test_ipv6_invalid_url(url: str) -> None:
@@ -360,6 +358,33 @@ def test_ipv6_invalid_url(url: str) -> None:
360358
URL(url)
361359

362360

361+
def test_ipv6_brackets_in_reversed_order() -> None:
362+
with pytest.raises(ValueError, match="Invalid IPv6 URL"):
363+
URL("http://]1dec:0:0:0::1[/")
364+
365+
366+
@pytest.mark.parametrize(
367+
"url",
368+
(
369+
"http://127.0.0.1[aa::ff]",
370+
"http://127.0.0.1[aa::ff]/",
371+
"http://127.0.0.1[aa::ff]:8080/",
372+
"http://[email protected][aa::ff]/",
373+
"http://example.com[::1]/",
374+
),
375+
ids=(
376+
"ipv4-before-bracket",
377+
"ipv4-before-bracket-with-path",
378+
"ipv4-before-bracket-with-port",
379+
"userinfo-ipv4-before-bracket",
380+
"hostname-before-bracket",
381+
),
382+
)
383+
def test_host_with_text_before_bracket_is_invalid(url: str) -> None:
384+
with pytest.raises(ValueError, match="Invalid IPv6 URL"):
385+
URL(url)
386+
387+
363388
def test_ipfuture_brackets_not_allowed() -> None:
364389
with pytest.raises(ValueError, match="IPvFuture address is invalid"):
365390
URL("http://[v10]/")

yarl/_parse.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ def split_url(url: str) -> SplitURLType:
6464
):
6565
raise ValueError("Invalid IPv6 URL")
6666
if has_left_bracket:
67+
# Per RFC 3986, brackets are only valid at the START of the host
68+
# for IP-literal addresses. Text before '[' (e.g. '127.0.0.1[::1]')
69+
# is invalid and must be rejected to prevent SSRF bypasses.
70+
hostinfo = netloc.rpartition("@")[2]
71+
if hostinfo[0] != "[":
72+
raise ValueError("Invalid IPv6 URL")
6773
bracketed_host = netloc.partition("[")[2].partition("]")[0]
6874
# Valid bracketed hosts are defined in
6975
# https://www.rfc-editor.org/rfc/rfc3986#page-49

0 commit comments

Comments
 (0)