前回の記事で、Cookieとはサーバー側がクライアント側に与える名前のようなものだ、と述べた。
今回はCookieがどのように発行されるのか、や
実際のPCでどのように管理されているか、などといった
Cookieの実体に迫ってみようと思う。
まず自分が現在どのようなCookieを保持しているのかをエクスチュアのページを例に確認してみよう。
Chromeでは
設定 → プライバシー → コンテンツの設定 → Cookieとサイトデータ
から簡単に確認ができる。
このようにエクスチュアでは14個のクッキーを発行しているようだ。
さて、これらのCookieが実際に発行される様子を見てみよう。
CookieはHTTPリクエストヘッダ内のCookie項目で宣言されなかった場合、
レスポンスヘッダ内のSet-Cookie項目で発行される。
したがって、先ほどの14個のCookieを削除した状態で、エクスチュアのサイトにアクセスすれば、
新たにCookieが発行されるはずである。
実際にやってみた様子がこちら。
このようにCookieが新たに発行され、Chromeの管理下に追加されたことが確認できる。
この後にページをリロードしてみると、先ほど発行されたばかりのCookieが、
リクエストヘッダのCookie項目で宣言されていることも確認できた。
さて、このようにCookieがHTTPレスポンスヘッダで発行され、
リクエストヘッダで宣言されていることが分かった。
ところでChromeは今までに発行されたCookieの情報をどこに記憶しているのだろうか。
調べたところ、windowsでは%UserProfile%配下の
/AppData/Local/Google/Chrome/User Data/Default
にCookiesという名前のSqliteファイルとして管理されていることが分かった。
せっかくなので実際にSqlite3を操作して、中身を覗いてみよう。
Cookieはcookieというテーブルで管理されており、
host_keyというカラムでそのCookieが発火するタイミングを指定しているようなので、
エクスチュアのページで送られるCookieを抽出してみたところ、
先ほど発行されたCookie(らしきもの)が確認できた。
しかし残念なことにvalueカラムが表示されず、これが本当に先ほど発行されたCookieなのか確信が持てない。
どうやらChrome33.0からvalueを暗号化して、encrypted_valueカラムに登録するようになったらしく、
これを復号化しなければvalueが手に入らないようだ。
どのようにして複合化すれば良いのか。
Googleの力を借りてcookie-issue-with-chrome-33-betaというページにたどり着くことができた。
どうやらWindowsにおけるCookieの暗号化は、
Windows OSが提供しているDPAPIという暗号および複合化用のAPIを使って行われているらしい。
この場合、暗号化時と同じユーザーで実行されているアプリケーションによって
複合化することが可能なようだ。上記のサイトのPythonスクリプトは、
ctypesという動的ライブラリ(dll)を呼ぶための標準ライブラリを使ってDPAPIを操作し、複合化を行っている。
したがってこのPythonスクリプトに少しだけ変更を加え、
host_Keyカラムが”.ex-ture.com”、 nameカラムが”__cfduid”、となっているレコードのencrypted_valueカラムを複合化して表示するスクリプト、CookieDecrypt.pyを書いた。
from ctypes import * from ctypes.wintypes import DWORD import sqlite3; class DATA_BLOB(Structure): _fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))]; def getData(blobOut): cbData = int(blobOut.cbData); pbData = blobOut.pbData; buffer = c_buffer(cbData); memcpy(buffer, pbData, cbData); LocalFree(pbData); return buffer.raw; def decrypt(cipherText): bufferIn = c_buffer(cipherText, len(cipherText)); blobIn = DATA_BLOB(len(cipherText), bufferIn); blobOut = DATA_BLOB(); if CryptUnprotectData(byref(blobIn), None, None, None, None, CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)): return getData(blobOut); else: raise Exception("Failed to decrypt data"); LocalFree = windll.kernel32.LocalFree; memcpy = cdll.msvcrt.memcpy; CryptProtectData = windll.crypt32.CryptProtectData; CryptUnprotectData = windll.crypt32.CryptUnprotectData; CRYPTPROTECT_UI_FORBIDDEN = 0x01; cookieFile="C:/Users/mitsuiki/AppData/Local/Google/Chrome/User Data/Default/Cookies" hostKey=".ex-ture.com"; name="__cfduid" conn = sqlite3.connect(cookieFile); c = conn.cursor(); c.execute("""\ SELECT host_key, name, path, value, encrypted_value FROM cookies WHERE host_key = '{0}' and name='{1}' ; """.format(hostKey,name)); cookie = c.fetchone(); c.close() print( \ """ host_key: {0} name: {1} path: {2} value: {3} encrpyted_value(decrypted): {4} """.format(cookie[0], cookie[1], cookie[2], cookie[3], decrypt(cookie[4])));
このスクリプトの実行結果が次。
無事復号化に成功し、このレコードが先ほど発行されたCookieであると判明した。
インターン生 薄井