목차
개요
브라우저의 동작을 분석해야 될 때가 간혹 있다. 그중에서 cookie에 무슨 값이 들어있나를 보고 다시 코드 레벨에서 쿠키를 그대로 다뤄야 하는 경우가 종종 발생한다. 그런데 이런 상황을 마주할 때마다 어떤 cookie를 사용하는지 확인하고 이를 코드레벨에서 다시 작성하기 많이 번거롭다는 생각이 든다.
한 두 개라면 몰라도 쿠키에 들어있는 데이터가 너무 많다면 매번 복붙으로 해결하는 것도 한계가 있다. 그러한 이유로 Python을 이용해 Cookie 데이터를 얻어올 수 있는 방법은 무엇인지 조사했다.
1. 사용 중인 cookie를 확인하자.
사용 중인 cookie를 확인하려면 EditThisCookie라는 플러그인을 이용하던가 혹은 Javascript 콘솔에서 코드를 작성해야 한다. 하지만 이는 앞서 언급했듯 Python에서 다시 dict로 만들려면 귀찮은 작업이다.
그래서 간단히 사용 중인 브라우저를 기준으로 cookie가 어디에 저장되있는지 조사했다. 필자는 MacOS에서 Chrome을 사용하고 있기 때문에 이를 기반으로 조사한 결과 Cookie가 저장된 위치는 다음과 같았다.
/Users/jako/Library/Application\\ Support/Google/Chrome/Default/Cookies
-rw-------@ 1 jako staff 1933312 10 29 17:10 ~/Library/Application Support/Google/Chrome/Default/Cookies
위의 결과로 확인할 수 있듯 Chrome 브라우저에서는 사용 중인 Cookie는 ‘Cookies’라는 파일에 저장된다. 그렇다면 이는 어떤 파일일까?
2. Cookies 열어보기
처음엔 단순히 cat으로 열어봤지만 단순한 text 형식의 파일은 아니라는 걸 알게 되었다. 조사한 끝에 sqlite를 기반으로 열 수 있다는 걸 알 수 있게 되었다.
╰─$ sqlite3 ~/Library/Application\\ Support/Google/Chrome/Default/Cookies
sqlite는 데이터 베이스이기에 당연히 테이블이 존재할 것이라 유추했다.
sqlite> .tables
cookies meta
cookies와 meta라는 테이블이 나왔는데 아무래도 cookies라는 테이블이 cookie 정보를 저장하는 테이블일 듯싶다.
**sqlite> .schema --indent cookies**
CREATE TABLE cookies(
creation_utc INTEGER NOT NULL,
host_key TEXT NOT NULL,
top_frame_site_key TEXT NOT NULL,
name TEXT NOT NULL,
value TEXT NOT NULL,
encrypted_value BLOB NOT NULL,
path TEXT NOT NULL,
expires_utc INTEGER NOT NULL,
is_secure INTEGER NOT NULL,
is_httponly INTEGER NOT NULL,
last_access_utc INTEGER NOT NULL,
has_expires INTEGER NOT NULL,
is_persistent INTEGER NOT NULL,
priority INTEGER NOT NULL,
samesite INTEGER NOT NULL,
source_scheme INTEGER NOT NULL,
source_port INTEGER NOT NULL,
is_same_party INTEGER NOT NULL,
last_update_utc INTEGER NOT NULL
);
CREATE UNIQUE INDEX cookies_unique_index ON cookies(
host_key,
top_frame_site_key,
name,
path
);
cookies 테이블의 정보를 확인했을 때 cookie를 저장할 때 어떤 칼럼들을 사용하는지 확인할 수 있었다.
3. Python으로 Cookie 정보 가져오기
이제 Python으로 Cookie 정보에 접근할 수 있는 기반 환경은 갖춰졌다. python에서 sqlite를 이용할 수 있으므로 다음과 같이 접근하자.
import sqlite3
cookie_path = "~/Library/Application Support/Google/Chrome/Default/Cookies"
connection = sqlite3.connect(cookie_path)
cursor = connection.cursor()
query = 'SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value FROM cookies;'
cursor.execute(query)
for row in cursor.fetchall():
print(row[4])
connection.close()
위 코드는 cookie name을 가져오는 코드이다. 결과는 다음과 같다.
...
__Secure-1PSIDCC
__Secure-3PSIDCC
그런데 column을 살피던 도중 의아함이 들었다 value와 encrypted_value를 구분해 놨다는 점이 그러했다. value와 encrypt_value를 확인하면 다음과 같은 결과가 나온다.
====================
name= ar_debug
value=
encrypted_value= b'v10\\x1f..\\xf4R'
====================
name= __Secure-3PSIDCC
value=
encrypted_value= b'v10[...xf0d'
value에는 빈 값(empty value)이 들어있고 encrypted_value에는 bytes가 들어있는 걸로 봐서는 encrypted_value를 decrypted 시켜야 될 듯하다.
4. 어떻게 decrypted 할까?
사실 이 부분은 조사를 하지 못했다. encrypted_value가 어떤 암호와 알고리즘을 사용했는지 암호화 알고리즘에 필요한 키는 뭔지와 관련된 세부 사항들이 그러하다. 하지만 방법은 몰라도 해결할 수 있는 방법은 있었다. ‘browsercookie’라는 python 라이브러리의 내부 코드를 일부 가져다 사용하는 것이다.
pip install browsercookie
browser의 cookie 정보를 가져올 수 있게 미리 만들어둔 라이브러리 같은데 이 라이브러리의 내부 코드를 읽어보면서 몇 가지 부분을 참고해 다음과 같은 코드를 가져다 사용했다.
import keyring
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
def clean(decrypted_value):
last = decrypted_value[-1]
if isinstance(last, int):
return decrypted_value[:-last].decode('utf8')
else:
return decrypted_value[:-ord(last)].decode('utf8')
def decrypt(salt, length, mypass, iterations, encrypt_value):
key = PBKDF2(mypass, salt, length, iterations)
iv = b' ' * 16
cipher = AES.new(key, AES.MODE_CBC, IV=iv)
return cipher.decrypt(encrypt_value[3:])
for row in cursor.fetchall():
value = row[5]
encrypted_value = row[6]
print(
row[4],
row[5],
clean(decrypt(
salt=b'saltysalt',
length=16,
mypass=keyring.get_password('Chrome Safe Storage', 'Chrome'),
iterations=1003,
encrypt_value=row[6]
))
)
결과는 다음과 같다.
5. 마치며
python에서 chrome의 cookie 정보에 접근할 수 있는 것까지 다루었기에 이다음은 잘 이용해서 원하는 정보만 뽑아서 cookie를 만들면 된다. 그런데 위에는 지극히도 내 개인적인 환경에 맞춰져 있기 때문에 다소 제한적이다.
chrome cookie 정보만을 추출해 내는 것을 목적으로 했지만 safari와 같은 다른 브라우저의 쿠키는 decrypted 하는 또 다를 수 있기 때문이다. 만약 이 글을 참고로 python에서 cookie를 추출해 내는 작업을 염두에 두고 계신다면 환경을 잘 고려해보셨으면 한다.
'Language > Python' 카테고리의 다른 글
[SQLAlchemy] Imperative Mapping (0) | 2023.11.05 |
---|---|
[SQLAlchemy] SQL Logging 가독성 향상시키기 (0) | 2023.11.03 |
[SQLAlchemy] Raw SQL (1) | 2023.10.19 |
LangChain으로 크롤링을 해보자. (0) | 2023.10.17 |
pyenv를 알아보자. (0) | 2023.09.18 |