Repository navigation
[security] CVE-2021-28861: http.server: Open Redirection if the URL path starts with // #87389
Description
Activity
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/../.sshIn this case, the actual path of .ssh was:
http://127.0.0.1:8000/.sshUpon 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
- addedtype-securityA security issueA security issue3.7 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life
on Feb 14, 2021 - changed the title
[-]Open Redirection In Python 3.7 & 3.8[/-][+][SECURITY] Open Redirection In Python 3.7 & 3.8[/+]on Feb 15, 2021 - changed the title
[-]Open Redirection In Python 3.7 & 3.8[/-][+][SECURITY] Open Redirection In Python 3.7 & 3.8[/+]on Feb 15, 2021 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 "//".
- added3.9 (EOL)end of lifeend of life3.10 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Feb 17, 2021 49 remaining items
fixed in main, 3.11, and 3.11. the remaining branches are up to the release managers. thanks!
- added a commit that references this issue
on Jun 22, 2022 - added a commit that references this issue
on Jun 26, 2022 For the record, this reproduces on python 2.7 (I used
wget --debugto 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.CVE-2021-28861 was assigned to this vulnerability.
- 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 I created https://python-security.readthedocs.io/vuln/http-server-redirection.html to track fixes of this vulnerability.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
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:
bugs.python.org fields:
Linked PRs