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.
This commit is contained in:
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…
Reference in New Issue
Block a user