diff --git a/isso/views/comments.py b/isso/views/comments.py index 8ac290d..f1e48a9 100644 --- a/isso/views/comments.py +++ b/isso/views/comments.py @@ -33,8 +33,8 @@ class JSON(Response): def csrf(view): - """A decorator to check if HTTP_Origin matches configured host. If not, - return 401 Forbidden. See + """A decorator to check if Origin matches Host (may be empty if in the same + origin, except for IE of course). When MSIE, use Referer. See * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Checking_The_Origin_Header * http://tools.ietf.org/html/draft-abarth-origin-09 @@ -45,12 +45,14 @@ def csrf(view): def dec(self, environ, request, *args, **kwargs): + hosts = map(parse.host, self.conf.getiter("host")) + if UserAgent(environ).browser == "msie": # yup - origin = request.headers.get("Referer", "") - else: - origin = request.headers.get("Origin", "") - if parse.host(origin) not in map(parse.host, self.conf.getiter("host")): - raise Forbidden("CSRF") + if parse.host(request.headers.get("Referer", "")) not in hosts: + raise Forbidden("CSRF") + elif "Origin" in request.headers: + if parse.host(request.headers.get("Origin", "")) not in hosts: + raise Forbidden("CSRF") return view(self, environ, request, *args, **kwargs) diff --git a/specs/fixtures.py b/specs/fixtures.py index d61c7fa..91f8349 100644 --- a/specs/fixtures.py +++ b/specs/fixtures.py @@ -11,7 +11,6 @@ class FakeIP(object): def __call__(self, environ, start_response): environ['REMOTE_ADDR'] = self.ip - environ['HTTP_ORIGIN'] = "http://localhost:8080" return self.app(environ, start_response) diff --git a/specs/test_comments.py b/specs/test_comments.py index 1985f0e..31d9e0b 100644 --- a/specs/test_comments.py +++ b/specs/test_comments.py @@ -269,10 +269,27 @@ class TestComments(unittest.TestCase): def testDeleteCommentRemovesThread(self): - rv = self.client.post('/new?uri=%2F', data=json.dumps({"text": "..."})) - assert '/' in self.app.db.threads - self.client.delete('/id/1') - assert '/' not in self.app.db.threads + rv = self.client.post('/new?uri=%2F', data=json.dumps({"text": "..."})) + assert '/' in self.app.db.threads + self.client.delete('/id/1') + assert '/' not in self.app.db.threads + + def testCSRF(self): + + payload = json.dumps({"text": "..."}) + + assert self.client.post('/new?uri=%2F', data=payload, + headers={"Origin": "http://localhost:8080"} + ).status_code == 201 + + assert self.client.post('/new?uri=%2F', data=payload, + headers={"Referer": "http://other.example/asdf", + "User-Agent": "msie"} + ).status_code == 403 + + assert self.client.post('/new?uri=%2F', data=payload, + headers={"Origin": "http://other.example"} + ).status_code == 403 class TestModeratedComments(unittest.TestCase):