Commit 1d628d7e authored by Luke Plant's avatar Luke Plant
Browse files

[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
parent 63686ce2
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ from django.conf import settings
from django.core.urlresolvers import get_callable
from django.utils.cache import patch_vary_headers
from django.utils.hashcompat import md5_constructor
from django.utils.http import same_origin
from django.utils.safestring import mark_safe

_POST_FORM_RE = \
@@ -137,10 +138,9 @@ class CsrfViewMiddleware(object):
                if referer is None:
                    return self._reject(request, REASON_NO_REFERER)

                # The following check ensures that the referer is HTTPS,
                # the domains match and the ports match.  This might be too strict.
                # Note that request.get_host() includes the port
                good_referer = 'https://%s/' % request.get_host()
                if not referer.startswith(good_referer):
                if not same_origin(referer, good_referer):
                    return self._reject(request, REASON_BAD_REFERER %
                                        (referer, good_referer))

+18 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import datetime
import re
import sys
import urllib
import urlparse
from email.Utils import formatdate

from django.utils.encoding import smart_str, force_unicode
@@ -186,3 +187,20 @@ def quote_etag(etag):
    """
    return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"')

if sys.version_info >= (2, 6):
    def same_origin(url1, url2):
        """
        Checks if two URLs are 'same-origin'
        """
        p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
        return (p1.scheme, p1.hostname, p1.port) == (p2.scheme, p2.hostname, p2.port)
else:
    # Python 2.4, 2.5 compatibility. This actually works for Python 2.6 and
    # above, but the above definition is much more obviously correct and so is
    # preferred going forward.
    def same_origin(url1, url2):
        """
        Checks if two URLs are 'same-origin'
        """
        p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
        return p1[0:2] == p2[0:2]
+13 −0
Original line number Diff line number Diff line
@@ -373,3 +373,16 @@ class CsrfMiddlewareTest(TestCase):
        req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
        self.assertEquals(None, req2)

    def test_https_good_referer_2(self):
        """
        Test that a POST HTTPS request with a good referer is accepted
        where the referer contains no trailing slash
        """
        # See ticket #15617
        req = self._get_POST_request_with_token()
        req._is_secure = True
        req.META['HTTP_HOST'] = 'www.example.com'
        req.META['HTTP_REFERER'] = 'https://www.example.com'
        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
        self.assertEqual(None, req2)
+24 −0
Original line number Diff line number Diff line
import unittest

from django.utils import http

class TestUtilsHttp(unittest.TestCase):

    def test_same_origin_true(self):
        # Identical
        self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/'))
        # One with trailing slash - see #15617
        self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/'))
        self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com'))
        # With port
        self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/'))

    def test_same_origin_false(self):
        # Different scheme
        self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com'))
        # Different host
        self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com'))
        # Different host again
        self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com'))
        # Different port
        self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001'))
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ from feedgenerator import *
from module_loading import *
from termcolors import *
from html import *
from http import *
from checksums import *
from text import *
from simplelazyobject import *