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):
|
def csrf(view):
|
||||||
"""A decorator to check if HTTP_Origin matches configured host. If not,
|
"""A decorator to check if Origin matches Host (may be empty if in the same
|
||||||
return 401 Forbidden. See
|
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
|
* 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
|
* http://tools.ietf.org/html/draft-abarth-origin-09
|
||||||
@ -45,12 +45,14 @@ def csrf(view):
|
|||||||
|
|
||||||
def dec(self, environ, request, *args, **kwargs):
|
def dec(self, environ, request, *args, **kwargs):
|
||||||
|
|
||||||
|
hosts = map(parse.host, self.conf.getiter("host"))
|
||||||
|
|
||||||
if UserAgent(environ).browser == "msie": # yup
|
if UserAgent(environ).browser == "msie": # yup
|
||||||
origin = request.headers.get("Referer", "")
|
if parse.host(request.headers.get("Referer", "")) not in hosts:
|
||||||
else:
|
raise Forbidden("CSRF")
|
||||||
origin = request.headers.get("Origin", "")
|
elif "Origin" in request.headers:
|
||||||
if parse.host(origin) not in map(parse.host, self.conf.getiter("host")):
|
if parse.host(request.headers.get("Origin", "")) not in hosts:
|
||||||
raise Forbidden("CSRF")
|
raise Forbidden("CSRF")
|
||||||
|
|
||||||
return view(self, environ, request, *args, **kwargs)
|
return view(self, environ, request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ class FakeIP(object):
|
|||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
environ['REMOTE_ADDR'] = self.ip
|
environ['REMOTE_ADDR'] = self.ip
|
||||||
environ['HTTP_ORIGIN'] = "http://localhost:8080"
|
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,10 +269,27 @@ class TestComments(unittest.TestCase):
|
|||||||
|
|
||||||
def testDeleteCommentRemovesThread(self):
|
def testDeleteCommentRemovesThread(self):
|
||||||
|
|
||||||
rv = self.client.post('/new?uri=%2F', data=json.dumps({"text": "..."}))
|
rv = self.client.post('/new?uri=%2F', data=json.dumps({"text": "..."}))
|
||||||
assert '/' in self.app.db.threads
|
assert '/' in self.app.db.threads
|
||||||
self.client.delete('/id/1')
|
self.client.delete('/id/1')
|
||||||
assert '/' not in self.app.db.threads
|
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):
|
class TestModeratedComments(unittest.TestCase):
|
||||||
|
Loading…
Reference in New Issue
Block a user