From a1a746a4221317c873d4f8067b8164fef24751c9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 20 Feb 2014 13:10:50 -0800 Subject: [PATCH] server: Check for relative paths to invalid directories Avoid leaking information to requests like: http://localhost:4000/../../etc/passwd PEP 333 isn't clear on what values are allowed for PATH_INFO, but it does mention them as "CGI-style" [1]. RFC 3875, defining CGI 1.1, says about PATH_INFO [2]: The server MAY impose restrictions and limitations on what values it permits for PATH_INFO, and MAY reject the request with an error if it encounters any values considered objectionable. I can't actually exploit this with Python's reference WSGI implementation. When I tried to fetch /../../etc/passwd with Wget, I got '/etc/passwd' as PATH_INFO, but this seems like an important-enough risk that a little extra checking would not be wrong ;). Also drop the urlparse call, because PATH_INFO is already the parsed path portion of the URL. [1]: http://legacy.python.org/dev/peps/pep-0333/#specification-details [2]: http://tools.ietf.org/search/rfc3875#section-4.1.5 --- package_cache/server.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/package_cache/server.py b/package_cache/server.py index be34ee4..1da14d6 100644 --- a/package_cache/server.py +++ b/package_cache/server.py @@ -4,7 +4,6 @@ import email.utils as _email_utils import mimetypes as _mimetypes import os as _os import urllib.error as _urllib_error -import urllib.parse as _urllib_parse import urllib.request as _urllib_request from . import __version__ @@ -41,9 +40,7 @@ class Server (object): url = environ.get('PATH_INFO', None) if url is None: raise InvalidFile(url=url) - parsed_url = _urllib_parse.urlparse(url) - relative_path = parsed_url.path.lstrip('/').replace('/', _os.path.sep) - cache_path = _os.path.join(self.cache, relative_path) + cache_path = self._get_cache_path(url=url) if not _os.path.exists(path=cache_path): self._get_file_from_sources(url=url, path=cache_path) if not _os.path.isfile(path=cache_path): @@ -51,6 +48,15 @@ class Server (object): return self._serve_file( path=cache_path, environ=environ, start_response=start_response) + def _get_cache_path(self, url): + relative_path = url.lstrip('/').replace('/', _os.path.sep) + cache_path = _os.path.abspath(_os.path.join(self.cache, relative_path)) + check_relative_path = _os.path.relpath( + path=cache_path, start=self.cache) + if check_relative_path.startswith(_os.pardir + _os.path.sep): + raise InvalidFile(url=url) + return cache_path + def _get_file_from_sources(self, url, path): dirname = _os.path.dirname(path) if not _os.path.isdir(dirname): -- 2.26.2