本文和大家分享的主要是tornado中cookie 驗證機制相關(guān)內(nèi)容,一起來看看吧,希望對大家
學習tornado有所幫助。
處理過程簡單來說就是驗證密碼之后服務器端(tornado) 返回帶有 cookie 信息的 Set-Cookie header 給客戶端 , 之后客戶端發(fā)起請求時會把此 cookie 放入 Cookie header 中發(fā)給服務器端。
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 字段的內(nèi)容 , 然后解析一下
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 和瀏覽器中傳遞的過程 . 當然這只是簡單的傳遞 , 很容易找到規(guī)律并進行暴力**進行提權(quán)攻擊 .
tornado 提供了加密的 cookie, cookie 的傳遞還是上述代碼 , 唯一的不同是在服務端對 cookie 的 value 進行了加密 , 這樣用戶即使知道其他用戶的名字 , 也無法在短時間內(nèi)構(gòu)造出一條正確的 cookie
tornado 在服務端需要自己定義一個 secret key. 一個加密的 cookie 的 value 由 (value, times**p, signature) 三元組組成 , 一般 value 可以通過加密算法構(gòu)造 , 而 times**p 則直接可以從現(xiàn)有的 cookie 里面直接取 , 所以最重要的是 signature 的構(gòu)造 , 由于用戶不知道 secret. 所以用戶無法通過算法構(gòu)造出 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
總結(jié)
函數(shù) _time_independent_equals 是很講究的。 它總是花費同樣的時間去比較用戶的輸入和你計算的結(jié)果。比如用戶想要暴力構(gòu)造一些 session, 如果比較函數(shù)花費的時間和 signature 前面 n 字節(jié)是否正確正 ( 或者負 ) 相關(guān)。那么變更 signature, 通過大量查看延時 , 理論上是能把 signature 暴力**出來的 , 而這個 _time_independent_equals 可以防止這種攻擊。
另外, tornado 這種校驗 cookie 的方式能夠天然解決 cookie 一致性的問題,可以方面的進行水平擴展。
來源: nosa.me