Skip to content

Instantly share code, notes, and snippets.

@hargup
Created August 27, 2024 17:16
Show Gist options
  • Save hargup/92154883d667bf8920fcebe4013a79cb to your computer and use it in GitHub Desktop.
Save hargup/92154883d667bf8920fcebe4013a79cb to your computer and use it in GitHub Desktop.
import http.cookiejar
import json
import os
import re
import shutil
import sqlite3
import subprocess
import sys
import tempfile
from cryptography.fernet import Fernet
# Supported browsers
SUPPORTED_BROWSERS = {
'chrome',
'chromium',
'brave',
'edge',
'opera',
'vivaldi',
'firefox',
'safari',
}
def get_cookie_database_path(browser_name, profile=None):
"""Get the path to the browser's cookie database."""
if browser_name not in SUPPORTED_BROWSERS:
raise ValueError(f'Unsupported browser: {browser_name}')
if sys.platform == 'win32':
appdata = os.environ['LOCALAPPDATA']
browser_dir = {
'chrome': r'Google\Chrome\User Data',
'chromium': r'Chromium\User Data',
'brave': r'BraveSoftware\Brave-Browser\User Data',
'edge': r'Microsoft\Edge\User Data',
'opera': r'Opera Software\Opera Stable',
'vivaldi': r'Vivaldi\User Data',
}[browser_name]
cookie_path = os.path.join(appdata, browser_dir, 'Default' if profile is None else profile, 'Cookies')
elif sys.platform == 'darwin':
appdata = os.path.expanduser('~/Library/Application Support')
browser_dir = {
'chrome': 'Google/Chrome',
'chromium': 'Chromium',
'brave': 'BraveSoftware/Brave-Browser',
'edge': 'Microsoft Edge',
'opera': 'com.operasoftware.Opera',
'vivaldi': 'Vivaldi',
'firefox': 'Firefox',
}[browser_name]
cookie_path = os.path.join(appdata, browser_dir, 'Default' if profile is None else profile, 'Cookies')
else:
config_home = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
browser_dir = {
'chrome': 'google-chrome',
'chromium': 'chromium',
'brave': 'BraveSoftware/Brave-Browser',
'edge': 'microsoft-edge',
'opera': 'opera',
'vivaldi': 'vivaldi',
}[browser_name]
cookie_path = os.path.join(config_home, browser_dir, 'Default' if profile is None else profile, 'Cookies')
if not os.path.exists(cookie_path):
raise FileNotFoundError(f'Cookie database not found: {cookie_path}')
return cookie_path
def extract_cookies_from_chrome(browser_name, profile=None):
"""Extract cookies from a Chromium-based browser."""
cookie_path = get_cookie_database_path(browser_name, profile)
# Read encryption key
if sys.platform == 'linux':
try:
# Path to local state file
local_state_path = os.path.join(os.path.dirname(cookie_path), 'Local State')
with open(local_state_path, 'r', encoding='utf-8') as f:
local_state = json.load(f)
encrypted_key = local_state['os_crypt']['encrypted_key']
# Decode the encrypted key
encrypted_key = base64.b64decode(encrypted_key)
# Remove the DPAPI prefix
encrypted_key = encrypted_key[5:]
# Decrypt the key using the keyring
key = subprocess.check_output(
['secret-tool', 'lookup', 'service', f'{browser_name} Safe Storage'],
stderr=subprocess.DEVNULL,
)
# Decrypt the cookies
fernet = Fernet(key)
decrypted_key = fernet.decrypt(encrypted_key)
except (FileNotFoundError, subprocess.CalledProcessError):
print(
f'Warning: Could not decrypt cookies from {browser_name}. '
'Make sure you have the `libsecret` package installed and '
f'the {browser_name} Safe Storage password saved in the keyring.',
file=sys.stderr,
)
return http.cookiejar.MozillaCookieJar() # Change this line
with tempfile.TemporaryDirectory() as tmpdir:
# Copy the cookie database to a temporary file
temp_cookie_path = os.path.join(tmpdir, 'Cookies.db')
shutil.copyfile(cookie_path, temp_cookie_path)
# Open the database and extract cookies
conn = sqlite3.connect(temp_cookie_path)
cursor = conn.cursor()
cursor.execute(
'SELECT host_key, name, value, path, expires_utc, is_secure, encrypted_value '
'FROM cookies'
)
jar = http.cookiejar.MozillaCookieJar() # Change this line
for row in cursor.fetchall():
host, name, value, path, expires, secure, encrypted_value = row
# Decrypt the cookie value if necessary
if encrypted_value:
if sys.platform == 'linux':
try:
value = fernet.decrypt(encrypted_value).decode()
except Exception:
print(f'Warning: Failed to decrypt cookie {name} for {host}', file=sys.stderr)
continue
elif sys.platform == 'win32':
# Decrypt using Win32CryptProtectData
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
encrypted_value = encrypted_value[3:] # Remove DPAPI prefix
cipher = Cipher(algorithms.AES(decrypted_key), modes.CBC(b'\0' * 16), backend=default_backend())
decryptor = cipher.decryptor()
padded_data = decryptor.update(encrypted_value) + decryptor.finalize()
unpadder = PKCS7(128).unpadder()
value = unpadder.update(padded_data) + unpadder.finalize()
value = value.decode()
elif sys.platform == 'darwin':
# Decrypt using macOS Security framework
try:
value = subprocess.check_output(
[
'security',
'find-generic-password',
'-wa',
f'{browser_name} Safe Storage',
'-s',
f'{host} {name} {path}',
],
stderr=subprocess.DEVNULL,
).decode().strip()
except subprocess.CalledProcessError:
print(f'Warning: Failed to decrypt cookie {name} for {host}', file=sys.stderr)
continue
# Create a cookie object
cookie = http.cookiejar.Cookie(
version=0,
name=name,
value=value,
port=None,
port_specified=False,
domain=host,
domain_specified=True,
domain_initial_dot=host.startswith('.'),
path=path,
path_specified=True,
secure=secure,
expires=expires / 1000000 - 11644473600 if expires else None, # Convert Webkit timestamp to Unix timestamp
discard=False,
comment=None,
comment_url=None,
rest={},
)
jar.set_cookie(cookie)
conn.close()
return jar
def extract_cookies_from_firefox(profile=None):
"""Extract cookies from Firefox."""
if sys.platform == 'darwin':
firefox_dir = os.path.expanduser('~/Library/Application Support/Firefox/Profiles')
else:
firefox_dir = os.path.join(os.path.expanduser('~'), '.mozilla', 'firefox')
# print(firefox_dir)
if profile is None:
profiles = [d for d in os.listdir(firefox_dir) if os.path.isdir(os.path.join(firefox_dir, d))]
# print(f"Profiles: {profiles}")
if not profiles:
raise FileNotFoundError('No Firefox profiles found')
# Find the most recently updated profile
profile = max(profiles, key=lambda p: os.path.getmtime(os.path.join(firefox_dir, p)))
cookie_path = os.path.join(firefox_dir, profile, 'cookies.sqlite')
if not os.path.exists(cookie_path):
raise FileNotFoundError(f'Firefox cookie database not found: {cookie_path}')
with tempfile.TemporaryDirectory() as tmpdir:
temp_cookie_path = os.path.join(tmpdir, 'cookies.sqlite')
shutil.copyfile(cookie_path, temp_cookie_path)
conn = sqlite3.connect(temp_cookie_path)
cursor = conn.cursor()
cursor.execute(
'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies'
)
jar = http.cookiejar.MozillaCookieJar() # Change this line
for row in cursor.fetchall():
host, name, value, path, expiry, secure = row
cookie = http.cookiejar.Cookie(
version=0,
name=name,
value=value,
port=None,
port_specified=False,
domain=host,
domain_specified=True,
domain_initial_dot=host.startswith('.'),
path=path,
path_specified=True,
secure=secure,
expires=expiry,
discard=False,
comment=None,
comment_url=None,
rest={},
)
jar.set_cookie(cookie)
conn.close()
return jar
def extract_cookies_from_safari():
"""Extract cookies from Safari."""
cookie_path = os.path.expanduser('~/Library/Cookies/Cookies.binarycookies')
if not os.path.exists(cookie_path):
raise FileNotFoundError(f'Safari cookie database not found: {cookie_path}')
# Parsing binary cookies is not trivial, so use a third-party library
# Install with: pip install browser-cookie3
try:
import browser_cookie3
temp_jar = browser_cookie3.safari()
jar = http.cookiejar.MozillaCookieJar()
for cookie in temp_jar:
jar.set_cookie(cookie)
except ImportError:
raise ImportError(
'Could not extract cookies from Safari because browser-cookie3 is not installed. '
'Install with: pip install browser-cookie3'
)
return jar
def save_cookies_to_file(jar, filename):
"""Save cookies to a file."""
jar.save(filename, ignore_discard=True, ignore_expires=True)
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python copy_cookies.py <browser> <output_file> [profile]', file=sys.stderr)
sys.exit(1)
browser_name = sys.argv[1].lower()
output_file = sys.argv[2]
profile = sys.argv[3] if len(sys.argv) > 3 else None
if browser_name in ('chrome', 'chromium', 'brave', 'edge', 'opera', 'vivaldi'):
jar = extract_cookies_from_chrome(browser_name, profile)
elif browser_name == 'firefox':
jar = extract_cookies_from_firefox(profile)
elif browser_name == 'safari':
jar = extract_cookies_from_safari()
else:
raise ValueError(f'Unsupported browser: {browser_name}')
save_cookies_to_file(jar, output_file)
print(f'Saved cookies to {output_file}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment