python提取瀏覽器Cookie

在用瀏覽器進行網頁訪問時,會向網頁所在的服務器發送http協議的GET或者POST等請求,在請求中除了指定所請求的方法以及URI以外,後面還跟隨着一段Request Header。Request Header的內容主要用於描述本地信息,如所用的瀏覽器、所用的系統、語言、所能接受的返回數據的編碼格式等,其中有一個很是重要的Header項就是Cookie,Cookie能夠說是網站的自定義數據集。因爲服務器端沒法沒法控制本地(瀏覽器)的內存數據,但服務器又有必要蒐集與本身所提供的服務相關的本地狀態信息,而Cookie就承載了這一功能,目的是記錄用戶在網站的狀態信息。html

在用python對網頁進行訪問的時候,若是但願獲得與在網頁端相同的結果,用該網頁在瀏覽器中所保留的Cookie做爲python的請求Cookie是一個比較值得推薦的作法。python

本文主要討論如何提取瀏覽器保存在本地的Cookie,所用的瀏覽器爲Chrome。git

 

 

Cookie文件

Chrome用sqlite來維護Cookie,Cookie中的信息被保存在sqlite數據庫當中,若是系統爲Windows,那麼數據庫文件所在的位置爲[1]github

C:\Users\{UserName}\AppData\Local\Google\Chrome\User Data\Default\Cookies

其中該路徑的{UserName}是當前系統的用戶名。sql

username = os.environ.get('USERNAME')
cookie_file = 'C:\Users\{UserName}\AppData\Local\Google\Chrome\User Data\Default\Cookies'.format(UserName=username)

 

 

Cookie表單

經過Cookie文件路徑,咱們能夠創建數據庫鏈接,而後提取出數據庫的信息。數據庫

con = sqlite3.connect(cookie_file)
cursor = con.cursor()

數據庫中的cookies表就是用於保存瀏覽器Cookie的。提取表中各列的名稱windows

cursor.execute('SELECT * FROM cookies')
for description in cursor.description:
    print(description[0])

各字段的描述以下api

  • creation_utc:Cookie產生的utc時間
  • host_key:Cookie所在的網頁(domain)
  • name:Cookie名稱
  • value:不加密的Cookie值,因爲Chrome幾乎都會對Cookie值加密後再存儲,所以這個字段基本都是空的
  • path:若是服務器須要設置Cookies,那麼服務器在響應瀏覽器請求的時候會返回Set-Cookie的響應,而且附帶所要設置的Cookies,這裏的path的默認值就是返回Set-Cookie的那個頁面。path以'/'爲開頭。
  • expires_utc:Cookie的有效期限
  • is_secure:指示在瀏覽器與服務器之間傳輸該Cookie時須要採用加密通道,即https
  • is_httponly:當設置了該值爲1時,在瀏覽器上運行的JS不能讀取到該Cookie,該Cookie只能由http請求讀取。這個標記主要目的是提升Cookie的安全性,防止無關的JS腳本竊取Cookie中的重要信息
  • last_access_utc:上一次訪問到該Cookie的時間
  • has_expires:Cookie的期限是否有效
  • is_persistent:若是expires_utc不爲0,那麼這個值爲1
  • priority:Cookie的刪除優先級,Cookie也有存儲上限的,當超出上限則須要刪除,此時會有特定的刪除策略來刪除不一樣priority的Cookie
  • encrypted_value:加密後的Cookie值
  • firstpartyonly:first-party以及third-party是HTTP Request的一種分類,first-party指的是當前所發送的HTTP請求的URL跟瀏覽器地址欄上的URL一致;不然就是third-party。如咱們日常看到的不少網頁上的圖片或者廣告,其實都是third-party request。不管first-party或者third-party,都是HTTP請求,在往服務器發送請求的時候會帶上host爲該URL的Cookies,不過若是一個Cookie指定了firstpartyonly,那麼若是請求爲thrid-party,在發送請求的時候不會附帶該Cookie。以上面所說的網頁中的圖片爲例子,若是該圖片URL的Cookie設定爲firstpartyonly,在瀏覽網站時,經過third-party訪問了該圖片所在的URL,就不會發送該Cookie。

 

上述字段中有些是隻有瀏覽器纔會用到的,這裏沒有必要用上,所以咱們僅須要提取一些必要字段瀏覽器

cursor.execute('SELECT host_key, name, value, path, expires_utc, is_secure, encrypted_value '
                        'FROM cookies WHERE host_key like "%{}%";'.format(domain_name))

 

 

解密Cookie

咱們前面說過,Chrome在對Cookie的值保存以前會進行加密處理,並保存在數據庫的encrypt_value字段中。在Windows系統中,Cookie加密採用的是系統提供的函數CryptProtectData,咱們在解密的時候也須要調用系統提供的函數CryptUnprotectData[1]。解密的Windows用戶必須與加密的用戶一致才能成功解密。安全

不過系統提供的是C函數,python經過ctypes庫來實現對C函數的調用。

