Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 92d3430

Browse files
committed
Fixed a security issue related to password resets
Full disclosure and new release are forthcoming backport from master
1 parent 73991b0 commit 92d3430

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

django/contrib/auth/tests/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def userpage(request):
5151
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
5252
(r'^remote_user/$', remote_user_auth_view),
5353
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='[email protected]')),
54+
(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
5455
(r'^login_required/$', login_required(password_reset)),
5556
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
5657

django/contrib/auth/tests/views.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.contrib.sites.models import Site, RequestSite
88
from django.contrib.auth.models import User
99
from django.core import mail
10+
from django.core.exceptions import SuspiciousOperation
1011
from django.core.urlresolvers import reverse, NoReverseMatch
1112
from django.http import QueryDict
1213
from django.utils.encoding import force_unicode
@@ -106,6 +107,42 @@ def test_email_found_custom_from(self):
106107
self.assertEqual(len(mail.outbox), 1)
107108
self.assertEqual("[email protected]", mail.outbox[0].from_email)
108109

110+
def test_admin_reset(self):
111+
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
112+
response = self.client.post('/admin_password_reset/',
113+
{'email': '[email protected]'},
114+
HTTP_HOST='adminsite.com'
115+
)
116+
self.assertEqual(response.status_code, 302)
117+
self.assertEqual(len(mail.outbox), 1)
118+
self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
119+
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
120+
121+
def test_poisoned_http_host(self):
122+
"Poisoned HTTP_HOST headers can't be used for reset emails"
123+
# This attack is based on the way browsers handle URLs. The colon
124+
# should be used to separate the port, but if the URL contains an @,
125+
# the colon is interpreted as part of a username for login purposes,
126+
# making 'evil.com' the request domain. Since HTTP_HOST is used to
127+
# produce a meaningful reset URL, we need to be certain that the
128+
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
129+
# is invoked, but we check here as a practical consequence.
130+
with self.assertRaises(SuspiciousOperation):
131+
self.client.post('/password_reset/',
132+
{'email': '[email protected]'},
133+
HTTP_HOST='www.example:[email protected]'
134+
)
135+
self.assertEqual(len(mail.outbox), 0)
136+
137+
def test_poisoned_http_host_admin_site(self):
138+
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
139+
with self.assertRaises(SuspiciousOperation):
140+
self.client.post('/admin_password_reset/',
141+
{'email': '[email protected]'},
142+
HTTP_HOST='www.example:[email protected]'
143+
)
144+
self.assertEqual(len(mail.outbox), 0)
145+
109146
def _test_confirm_start(self):
110147
# Start by creating the email
111148
response = self.client.post('/password_reset/', {'email': '[email protected]'})

django/contrib/auth/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def password_reset(request, is_admin_site=False,
156156
'request': request,
157157
}
158158
if is_admin_site:
159-
opts = dict(opts, domain_override=request.META['HTTP_HOST'])
159+
opts = dict(opts, domain_override=request.get_host())
160160
form.save(**opts)
161161
return HttpResponseRedirect(post_reset_redirect)
162162
else:

django/http/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ def get_host(self):
212212
server_port = str(self.META['SERVER_PORT'])
213213
if server_port != (self.is_secure() and '443' or '80'):
214214
host = '%s:%s' % (host, server_port)
215+
216+
# Disallow potentially poisoned hostnames.
217+
if set(';/?@&=+$,').intersection(host):
218+
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
219+
215220
return host
216221

217222
def get_full_path(self):

0 commit comments

Comments
 (0)