Update Tornado 3.2.2
This commit is contained in:
+84
-8
@@ -72,7 +72,6 @@ import time
|
||||
import tornado
|
||||
import traceback
|
||||
import types
|
||||
import uuid
|
||||
|
||||
from tornado.concurrent import Future, is_future
|
||||
from tornado import escape
|
||||
@@ -84,7 +83,7 @@ from tornado.log import access_log, app_log, gen_log
|
||||
from tornado import stack_context
|
||||
from tornado import template
|
||||
from tornado.escape import utf8, _unicode
|
||||
from tornado.util import bytes_type, import_object, ObjectDict, raise_exc_info, unicode_type
|
||||
from tornado.util import bytes_type, import_object, ObjectDict, raise_exc_info, unicode_type, _websocket_mask
|
||||
|
||||
try:
|
||||
from io import BytesIO # python 3
|
||||
@@ -1076,16 +1075,87 @@ class RequestHandler(object):
|
||||
as a potential forgery.
|
||||
|
||||
See http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
|
||||
.. versionchanged:: 3.2.2
|
||||
The xsrf token will now be have a random mask applied in every
|
||||
request, which makes it safe to include the token in pages
|
||||
that are compressed. See http://breachattack.com for more
|
||||
information on the issue fixed by this change. Old (version 1)
|
||||
cookies will be converted to version 2 when this method is called
|
||||
unless the ``xsrf_cookie_version`` `Application` setting is
|
||||
set to 1.
|
||||
"""
|
||||
if not hasattr(self, "_xsrf_token"):
|
||||
token = self.get_cookie("_xsrf")
|
||||
if not token:
|
||||
token = binascii.b2a_hex(uuid.uuid4().bytes)
|
||||
version, token, timestamp = self._get_raw_xsrf_token()
|
||||
output_version = self.settings.get("xsrf_cookie_version", 2)
|
||||
if output_version == 1:
|
||||
self._xsrf_token = binascii.b2a_hex(token)
|
||||
elif output_version == 2:
|
||||
mask = os.urandom(4)
|
||||
self._xsrf_token = b"|".join([
|
||||
b"2",
|
||||
binascii.b2a_hex(mask),
|
||||
binascii.b2a_hex(_websocket_mask(mask, token)),
|
||||
utf8(str(int(timestamp)))])
|
||||
else:
|
||||
raise ValueError("unknown xsrf cookie version %d",
|
||||
output_version)
|
||||
if version is None:
|
||||
expires_days = 30 if self.current_user else None
|
||||
self.set_cookie("_xsrf", token, expires_days=expires_days)
|
||||
self._xsrf_token = token
|
||||
self.set_cookie("_xsrf", self._xsrf_token,
|
||||
expires_days=expires_days)
|
||||
return self._xsrf_token
|
||||
|
||||
def _get_raw_xsrf_token(self):
|
||||
"""Read or generate the xsrf token in its raw form.
|
||||
|
||||
The raw_xsrf_token is a tuple containing:
|
||||
|
||||
* version: the version of the cookie from which this token was read,
|
||||
or None if we generated a new token in this request.
|
||||
* token: the raw token data; random (non-ascii) bytes.
|
||||
* timestamp: the time this token was generated (will not be accurate
|
||||
for version 1 cookies)
|
||||
"""
|
||||
if not hasattr(self, '_raw_xsrf_token'):
|
||||
cookie = self.get_cookie("_xsrf")
|
||||
if cookie:
|
||||
version, token, timestamp = self._decode_xsrf_token(cookie)
|
||||
else:
|
||||
version, token, timestamp = None, None, None
|
||||
if token is None:
|
||||
version = None
|
||||
token = os.urandom(16)
|
||||
timestamp = time.time()
|
||||
self._raw_xsrf_token = (version, token, timestamp)
|
||||
return self._raw_xsrf_token
|
||||
|
||||
def _decode_xsrf_token(self, cookie):
|
||||
"""Convert a cookie string into a the tuple form returned by
|
||||
_get_raw_xsrf_token.
|
||||
"""
|
||||
m = _signed_value_version_re.match(utf8(cookie))
|
||||
if m:
|
||||
version = int(m.group(1))
|
||||
if version == 2:
|
||||
_, mask, masked_token, timestamp = cookie.split("|")
|
||||
mask = binascii.a2b_hex(utf8(mask))
|
||||
token = _websocket_mask(
|
||||
mask, binascii.a2b_hex(utf8(masked_token)))
|
||||
timestamp = int(timestamp)
|
||||
return version, token, timestamp
|
||||
else:
|
||||
# Treat unknown versions as not present instead of failing.
|
||||
return None, None, None
|
||||
elif len(cookie) == 32:
|
||||
version = 1
|
||||
token = binascii.a2b_hex(utf8(cookie))
|
||||
# We don't have a usable timestamp in older versions.
|
||||
timestamp = int(time.time())
|
||||
return (version, token, timestamp)
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
def check_xsrf_cookie(self):
|
||||
"""Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
|
||||
|
||||
@@ -1106,13 +1176,19 @@ class RequestHandler(object):
|
||||
information please see
|
||||
http://www.djangoproject.com/weblog/2011/feb/08/security/
|
||||
http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
|
||||
|
||||
.. versionchanged:: 3.2.2
|
||||
Added support for cookie version 2. Both versions 1 and 2 are
|
||||
supported.
|
||||
"""
|
||||
token = (self.get_argument("_xsrf", None) or
|
||||
self.request.headers.get("X-Xsrftoken") or
|
||||
self.request.headers.get("X-Csrftoken"))
|
||||
if not token:
|
||||
raise HTTPError(403, "'_xsrf' argument missing from POST")
|
||||
if self.xsrf_token != token:
|
||||
_, token, _ = self._decode_xsrf_token(token)
|
||||
_, expected_token, _ = self._get_raw_xsrf_token()
|
||||
if not _time_independent_equals(utf8(token), utf8(expected_token)):
|
||||
raise HTTPError(403, "XSRF cookie does not match POST argument")
|
||||
|
||||
def xsrf_form_html(self):
|
||||
|
||||
Reference in New Issue
Block a user