1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
diff -Naur Python-2.7.1.ori/Lib/test/test_urllib2.py Python-2.7.1/Lib/test/test_urllib2.py
--- Python-2.7.1.ori/Lib/test/test_urllib2.py 2010-11-21 21:04:33.000000000 -0800
+++ Python-2.7.1/Lib/test/test_urllib2.py 2011-04-15 05:02:13.278853672 -0700
@@ -969,6 +969,27 @@
self.assertEqual(count,
urllib2.HTTPRedirectHandler.max_redirections)
+ def test_invalid_redirect(self):
+ from_url = "http://example.com/a.html"
+ valid_schemes = ['http', 'https', 'ftp']
+ invalid_schemes = ['file', 'imap', 'ldap']
+ schemeless_url = "example.com/b.html"
+ h = urllib2.HTTPRedirectHandler()
+ o = h.parent = MockOpener()
+ req = Request(from_url)
+
+ for scheme in invalid_schemes:
+ invalid_url = scheme + '://' + schemeless_url
+ self.assertRaises(urllib2.HTTPError, h.http_error_302,
+ req, MockFile(), 302, "Security Loophole",
+ MockHeaders({"location": invalid_url}))
+
+ for scheme in valid_schemes:
+ valid_url = scheme + '://' + schemeless_url
+ h.http_error_302(req, MockFile(), 302, "That's fine",
+ MockHeaders({"location": valid_url}))
+ self.assertEqual(o.req.get_full_url(), valid_url)
+
def test_cookie_redirect(self):
# cookies shouldn't leak into redirected requests
from cookielib import CookieJar
diff -Naur Python-2.7.1.ori/Lib/test/test_urllib.py Python-2.7.1/Lib/test/test_urllib.py
--- Python-2.7.1.ori/Lib/test/test_urllib.py 2010-11-21 05:34:58.000000000 -0800
+++ Python-2.7.1/Lib/test/test_urllib.py 2011-04-15 05:02:13.278853672 -0700
@@ -161,6 +161,20 @@
finally:
self.unfakehttp()
+ def test_invalid_redirect(self):
+ # urlopen() should raise IOError for many error codes.
+ self.fakehttp("""HTTP/1.1 302 Found
+Date: Wed, 02 Jan 2008 03:03:54 GMT
+Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e
+Location: file:README
+Connection: close
+Content-Type: text/html; charset=iso-8859-1
+""")
+ try:
+ self.assertRaises(IOError, urllib.urlopen, "http://python.org/")
+ finally:
+ self.unfakehttp()
+
def test_empty_socket(self):
# urlopen() raises IOError if the underlying socket does not send any
# data. (#1680230)
diff -Naur Python-2.7.1.ori/Lib/urllib2.py Python-2.7.1/Lib/urllib2.py
--- Python-2.7.1.ori/Lib/urllib2.py 2010-11-20 03:24:08.000000000 -0800
+++ Python-2.7.1/Lib/urllib2.py 2011-04-15 05:02:13.278853672 -0700
@@ -579,6 +579,17 @@
newurl = urlparse.urljoin(req.get_full_url(), newurl)
+ # For security reasons we do not allow redirects to protocols
+ # other than HTTP, HTTPS or FTP.
+ newurl_lower = newurl.lower()
+ if not (newurl_lower.startswith('http://') or
+ newurl_lower.startswith('https://') or
+ newurl_lower.startswith('ftp://')):
+ raise HTTPError(newurl, code,
+ msg + " - Redirection to url '%s' is not allowed" %
+ newurl,
+ headers, fp)
+
# XXX Probably want to forget about the state of the current
# request, although that might interact poorly with other
# handlers that also use handler-specific request attributes
diff -Naur Python-2.7.1.ori/Lib/urllib.py Python-2.7.1/Lib/urllib.py
--- Python-2.7.1.ori/Lib/urllib.py 2010-11-21 21:04:33.000000000 -0800
+++ Python-2.7.1/Lib/urllib.py 2011-04-15 05:02:13.278853672 -0700
@@ -644,6 +644,18 @@
fp.close()
# In case the server sent a relative URL, join with original:
newurl = basejoin(self.type + ":" + url, newurl)
+
+ # For security reasons we do not allow redirects to protocols
+ # other than HTTP, HTTPS or FTP.
+ newurl_lower = newurl.lower()
+ if not (newurl_lower.startswith('http://') or
+ newurl_lower.startswith('https://') or
+ newurl_lower.startswith('ftp://')):
+ raise IOError('redirect error', errcode,
+ errmsg + " - Redirection to url '%s' is not allowed" %
+ newurl,
+ headers)
+
return self.open(newurl)
def http_error_301(self, url, fp, errcode, errmsg, headers, data=None):
|