CryptUnprotectData須要7個參數:

  • pDataIn:一個指向DATA_BLOB結構體的指針,該DATA_BLOB內需存放被解密的數據。DATA_BLOB結構體內含兩個成員:cbData,數據的所佔用的字節數;pbData,指向數據所在內存的指針。
  • ppszDataDescr:描述該加密數據的信息,若是在進行加密操做的時候添加了描述,那麼在解密的時候也能獲得該描述信息。得到該描述後須要調用系統提供的LocalFree釋放ppszDataDescr指向的內存。若是不須要,設爲NULL便可。
  • pOptionalEntropy:一個指向含有密鑰DATA_BLOB的指針,不過在進行Cookie加密時一般不會用到。
  • pvReserved:保留參數,設爲NULL便可。
  • pPromptStruct:解密是一個有安全風險的操做,可能須要彈出風險提高,若是不須要彈出提示設置爲NULL便可。
  • dwFlags:安全相關的標誌,設置爲0便可。
  • pDataOut:一個指向解密後的數據的DATA_BLOB,得到解密數據後須要調用系統提供的LocalFree函數釋放pbData指向的內存。
class DATA_BLOB(ctypes.Structure):
    _fields_ = [("cbData", ctypes.wintypes.DWORD),
                ("pbData", ctypes.POINTER(ctypes.c_char))]
    def __init__(self, data):
        string = str(data)
        self.cbData = len(string)
        self.pbData = ctypes.create_string_buffer(string)

def descrypt(cipher):
    #parameters
    DataIn = DATA_BLOB(cipher)
    Descr = ctypes.c_wchar_p()
    DataEntropy = DATA_BLOB('')
    Reserved = None
    PromptStruct = None
    CRYPTPROTECT_UI_FORBIDDEN = 0x00
    DataOut = DATA_BLOB('')
    #win call
    ret = ctypes.windll.crypt32.CryptUnprotectData(ctypes.byref(DataIn),
                                                    Descr, 
                                                    ctypes.byref(DataEntropy), 
                                                    Reserved, 
                                                    PromptStruct, 
                                                    CRYPTPROTECT_UI_FORBIDDEN, 
                                                    ctypes.byref(DataOut)
                                                    )
    if not ret:
        raise RuntimeError("failed to descrypt")

    buf = ctypes.create_string_buffer(int(DataOut.cbData))
    ctypes.memmove(buf, DataOut.pbData, DataOut.cbData)
    ctypes.windll.kernel32.LocalFree(Descr)    
    ctypes.windll.kernel32.LocalFree(DataOut.pbData)
    return buf.value

 

 

建立Cookie並填充CookieJar

python並不推薦咱們去自行建立Cookie,由於做爲用戶一般不須要去改動,甚至沒有必要知道Cookie的內容,不過此處出於特殊的需求,須要經過在Cookie數據庫中獲取的數據來建立Cookie。

首先咱們來簡單瞭解一下Set-Cookie。咱們知道Cookie都是服務器爲瀏覽器設置的,設置Cookie是經過服務器返回的Response Header,若是header中包含有Set-Cookiie相關字段,則能進行Cookie的設置。

http.cookiejar.Cookie的初始化須要提供18個參數[2]

  • version:比較老版本的Cookie(如rfc2109,已淘汰)要求Cookie必須有Version字段,不過較新的版本(如rfc6265)的Cookie去掉了這一字段,所以這裏填0就行。
  • name:Cookie名稱
  • value:Cookie值
  • port:http端口,指定了該字段的Cookie只能發送到服務器的指定端口,只有rfc2965中的Set-Cookie2纔用到該字段,rfc2695是一個已淘汰的版本,這裏填None就行。
  • port_specified:是否有指定端口,一樣是已淘汰的參數,填False。
  • domain:服務器域名
  • domain_specified:是否指定了服務器域名
  • domain_initial_dot:服務器域名是否以"."做爲開頭
  • path:服務器路徑
  • path_specified:是否指定了服務器路徑
  • secure:是否採用安全通道
  • expires:Cookie期限
  • discard:是否爲一次性Cookie,同上方的is_persistent,填False
  • comment:Cookie註釋,填None
  • comment_url:Cookie註釋所在的URL,填None
  • rest:存儲該Cookie的一些非標準的屬性,填{}
  • rfc2109:是否爲rfc2109標準,默認值爲False

 

    cj = http.cookiejar.CookieJar()
    for row in cursor.fetchall():
        host, name, value, path, expires, secure, encrypted_value = row[:]
        data = descrypt(encrypted_value)
        c = http.cookiejar.Cookie(0, name, data, None, False, host, host.startswith('.'), host.startswith('.'),
                                    path, True, secure, expires, False, None, None, {})
        cj.set_cookie(c)

 

其中比較重要的domain相關參數請查看:rfc2109的Interpreting Set-Cookie、Rejecting Cookies以及rfc6265的The Domain Attribute、Storage Model

 

Reference

  1. Client Side HTTP Cookie Security
  2. cookiejar.Cookie
相關文章
相關標籤/搜索