Visitar URL original
[security] CVE-2021-28861: http.server: Open Redirection if the URL path starts with // · Issue #87389 · python/cpython · GitHub
Skip to content

[security] CVE-2021-28861: http.server: Open Redirection if the URL path starts with // #87389

Description

@hamzaavvan
BPO 43223
Nosy @pfmoore, @vstinner, @tiran, @tjguk, @ned-deily, @ambv, @zware, @zooba, @hamzaavvan
PRs
  • bpo-43223: [SECURITY] Patched Open Redirection In SimpleHTTPServer Module #24848
  • Files
  • Capture.PNG
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2021-02-14.11:42:36.898>
    labels = ['type-security', 'deferred-blocker', '3.8', '3.9', '3.10', '3.11', '3.7', 'library']
    title = '[security] http.server: Open Redirection if the URL path starts with //'
    updated_at = <Date 2021-05-23.08:10:57.234>
    user = 'https://github.com/hamzaavvan'

    bugs.python.org fields:

    activity = <Date 2021-05-23.08:10:57.234>
    actor = 'hamzaavvan'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2021-02-14.11:42:36.898>
    creator = 'hamzaavvan'
    dependencies = []
    files = ['49808']
    hgrepos = ['404']
    issue_num = 43223
    keywords = ['patch']
    message_count = 5.0
    messages = ['386945', '387193', '387284', '390047', '394193']
    nosy_count = 9.0
    nosy_names = ['paul.moore', 'vstinner', 'christian.heimes', 'tim.golden', 'ned.deily', 'lukasz.langa', 'zach.ware', 'steve.dower', 'hamzaavvan']
    pr_nums = ['24848']
    priority = 'deferred blocker'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'security'
    url = 'https://bugs.python.org/issue43223'
    versions = ['Python 3.6', 'Python 3.7', 'Python 3.8', 'Python 3.9', 'Python 3.10', 'Python 3.11']

    Linked PRs

    Activity

    1. hamzaavvan commented on Feb 14, 2021

      hamzaavvanmannequin
      MannequinAuthor

      The provided version of python distros 3.8.7 and 3.7.4 are vulnerable to open redirection while traversing to an existing directory.

      # PAYLOAD
      http://127.0.0.1:8000//attacker.com/..%2f..%2f..%2f..%2f..%2f../%0a%0d/../.ssh

      In this case, the actual path of .ssh was:
      http://127.0.0.1:8000/.ssh

      Upon visiting the payload URI the python server will respond back with a Location header instead of serving the directory contents directly which triggers the redirection to attacker.com

      Server: SimpleHTTP/0.6 Python/3.8.7

    2. changed the title [-]Open Redirection In Python 3.7 & 3.8[/-] [+][SECURITY] Open Redirection In Python 3.7 & 3.8[/+] on Feb 15, 2021
    3. changed the title [-]Open Redirection In Python 3.7 & 3.8[/-] [+][SECURITY] Open Redirection In Python 3.7 & 3.8[/+] on Feb 15, 2021
    4. vstinner commented on Feb 17, 2021

      @vstinner
      Member

      I can only reproduce the issue if the current directory (directory used by the HTTP server, see --directory command line option) contains a .ssh/ subdirectory.

      The problem is that the HTTP Header Location starts with "//domain/" and such URL is interpreted as an absolute URL of a new domain name ("domain"), rather than a relative path of the same domain ("localhost").

      Maybe we should simply strip all additional leading slashes to only keep one. Replace "//path" or "/////path" with "/path" for example.

      ---

      By the way, http.server uses urllib.parse.urlsplit() on the request URL without passing its own domain, and urllib.parse.urlsplit() interprets "//attacker.com/path" as if attacker.com is a host with no scheme:

      >>> urllib.parse.urlsplit('//attacker.com/path')
      SplitResult(scheme='', netloc='attacker.com', path='/path', query='', fragment='')

      Maybe parse_qs() should be used instead? Or we should reinject the server domain and port number? I am not sure that it's an issue in practice.

      SimpleHTTPRequestHandler.translate_path('//attacker.com/..%2f..%2f..%2f..%2f..%2f../.ssh') returns os.path.join(self.directory, ".ssh"). I don't think that it's an issue, it sounds like the expected behavior. We don't attempt to reject ".." in URL.

      ---

      To reproduce the issue, I used two terminals.

      Terminal 1:

      $ python3.8 -V
      Python 3.8.7
      $ python3.8 -m http.server
      Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
      127.0.0.1 - - [15/Feb/2021 09:18:20] "GET
      //attacker.com/..%2f..%2f..%2f..%2f..%2f../.ssh HTTP/1.1" 301 -

      Terminal 2:

      $ wget 'http://127.0.0.1:8000//attacker.com/..%2f..%2f..%2f..%2f..%2f../.ssh'
      (...)
      HTTP request sent, awaiting response... 301 Moved Permanently
      Location: //attacker.com/..%2f..%2f..%2f..%2f..%2f../.ssh/ [following]

      --2021-02-15 09:18:20-- http://attacker.com/..%2f..%2f..%2f..%2f..%2f../.ssh/
      Resolving attacker.com (attacker.com)... 45.88.202.115
      Connecting to attacker.com (attacker.com)|45.88.202.115|:80... connected.

      (...)

      wget is redirected and connects to attacker.com.

      The HTTP redirection comes from Lib/http/server.py:

          def send_head(self):
              path = self.translate_path(self.path)
              f = None
              if os.path.isdir(path):
                  parts = urllib.parse.urlsplit(self.path)
                  if not parts.path.endswith('/'):
                      # redirect browser - doing basically what apache does
                      self.send_response(HTTPStatus.MOVED_PERMANENTLY)
                      new_parts = (parts[0], parts[1], parts[2] + '/',
                                   parts[3], parts[4])
                      new_url = urllib.parse.urlunsplit(new_parts)
                      self.send_header("Location", new_url)
                      self.end_headers()
                      return None
                  ...
              ...

      The problem is that the "Location" header starts with "//".

    5. added
      stdlibStandard Library Python modules in the Lib/ directory
      on Feb 17, 2021
    6. 49 remaining items

    7. added a commit that references this issue on Jun 21, 2022
    8. gpshead commented on Jun 21, 2022

      @gpshead
      Member

      fixed in main, 3.11, and 3.11. the remaining branches are up to the release managers. thanks!

    9. added 2 commits that reference this issue on Jun 22, 2022
    10. added a commit that references this issue on Jun 22, 2022
    11. ngie-eign commented on Oct 11, 2022

      @ngie-eign
      Contributor

      For the record, this reproduces on python 2.7 (I used wget --debug to confirm that the Location field was modified). I suspect that this also reproduces on python version <3.7.
      Just a note for others who might be supporting python versions <3.7 in OS distributions.

    12. vstinner commented on Oct 12, 2022

      @vstinner
      Member

      CVE-2021-28861 was assigned to this vulnerability.

    13. changed the title [-][security] http.server: Open Redirection if the URL path starts with //[/-] [+][security] CVE-2021-28861: http.server: Open Redirection if the URL path starts with //[/+] on Oct 12, 2022
    14. vstinner commented on Oct 12, 2022

      @vstinner
      Member

      I created https://python-security.readthedocs.io/vuln/http-server-redirection.html to track fixes of this vulnerability.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions