Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.2.X] Fixed #15617 - CSRF referer checking too strict

Thanks to adam for the report.

Backport of [15840] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@15844 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1d628d7ecf9ae832894e54acf7757a62caf7b548 1 parent 63686ce
Luke Plant authored March 15, 2011
6  django/middleware/csrf.py
@@ -13,6 +13,7 @@
13 13
 from django.core.urlresolvers import get_callable
14 14
 from django.utils.cache import patch_vary_headers
15 15
 from django.utils.hashcompat import md5_constructor
  16
+from django.utils.http import same_origin
16 17
 from django.utils.safestring import mark_safe
17 18
 
18 19
 _POST_FORM_RE = \
@@ -137,10 +138,9 @@ def process_view(self, request, callback, callback_args, callback_kwargs):
137 138
                 if referer is None:
138 139
                     return self._reject(request, REASON_NO_REFERER)
139 140
 
140  
-                # The following check ensures that the referer is HTTPS,
141  
-                # the domains match and the ports match.  This might be too strict.
  141
+                # Note that request.get_host() includes the port
142 142
                 good_referer = 'https://%s/' % request.get_host()
143  
-                if not referer.startswith(good_referer):
  143
+                if not same_origin(referer, good_referer):
144 144
                     return self._reject(request, REASON_BAD_REFERER %
145 145
                                         (referer, good_referer))
146 146
 
18  django/utils/http.py
@@ -3,6 +3,7 @@
3 3
 import re
4 4
 import sys
5 5
 import urllib
  6
+import urlparse
6 7
 from email.Utils import formatdate
7 8
 
8 9
 from django.utils.encoding import smart_str, force_unicode
@@ -186,3 +187,20 @@ def quote_etag(etag):
186 187
     """
187 188
     return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"')
188 189
 
  190
+if sys.version_info >= (2, 6):
  191
+    def same_origin(url1, url2):
  192
+        """
  193
+        Checks if two URLs are 'same-origin'
  194
+        """
  195
+        p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
  196
+        return (p1.scheme, p1.hostname, p1.port) == (p2.scheme, p2.hostname, p2.port)
  197
+else:
  198
+    # Python 2.4, 2.5 compatibility. This actually works for Python 2.6 and
  199
+    # above, but the above definition is much more obviously correct and so is
  200
+    # preferred going forward.
  201
+    def same_origin(url1, url2):
  202
+        """
  203
+        Checks if two URLs are 'same-origin'
  204
+        """
  205
+        p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
  206
+        return p1[0:2] == p2[0:2]
13  tests/regressiontests/csrf_tests/tests.py
@@ -373,3 +373,16 @@ def test_https_good_referer(self):
373 373
         req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
374 374
         req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
375 375
         self.assertEquals(None, req2)
  376
+
  377
+    def test_https_good_referer_2(self):
  378
+        """
  379
+        Test that a POST HTTPS request with a good referer is accepted
  380
+        where the referer contains no trailing slash
  381
+        """
  382
+        # See ticket #15617
  383
+        req = self._get_POST_request_with_token()
  384
+        req._is_secure = True
  385
+        req.META['HTTP_HOST'] = 'www.example.com'
  386
+        req.META['HTTP_REFERER'] = 'https://www.example.com'
  387
+        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
  388
+        self.assertEqual(None, req2)
24  tests/regressiontests/utils/http.py
... ...
@@ -0,0 +1,24 @@
  1
+import unittest
  2
+
  3
+from django.utils import http
  4
+
  5
+class TestUtilsHttp(unittest.TestCase):
  6
+
  7
+    def test_same_origin_true(self):
  8
+        # Identical
  9
+        self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/'))
  10
+        # One with trailing slash - see #15617
  11
+        self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/'))
  12
+        self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com'))
  13
+        # With port
  14
+        self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/'))
  15
+
  16
+    def test_same_origin_false(self):
  17
+        # Different scheme
  18
+        self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com'))
  19
+        # Different host
  20
+        self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com'))
  21
+        # Different host again
  22
+        self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com'))
  23
+        # Different port
  24
+        self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001'))
1  tests/regressiontests/utils/tests.py
@@ -7,6 +7,7 @@
7 7
 from module_loading import *
8 8
 from termcolors import *
9 9
 from html import *
  10
+from http import *
10 11
 from checksums import *
11 12
 from text import *
12 13
 from simplelazyobject import *

0 notes on commit 1d628d7

Please sign in to comment.
Something went wrong with that request. Please try again.