Ticket #16010: crsf-origin-check.diff

File crsf-origin-check.diff, 7.4 KB (added by davidben, 14 years ago)

Updated patch

  • django/middleware/csrf.py

    From 063990cabc795bd8989814c6e6e03fc148e23509 Mon Sep 17 00:00:00 2001
    From: David Benjamin <davidben@mit.edu>
    Date: Sat, 7 May 2011 23:21:07 -0400
    Subject: [PATCH] Added support for the Origin header in CSRF middleware
    
    Also added documentation and unit tests for it.
    ---
     django/middleware/csrf.py                 |   29 +++++++++++--
     docs/ref/contrib/csrf.txt                 |    6 +++
     tests/regressiontests/csrf_tests/tests.py |   63 ++++++++++++++++++++++++++++-
     3 files changed, 93 insertions(+), 5 deletions(-)
    
    diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
    index 2f36f18..b412406 100644
    a b else:  
    2525    randrange = random.randrange
    2626_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
    2727
     28REASON_BAD_ORIGIN = "Origin checking failed - %s does not match %s."
    2829REASON_NO_REFERER = "Referer checking failed - no Referer."
    2930REASON_BAD_REFERER = "Referer checking failed - %s does not match %s."
    3031REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
    class CsrfViewMiddleware(object):  
    115116                # any branches that call reject()
    116117                return self._accept(request)
    117118
     119            # Note that request.get_host() includes the port
     120            good_origin = '%s://%s' % (request.is_secure() and 'https' or 'http',
     121                                       request.get_host())
     122
     123            if 'HTTP_ORIGIN' in request.META:
     124                # Cookies are unreliable when there are untrusted sites on sibling
     125                # domains. foo.example.com can set a cookie scoped to .example.com
     126                # that is then visible to bar.example.com. There is a proposal for a
     127                # new Origin header which should be a better CSRF check. When the
     128                # browser supports it, verify the match. It is currently implemented
     129                # by WebKit-based browsers, and Mozilla has a ticket to add support.
     130                origin = request.META['HTTP_ORIGIN']
     131                if good_origin != origin:
     132                    reason = REASON_BAD_ORIGIN % (origin, good_origin)
     133                    logger.warning('Forbidden (%s): %s' % (reason, request.path),
     134                        extra={
     135                            'status_code': 403,
     136                            'request': request,
     137                        }
     138                    )
     139                    return self._reject(request, reason)
     140
    118141            if request.is_secure():
    119142                # Suppose user visits http://example.com/
    120143                # An active network attacker,(man-in-the-middle, MITM) sends a
    class CsrfViewMiddleware(object):  
    141164                    )
    142165                    return self._reject(request, REASON_NO_REFERER)
    143166
    144                 # Note that request.get_host() includes the port
    145                 good_referer = 'https://%s/' % request.get_host()
    146                 if not same_origin(referer, good_referer):
    147                     reason = REASON_BAD_REFERER % (referer, good_referer)
     167                if not same_origin(referer, good_origin):
     168                    reason = REASON_BAD_REFERER % (referer, good_origin)
    148169                    logger.warning('Forbidden (%s): %s' % (reason, request.path),
    149170                        extra={
    150171                            'status_code': 403,
  • docs/ref/contrib/csrf.txt

    diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt
    index 013125a..dee0c37 100644
    a b The CSRF protection is based on the following things:  
    213213   done for HTTP requests because the presence of the Referer header is not
    214214   reliable enough under HTTP.)
    215215
     2165. When the browser provides the `Origin header`_, it is checked for the correct
     217   origin as in the referer checking above.  This provides protection against
     218   cross-subdomain attacks for browsers which implement the header.
     219
     220.. _Origin header: http://www.ietf.org/id/draft-ietf-websec-origin-00.txt
     221
    216222This ensures that only forms that have originated from your Web site can be used
    217223to POST data back.
    218224
  • tests/regressiontests/csrf_tests/tests.py

    diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py
    index a98a6a4..751a0cc 100644
    a b class CsrfViewMiddlewareTest(TestCase):  
    5151    _csrf_id = "1"
    5252
    5353    def _get_GET_no_csrf_cookie_request(self):
    54         return TestingHttpRequest()
     54        req = TestingHttpRequest()
     55        # Include some host so request.get_host() doesn't error.
     56        req.META['HTTP_HOST'] = 'www.example.com'
     57        return req
    5558
    5659    def _get_GET_csrf_cookie_request(self):
    5760        req = TestingHttpRequest()
     61        # Include some host so request.get_host() doesn't error.
     62        req.META['HTTP_HOST'] = 'www.example.com'
    5863        req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie
    5964        return req
    6065
    class CsrfViewMiddlewareTest(TestCase):  
    249254        req.META['HTTP_REFERER'] = 'https://www.example.com'
    250255        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
    251256        self.assertEqual(None, req2)
     257
     258    def test_bad_origin(self):
     259        """
     260        Test that a request with a bad origin is rejected
     261        """
     262        req = self._get_POST_request_with_token()
     263        req.META['HTTP_HOST'] = 'www.example.com'
     264        req.META['HTTP_ORIGIN'] = 'https://www.evil.org'
     265        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
     266        self.assertNotEqual(None, req2)
     267        self.assertEqual(403, req2.status_code)
     268
     269    def test_bad_origin_2(self):
     270        """
     271        Test that a request with a null origin is rejected
     272        """
     273        req = self._get_POST_request_with_token()
     274        req.META['HTTP_HOST'] = 'www.example.com'
     275        req.META['HTTP_ORIGIN'] = 'null'
     276        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
     277        self.assertNotEqual(None, req2)
     278        self.assertEqual(403, req2.status_code)
     279
     280    def test_bad_origin_3(self):
     281        """
     282        Test that a request with an origin with wrong protocol is rejected
     283        """
     284        req = self._get_POST_request_with_token()
     285        req._is_secure = True
     286        req.META['HTTP_HOST'] = 'www.example.com'
     287        req.META['HTTP_ORIGIN'] = 'http://example.com'
     288        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
     289        self.assertNotEqual(None, req2)
     290        self.assertEqual(403, req2.status_code)
     291
     292    def test_good_origin(self):
     293        """
     294        Test that a POST HTTP request with a good origin is accepted
     295        """
     296        req = self._get_POST_request_with_token()
     297        req.META['HTTP_HOST'] = 'www.example.com'
     298        req.META['HTTP_ORIGIN'] = 'http://www.example.com'
     299        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
     300        self.assertEqual(None, req2)
     301
     302    def test_good_origin_2(self):
     303        """
     304        Test that a POST HTTPS request with a good origin is accepted
     305        """
     306        req = self._get_POST_request_with_token()
     307        req._is_secure = True
     308        req.META['HTTP_HOST'] = 'www.example.com'
     309        req.META['HTTP_ORIGIN'] = 'https://www.example.com'
     310        req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
     311        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
     312        self.assertEqual(None, req2)
Back to Top