Sitelet https://github.com/python/cpython/issues/102153#issuecomment-1447921678
Skip to content

urllib.parse space handling CVE-2023-24329 appears unfixed #102153

Description

@AdrianBunk

Activity

  1. ned-deily commented on Feb 23, 2023

    @ned-deily
    Member
  2. pablogsal commented on Feb 24, 2023

    @pablogsal
    Member

    The backport was merged here #99446 no?

  3. AdrianBunk commented on Feb 24, 2023

    @AdrianBunk
    Author

    @pablogsal #99446 is a backport of #99421 that does not seem to fix CVE-2023-24329:

    $ cat test.py 
    import urllib.request
    from urllib.parse import urlparse
    def safeURLOpener(inputLink):
        block_host = ["instagram.com", "youtube.com", "tiktok.com", "example.com"]
        input_hostname = urlparse(inputLink).hostname
        if input_hostname in block_host:
            print("input hostname is forbidden")
            return
        target = urllib.request.urlopen(inputLink)
        content = target.read()
        print(content)
    
    safeURLOpener("https://example.com")
    safeURLOpener(" https://example.com")  # CVE-2023-24329
    safeURLOpener("+https://example.com")  # 99421
    $ python3.10 test.py 
    input hostname is forbidden
    b'<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset="utf-8" />\n    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initial-scale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 2em;\n        background-color: #fdfdff;\n        border-radius: 0.5em;\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        div {\n            margin: 0 auto;\n            width: auto;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is for use in illustrative examples in documents. You may use this\n    domain in literature without prior coordination or asking for permission.</p>\n    <p><a href="/sitelet?url=https%3A%2F%2Fwww.iana.org%2Fdomains%2Fexample">More information...</a></p>\n</div>\n</body>\n</html>\n'
    input hostname is forbidden
    $ python3.11 test.py 
    input hostname is forbidden
    b'<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset="utf-8" />\n    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initial-scale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 2em;\n        background-color: #fdfdff;\n        border-radius: 0.5em;\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        div {\n            margin: 0 auto;\n            width: auto;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is for use in illustrative examples in documents. You may use this\n    domain in literature without prior coordination or asking for permission.</p>\n    <p><a href="/sitelet?url=https%3A%2F%2Fwww.iana.org%2Fdomains%2Fexample">More information...</a></p>\n</div>\n</body>\n</html>\n'
    Traceback (most recent call last):
      File "/tmp/test.py", line 15, in <module>
        safeURLOpener("+https://example.com")  # 99421
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/tmp/test.py", line 9, in safeURLOpener
        target = urllib.request.urlopen(inputLink)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/urllib/request.py", line 216, in urlopen
        return opener.open(url, data, timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/urllib/request.py", line 519, in open
        response = self._open(req, data)
                   ^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/urllib/request.py", line 541, in _open
        return self._call_chain(self.handle_open, 'unknown',
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/urllib/request.py", line 496, in _call_chain
        result = func(*args)
                 ^^^^^^^^^^^
      File "/usr/lib/python3.11/urllib/request.py", line 1419, in unknown_open
        raise URLError('unknown url type: %s' % type)
    urllib.error.URLError: <urlopen error unknown url type: +https>
    $
    
  4. pablogsal commented on Feb 24, 2023

    @pablogsal
    Member
  5. self-assigned this
    on Feb 24, 2023
  6. arhadthedev commented on Feb 27, 2023

    @arhadthedev
    Member
  7. CharlieZhao95 commented on Feb 28, 2023

    @CharlieZhao95
    Contributor

    BTW, does this patch (CVE-2023-24329) require a backport to 3.10, 3.9 and older branches?

    I noticed that the bug is currently only backported to the 3.11 branch, but it actually affects all versions prior to 3.11.

  8. RSAlderman commented on Feb 28, 2023

    @RSAlderman
  9. CharlesBryant-G commented on Feb 28, 2023

    @CharlesBryant-G

    Maybe it's worth taking a step back and looking at the problem in a wider context.

    In the PoC, the vulnerability arises not because parse() returns the wrong answer, but because it interprets the url differently from urlopen(). If they were both wrong in the same way it would be harmless. Why is there more than one piece of code which parses URLs? The DRY principle should apply.

    Closely related, note that urlparse() does not have a vulnerability at all - any vulnerability is in code which relies on it and does so in a way in which it creates a vulnerability. In the PoC, the vulnerability is in the code for safeURLOpener().

    The way in which urlparse() is implemented is fragile and bug prone. As a general principle, parsing code should not look ahead for known delimiters, it should systematically work from the start, advancing over characters tested to be legitimate. So
    urlparse('example.com@!$%^&*()_+-={}[]:;"\\|?query#frag')
    should stop parsing at the '%' as that is not a legal character when not followed by two hex digits. It may return that "example.com@!$" is the path and there are extra characters after the URL (this style of parsing is often convenient when parsing items which may contain things to be parsed), or report failure due to an invalid URL. Instead, an early stage of processing skips ahead to the '?' and '#', so it claims there is a path, query, and fragment. While it could then validate these pieces and realise that the path is invalid, this can be forgotten and makes it unnecessarily difficult and dangerous to make the parser accept a valid URL followed by other characters (because it would need to reliably undo any parsing of anything past the valid part).

  10. changed the title [-]Is CVE-2023-24329 still unfixed in 3.11.2?[/-] [+]urllib.parse CVE-2023-24329 appears unfixed[/+] on Mar 1, 2023
  11. gpshead commented on Mar 1, 2023

    @gpshead
    Member

    We will backport something that makes sense if we determine this is a security issue, that's why I duped the other issue here. Backporting the existing commit further does not make sense to me until the leading space issue, if present as reported here, is resolved. (I haven't taken the time to look. this is not an emergency)

  12. xiaoge1001 commented on Mar 6, 2023

    @xiaoge1001
    >>> from urllib.parse import urlparse
    >>> urlparse(" https://example.com")
    ParseResult(scheme='', netloc='', path=' https://example.com', params='', query='', fragment='')

    I tested it and the problem doesn't seem to be fixed. I execute urlparse(" https://example.com"), the output before and after merging #99421 is the same.

  13. xiaoge1001 commented on Mar 6, 2023

    @xiaoge1001

    CVE-2023-24329 says that supplying a URL that starts with blank characters is bad.

    If a URL-scheme is " https", it will jump out of the loop in the following code:

    if c not in scheme_chars:

    After #99421 is merged, it will exit early:

    if i > 0 and url[0].isascii() and url[0].isalpha():

    The code in line 468 is not executed before and after the modification, the subsequent code execution will not change:
    image

    when input a URL that starts with blank characters,#99421 doesn't seem to have no effect.

  14. 78 remaining items

  15. added a commit that references this issue on Apr 23, 2025
  16. added a commit that references this issue on Jul 4, 2025
  17. added a commit that references this issue on Aug 12, 2025
  18. added a commit that references this issue on Feb 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errortype-securityA security issue

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions