Kaydet (Commit) 923ba361 authored tarafından Gregory P. Smith's avatar Gregory P. Smith

- Issue #2254: Fix CGIHTTPServer information disclosure. Relative paths are

  now collapsed within the url properly before looking in cgi_directories.
üst 183028ed
...@@ -70,27 +70,20 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -70,27 +70,20 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
def is_cgi(self): def is_cgi(self):
"""Test whether self.path corresponds to a CGI script, """Test whether self.path corresponds to a CGI script.
and return a boolean.
This function sets self.cgi_info to a tuple (dir, rest)
when it returns True, where dir is the directory part before
the CGI script name. Note that rest begins with a
slash if it is not empty.
The default implementation tests whether the path
begins with one of the strings in the list
self.cgi_directories (and the next character is a '/'
or the end of the string).
"""
path = self.path Returns True and updates the cgi_info attribute to the tuple
(dir, rest) if self.path requires running a CGI script.
Returns False otherwise.
for x in self.cgi_directories: The default implementation tests whether the normalized url
i = len(x) path begins with one of the strings in self.cgi_directories
if path[:i] == x and (not path[i:] or path[i] == '/'): (and the next character is a '/' or the end of the string).
self.cgi_info = path[:i], path[i+1:] """
return True splitpath = _url_collapse_path_split(self.path)
if splitpath[0] in self.cgi_directories:
self.cgi_info = splitpath
return True
return False return False
cgi_directories = ['/cgi-bin', '/htbin'] cgi_directories = ['/cgi-bin', '/htbin']
...@@ -330,6 +323,46 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -330,6 +323,46 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
self.log_message("CGI script exited OK") self.log_message("CGI script exited OK")
# TODO(gregory.p.smith): Move this into an appropriate library.
def _url_collapse_path_split(path):
"""
Given a URL path, remove extra '/'s and '.' path elements and collapse
any '..' references.
Implements something akin to RFC-2396 5.2 step 6 to parse relative paths.
Returns: A tuple of (head, tail) where tail is everything after the final /
and head is everything before it. Head will always start with a '/' and,
if it contains anything else, never have a trailing '/'.
Raises: IndexError if too many '..' occur within the path.
"""
# Similar to os.path.split(os.path.normpath(path)) but specific to URL
# path semantics rather than local operating system semantics.
path_parts = []
for part in path.split('/'):
if part == '.':
path_parts.append('')
else:
path_parts.append(part)
# Filter out blank non trailing parts before consuming the '..'.
path_parts = [part for part in path_parts[:-1] if part] + path_parts[-1:]
if path_parts:
tail_part = path_parts.pop()
else:
tail_part = ''
head_parts = []
for part in path_parts:
if part == '..':
head_parts.pop()
else:
head_parts.append(part)
if tail_part and tail_part == '..':
head_parts.pop()
tail_part = ''
return ('/' + '/'.join(head_parts), tail_part)
nobody = None nobody = None
def nobody_uid(): def nobody_uid():
......
...@@ -7,6 +7,7 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. ...@@ -7,6 +7,7 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler from SimpleHTTPServer import SimpleHTTPRequestHandler
from CGIHTTPServer import CGIHTTPRequestHandler from CGIHTTPServer import CGIHTTPRequestHandler
import CGIHTTPServer
import os import os
import sys import sys
...@@ -315,6 +316,45 @@ class CGIHTTPServerTestCase(BaseTestCase): ...@@ -315,6 +316,45 @@ class CGIHTTPServerTestCase(BaseTestCase):
finally: finally:
BaseTestCase.tearDown(self) BaseTestCase.tearDown(self)
def test_url_collapse_path_split(self):
test_vectors = {
'': ('/', ''),
'..': IndexError,
'/.//..': IndexError,
'/': ('/', ''),
'//': ('/', ''),
'/\\': ('/', '\\'),
'/.//': ('/', ''),
'cgi-bin/file1.py': ('/cgi-bin', 'file1.py'),
'/cgi-bin/file1.py': ('/cgi-bin', 'file1.py'),
'a': ('/', 'a'),
'/a': ('/', 'a'),
'//a': ('/', 'a'),
'./a': ('/', 'a'),
'./C:/': ('/C:', ''),
'/a/b': ('/a', 'b'),
'/a/b/': ('/a/b', ''),
'/a/b/c/..': ('/a/b', ''),
'/a/b/c/../d': ('/a/b', 'd'),
'/a/b/c/../d/e/../f': ('/a/b/d', 'f'),
'/a/b/c/../d/e/../../f': ('/a/b', 'f'),
'/a/b/c/../d/e/.././././..//f': ('/a/b', 'f'),
'../a/b/c/../d/e/.././././..//f': IndexError,
'/a/b/c/../d/e/../../../f': ('/a', 'f'),
'/a/b/c/../d/e/../../../../f': ('/', 'f'),
'/a/b/c/../d/e/../../../../../f': IndexError,
'/a/b/c/../d/e/../../../../f/..': ('/', ''),
}
for path, expected in test_vectors.iteritems():
if isinstance(expected, type) and issubclass(expected, Exception):
self.assertRaises(expected,
CGIHTTPServer._url_collapse_path_split, path)
else:
actual = CGIHTTPServer._url_collapse_path_split(path)
self.assertEquals(expected, actual,
msg='path = %r\nGot: %r\nWanted: %r' % (
path, actual, expected))
def test_headers_and_content(self): def test_headers_and_content(self):
res = self.request('/cgi-bin/file1.py') res = self.request('/cgi-bin/file1.py')
self.assertEquals(('Hello World\n', 'text/html', 200), \ self.assertEquals(('Hello World\n', 'text/html', 200), \
...@@ -339,6 +379,12 @@ class CGIHTTPServerTestCase(BaseTestCase): ...@@ -339,6 +379,12 @@ class CGIHTTPServerTestCase(BaseTestCase):
self.assertEquals(('Hello World\n', 'text/html', 200), \ self.assertEquals(('Hello World\n', 'text/html', 200), \
(res.read(), res.getheader('Content-type'), res.status)) (res.read(), res.getheader('Content-type'), res.status))
def test_no_leading_slash(self):
# http://bugs.python.org/issue2254
res = self.request('cgi-bin/file1.py')
self.assertEquals(('Hello World\n', 'text/html', 200),
(res.read(), res.getheader('Content-type'), res.status))
def test_main(verbose=None): def test_main(verbose=None):
try: try:
......
...@@ -216,6 +216,9 @@ Core and Builtins ...@@ -216,6 +216,9 @@ Core and Builtins
Library Library
------- -------
- Issue #2254: Fix CGIHTTPServer information disclosure. Relative paths are
now collapsed within the url properly before looking in cgi_directories.
- Issue #5095: Added bdist_msi to the list of bdist supported formats. - Issue #5095: Added bdist_msi to the list of bdist supported formats.
Initial fix by Steven Bethard. Initial fix by Steven Bethard.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment