HTTP Origin is only sent on cross-origin requests in Firefox

Therefore, only raise Forbidden if Origin (or Referer for MSIE) is sent
(which is a protected header and all modern browsers (except IE)).

Also add a basic unit test which asserts the failure for false origins.
pull/43/head
Martin Zimmermann 11 years ago
parent 8802b73b52
commit b839b2be31

@ -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)

@ -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)

@ -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):

Loading…
Cancel
Save