本文和大家分享的主要是tornado中cookie 验证机制相关内容,一起来看看吧,希望对大家
学习tornado有所帮助。
处理过程简单来说就是验证密码之后服务器端(tornado) 返回带有 cookie 信息的 Set-Cookie header 给客户端 , 之后客户端发起请求时会把此 cookie 放入 Cookie header 中发给服务器端。
tornado 设置 cookie
首先是对 cookie 的变量进行设置 , Morsel 是含有几个特殊 key 的类似于 dict 的对象
def set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None):
if not hasattr(self, "_new_cookie"):
self._new_cookie = Cookie.SimpleCookie()
self._new_cookie[name] = value
morsel = self._new_cookie[name]
if domain:
morsel["domain"] = domain
if expires_days is not None and not expires:
expires = datetime.datetime.utcnow() + datetime.timedelta(
days=expires_days)
if expires:
morsel["expires"] = httputil.format_times**p(expires)
if path:
morsel["path"] = path
然后将 cookie 的 header flush 给客户端
def flush(self, include_footers=False, callback=None):
...
if hasattr(self, "_new_cookie"):
for cookie in self._new_cookie.values():
self.add_header("Set-Cookie", cookie.OutputString( None))
...
return self.request.connection.write_headers(
start_line, self._headers, chunk, callback=callback)
...
torando 读取 cookie 并验证
tornado 从浏览器那获取 cookie 则特别简单,直接取出 header 中 Cookie 字段的内容 , 然后解析一下
def cookies(self):
if not hasattr(self, "_cookies"):
self._cookies = Cookie.SimpleCookie()
if "Cookie" in self.headers:
try:
parsed = parse_cookie(self.headers["Cookie"])
except Exception:
pass
else:
for k, v in parsed.items():
try:
self._cookies[k] = v
except Exception:
pass
return self._cookies
以上代码就是 cookie 在 server 和浏览器中传递的过程 . 当然这只是简单的传递 , 很容易找到规律并进行暴力**进行提权攻击 .
tornado 提供了加密的 cookie, cookie 的传递还是上述代码 , 唯一的不同是在服务端对 cookie 的 value 进行了加密 , 这样用户即使知道其他用户的名字 , 也无法在短时间内构造出一条正确的 cookie
tornado 在服务端需要自己定义一个 secret key. 一个加密的 cookie 的 value 由 (value, times**p, signature) 三元组组成 , 一般 value 可以通过加密算法构造 , 而 times**p 则直接可以从现有的 cookie 里面直接取 , 所以最重要的是 signature 的构造 , 由于用户不知道 secret. 所以用户无法通过算法构造出 signature, 只能窃取或者通过暴力** . 而 服务端则能够通过 cookie 确认 value 是正常的 value 且能够取出 value 里包含的信息
加密代码
def create_signed_value(secret, name, value):
clock = time.time
times**p = utf8(str(int(clock())))
value = base64.b64encode(utf8(value))
signature = _create_signature_v1(secret, name, value, times**p)
value = b"|".join([value, times**p, signature])
return value
def _create_signature_v1(secret, *parts):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
for part in parts:
hash.update(utf8(part))
return utf8(hash.hexdigest())
解密代码
def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
parts = utf8(value).split(b"|")
signature = _create_signature_v1(secret, name, parts[0], parts[1])
if not _time_independent_equals(parts[2], signature):
return None
clock = time.time
times**p = int(parts[1])
if times**p < clock() - max_age_days * 86400:
gen_log.warning("Expired cookie %r", value)
return None
return base64.b64decode(parts[0])
def _time_independent_equals(a, b):
for x, y in zip(a, b):
result |= ord(x) ^ ord(y)
return result == 0
总结
函数 _time_independent_equals 是很讲究的。 它总是花费同样的时间去比较用户的输入和你计算的结果。比如用户想要暴力构造一些 session, 如果比较函数花费的时间和 signature 前面 n 字节是否正确正 ( 或者负 ) 相关。那么变更 signature, 通过大量查看延时 , 理论上是能把 signature 暴力**出来的 , 而这个 _time_independent_equals 可以防止这种攻击。
另外, tornado 这种校验 cookie 的方式能够天然解决 cookie 一致性的问题,可以方面的进行水平扩展。
来源: nosa.me