+46
-1
lines changedFilter options
+46
-1
lines changed Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ def remote_user_auth_view(request):
19
19
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
20
20
(r'^remote_user/$', remote_user_auth_view),
21
21
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
22
+
(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
22
23
(r'^login_required/$', login_required(password_reset)),
23
24
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
24
25
)
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
9
9
from django.contrib.auth.models import User
10
10
from django.test import TestCase
11
11
from django.core import mail
12
+
from django.core.exceptions import SuspiciousOperation
12
13
from django.core.urlresolvers import reverse
13
14
from django.http import QueryDict
14
15
@@ -69,6 +70,44 @@ def test_email_found_custom_from(self):
69
70
self.assertEqual(len(mail.outbox), 1)
70
71
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
71
72
73
+
def test_admin_reset(self):
74
+
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
75
+
response = self.client.post('/admin_password_reset/',
76
+
{'email': 'staffmember@example.com'},
77
+
HTTP_HOST='adminsite.com'
78
+
)
79
+
self.assertEqual(response.status_code, 302)
80
+
self.assertEqual(len(mail.outbox), 1)
81
+
self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
82
+
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
83
+
84
+
def test_poisoned_http_host(self):
85
+
"Poisoned HTTP_HOST headers can't be used for reset emails"
86
+
# This attack is based on the way browsers handle URLs. The colon
87
+
# should be used to separate the port, but if the URL contains an @,
88
+
# the colon is interpreted as part of a username for login purposes,
89
+
# making 'evil.com' the request domain. Since HTTP_HOST is used to
90
+
# produce a meaningful reset URL, we need to be certain that the
91
+
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
92
+
# is invoked, but we check here as a practical consequence.
93
+
def test_host_poisoning():
94
+
self.client.post('/password_reset/',
95
+
{'email': 'staffmember@example.com'},
96
+
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
97
+
)
98
+
self.assertRaises(SuspiciousOperation, test_host_poisoning)
99
+
self.assertEqual(len(mail.outbox), 0)
100
+
101
+
def test_poisoned_http_host_admin_site(self):
102
+
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
103
+
def test_host_poisoning():
104
+
self.client.post('/admin_password_reset/',
105
+
{'email': 'staffmember@example.com'},
106
+
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
107
+
)
108
+
self.assertRaises(SuspiciousOperation, test_host_poisoning)
109
+
self.assertEqual(len(mail.outbox), 0)
110
+
72
111
def _test_confirm_start(self):
73
112
# Start by creating the email
74
113
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
Original file line number Diff line number Diff line change
@@ -151,7 +151,7 @@ def password_reset(request, is_admin_site=False,
151
151
'request': request,
152
152
}
153
153
if is_admin_site:
154
-
opts = dict(opts, domain_override=request.META['HTTP_HOST'])
154
+
opts = dict(opts, domain_override=request.get_host())
155
155
form.save(**opts)
156
156
return HttpResponseRedirect(post_reset_redirect)
157
157
else:
Original file line number Diff line number Diff line change
@@ -165,6 +165,11 @@ def get_host(self):
165
165
server_port = str(self.META['SERVER_PORT'])
166
166
if server_port != (self.is_secure() and '443' or '80'):
167
167
host = '%s:%s' % (host, server_port)
168
+
169
+
# Disallow potentially poisoned hostnames.
170
+
if set(';/?@&=+$,').intersection(host):
171
+
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
172
+
168
173
return host
169
174
170
175
def get_full_path(self):
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4