python提取瀏覽器Cookie


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

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

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

 

 

Cookie文件

Chrome用sqlite來維護Cookie,Cookie中的信息被保存在sqlite數據庫當中,如果系統為Windows,那么數據庫文件所在的位置為[1]

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

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

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的。提取表中各列的名稱

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

各字段的描述如下

  • 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM