python tornado下的csrf怎麼玩?

0、
什麼是CSRF,就是跨站僞造請求。

惡意Web B讓用戶去訪問Web A,比如讓它刪除自己的好友信息。
因爲用戶是帶了A的正式的cookie過去的,所以Web A不知道用戶的刪除好友操作是壞人所爲,就乖乖刪除了好友。


1、
怎麼防範-1?
用Referer字段咯。畢竟壞請求只能從Web B上發送出來,如果校驗Referer字段的話,那麼這個字段一定不是Web A從而防範成功。

2、
怎麼防範-2?
用token,服務器給http請求返回一個token(可以放到cookie中給客戶端瀏覽器),並要求Web A提交的請求必須帶上token。
因爲Web B中是絕對拿不到Web A的cookie的。。
所以把token放到Web A的cookie中,請求的時候帶給服務器,由服務器去驗證即可。

3、
具體tornado怎麼搞?
先在app設置裏面加一個xsrf_cookies = True。
app_settings = dict(
            template_path=os.path.join(os.path.dirname(__file__), "templates"),
            static_path=os.path.join(os.path.dirname(__file__), "static"),
            debug=False,
            login_url="/app_h5/backend/login",
            cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
            compiled_template_cache=False,
            xsrf_cookies=True,
        )
然後每一個post請求都要帶上cookies['_xsrf']上來。否則就給403.

4、
其實還是挺好奇tornado做了什麼的。
爲啥只要開啓xsrf_cookies=True,然後就會生成一個token,然後瀏覽器帶上這個token,服務器就能判斷這個token有效。
猜測:這說明token是可以解碼的!!!
看看源碼怎麼實現的:

4.0
先看看_xsrf長什麼樣子,類似:2|b5a0dd74|b54547002d496a9844ca63fc4bda38ab|1508209078

4.1
看文檔發現主要是RequestHandler.check_xsrf_cookie()在處理這個token的判斷邏輯。
def check_xsrf_cookie(self):
    token = (self.get_argument("_xsrf", None) or
             self.request.headers.get("X-Xsrftoken") or
             self.request.headers.get("X-Csrftoken"))
    if not token:   #### 不帶token就是403
        raise HTTPError(403, "'_xsrf' argument missing from POST")
    _, token, _ = self._decode_xsrf_token(token)  ##### 解析token,果然是可以解碼的!!
    _, expected_token, _ = self._get_raw_xsrf_token() 
    if not token:   #### 解碼失敗就是403
        raise HTTPError(403, "'_xsrf' argument has invalid format")
    if not _time_independent_equals(utf8(token), utf8(expected_token)): #兩個token是否相等
        raise HTTPError(403, "XSRF cookie does not match POST argument")
那麼看看怎麼個解碼法:self._decode_xsrf_token()
    def _decode_xsrf_token(self, cookie):
        try:
            # _signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$”)
            # 能匹配類似 2112312|fhdaheajdshfjkasdf這樣子的字符串。
            # 對應我們的_xsrf=2|b5a0dd74|b54547002d496a9844ca63fc4bda38ab|1508209078
                             # 那麼m.group(1) == 2
            m = _signed_value_version_re.match(utf8(cookie))  ### 
            
            if m:
                version = int(m.group(1))
                if version == 2:
                    # _=2
                                                 # mask=b5a0dd74
                                                  # masked_token=b54547002d496a9844ca63fc4bda38ab
                                                 # timestamp = 1508209078
                    _, mask, masked_token, timestamp = cookie.split("|")

                    mask = binascii.a2b_hex(utf8(mask))  ### '\xb5\xa0\xddt'
                    token = _websocket_mask(
                        mask, binascii.a2b_hex(utf8(masked_token)))  
                    # '\xb5EG\x00-Ij\x98D\xcac\xfcK\xda8\xab’

                            
                    timestamp = int(timestamp)
                    return version, token, timestamp
                else:
                    # Treat unknown versions as not present instead of failing.
                    raise Exception("Unknown xsrf cookie version")
            else:
                version = 1
                try:
                    token = binascii.a2b_hex(utf8(cookie))
                except (binascii.Error, TypeError):
                    token = utf8(cookie)
                # We don't have a usable timestamp in older versions.
                timestamp = int(time.time())
                return (version, token, timestamp)
        except Exception:
            # Catch exceptions and return nothing instead of failing.
            gen_log.debug("Uncaught exception in _decode_xsrf_token",
                          exc_info=True)
            return None, None, None
因爲version==2,進入一個分支:
                if version == 2:
                    ### _=2
                    ### mask=b5a0dd74
                    ### masked_token=b54547002d496a9844ca63fc4bda38ab
                    ### timestamp = 1508209078
                    _, mask, masked_token, timestamp = cookie.split("|")
                    
                    ### mask='\xb5\xa0\xddt'
                    mask = binascii.a2b_hex(utf8(mask))  

                    ### masked_token='\xb5EG\x00-Ij\x98D\xcac\xfcK\xda8\xab’
                    ### token='\x00\xe5\x9at\x98\xe9\xb7\xec\xf1j\xbe\x88\xfez\xe5\xdf'
                    token = _websocket_mask(
                        mask, binascii.a2b_hex(utf8(masked_token)))  
            
                    timestamp = int(timestamp)
                    return version, token, timestamp
然後就返回version,token,timestamp。

總結下:
1)、
我們用POST參數裏面的_xsrf值,通過self._decode_xsrf_token()過程,得到version1,token1,timestamp1。
2)、
然後重複這個self._decode_xsrf_token()過程,只不過這次的_xsrf是來自cookie。
得到version2,token2,timestamp2。
3)、
判斷兩個version1==version2, token1==token2, timestamp1 == timestamp2,如果不等則403。

以上




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章