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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
diff -Naur Python-3.2.ori/Doc/library/urllib.request.rst Python-3.2/Doc/library/urllib.request.rst
--- Python-3.2.ori/Doc/library/urllib.request.rst 2011-02-11 03:25:47.000000000 -0800
+++ Python-3.2/Doc/library/urllib.request.rst 2011-04-15 03:49:02.778745379 -0700
@@ -650,6 +650,10 @@
is the case, :exc:`HTTPError` is raised. See :rfc:`2616` for details of the
precise meanings of the various redirection codes.
+ An :class:`HTTPError` exception raised as a security consideration if the
+ HTTPRedirectHandler is presented with a redirected url which is not an HTTP,
+ HTTPS or FTP url.
+
.. method:: HTTPRedirectHandler.redirect_request(req, fp, code, msg, hdrs, newurl)
diff -Naur Python-3.2.ori/Lib/test/test_urllib2.py Python-3.2/Lib/test/test_urllib2.py
--- Python-3.2.ori/Lib/test/test_urllib2.py 2011-02-11 03:25:47.000000000 -0800
+++ Python-3.2/Lib/test/test_urllib2.py 2011-04-15 03:50:29.705417290 -0700
@@ -8,6 +8,7 @@
import urllib.request
from urllib.request import Request, OpenerDirector
+import urllib.error
# XXX
# Request
@@ -1029,6 +1030,29 @@
self.assertEqual(count,
urllib.request.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 = urllib.request.HTTPRedirectHandler()
+ o = h.parent = MockOpener()
+ req = Request(from_url)
+ req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT
+
+ for scheme in invalid_schemes:
+ invalid_url = scheme + '://' + schemeless_url
+ self.assertRaises(urllib.error.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 http.cookiejar import CookieJar
diff -Naur Python-3.2.ori/Lib/test/test_urllib.py Python-3.2/Lib/test/test_urllib.py
--- Python-3.2.ori/Lib/test/test_urllib.py 2010-12-17 09:35:56.000000000 -0800
+++ Python-3.2/Lib/test/test_urllib.py 2011-04-15 03:49:02.778745379 -0700
@@ -2,6 +2,7 @@
import urllib.parse
import urllib.request
+import urllib.error
import http.client
import email.message
import io
@@ -198,6 +199,21 @@
finally:
self.unfakehttp()
+ def test_invalid_redirect(self):
+ # urlopen() should raise IOError for many error codes.
+ self.fakehttp(b'''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://guidocomputer.athome.com:/python/license
+Connection: close
+Content-Type: text/html; charset=iso-8859-1
+''')
+ try:
+ self.assertRaises(urllib.error.HTTPError, 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-3.2.ori/Lib/urllib/request.py Python-3.2/Lib/urllib/request.py
--- Python-3.2.ori/Lib/urllib/request.py 2011-02-11 03:25:47.000000000 -0800
+++ Python-3.2/Lib/urllib/request.py 2011-04-15 03:49:02.778745379 -0700
@@ -545,6 +545,17 @@
# fix a possible malformed URL
urlparts = urlparse(newurl)
+
+ # For security reasons we don't allow redirection to anything other
+ # than http, https or ftp.
+
+ if not urlparts.scheme in ('http', 'https', 'ftp'):
+ raise HTTPError(newurl, code,
+ msg +
+ " - Redirection to url '%s' is not allowed" %
+ newurl,
+ headers, fp)
+
if not urlparts.path:
urlparts = list(urlparts)
urlparts[2] = "/"
@@ -1897,8 +1908,24 @@
return
void = fp.read()
fp.close()
+
# In case the server sent a relative URL, join with original:
newurl = urljoin(self.type + ":" + url, newurl)
+
+ urlparts = urlparse(newurl)
+
+ # For security reasons, we don't allow redirection to anything other
+ # than http, https and ftp.
+
+ # We are using newer HTTPError with older redirect_internal method
+ # This older method will get deprecated in 3.3
+
+ if not urlparts.scheme in ('http', 'https', 'ftp'):
+ raise HTTPError(newurl, errcode,
+ errmsg +
+ " Redirection to url '%s' is not allowed." % newurl,
+ headers, fp)
+
return self.open(newurl)
def http_error_301(self, url, fp, errcode, errmsg, headers, data=None):
|