Safari
Safari Cookies
Overview
Safari stores browser cookies in a proprietary binary format called Cookies.binarycookies. This artifact contains authentication tokens, session identifiers, tracking cookies, and user preference cookies set by websites. For forensic investigations, cookies can prove that a user authenticated to a specific service, reveal tracking relationships between sites, and establish the timeframes during which specific web sessions were active.
Cookie values are sensitive -- they may contain active session tokens that could be used for unauthorised access. Forensic tools should redact cookie values in reports and handle the raw file with appropriate security controls.
File Locations
| File | Path | Format | Notes |
|---|---|---|---|
| Cookies (legacy) | ~/Library/Cookies/Cookies.binarycookies | Binary | Pre-macOS 10.14 |
| Cookies (sandboxed) | ~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies | Binary | macOS 10.14 (Mojave) and later |
Starting with macOS 10.14, Safari runs in a sandboxed container and stores cookies in the container path. The legacy path may still exist on older systems or systems upgraded from earlier versions. Both paths should be checked during collection.
Binary File Format
Cookies.binarycookies uses a custom binary format that must be parsed with a dedicated parser. The file is not a standard database or property list.
File Structure
+------------------------------------+
| Magic: "cook" (4 bytes) |
| Number of Pages: uint32 BE |
| Page Sizes: [uint32 BE] x N |
+------------------------------------+
| Page 0 |
| Page Header: 00 00 01 00 |
| Number of Cookies: uint32 LE |
| Cookie Offsets: [uint32 LE] x N |
| End Marker: 0x00000000 |
| Cookie Records |
+------------------------------------+
| Page 1 ... |
+------------------------------------+
| Checksum (8 bytes) |
+------------------------------------+
File Header
| Offset | Size | Endianness | Field |
|---|---|---|---|
| 0 | 4 | -- | Magic bytes: cook (ASCII) |
| 4 | 4 | Big-endian | Number of pages |
| 8 | 4*N | Big-endian | Array of page sizes (one uint32 per page) |
Page Header
| Offset | Size | Endianness | Field |
|---|---|---|---|
| 0 | 4 | Little-endian | Page header magic: 0x00010000 |
| 4 | 4 | Little-endian | Number of cookies in this page |
| 8 | 4*N | Little-endian | Array of cookie offsets within the page |
| 8+4*N | 4 | Little-endian | End marker: 0x00000000 |
Cookie Record
Each cookie record begins at the offset specified in the page header.
| Offset | Size | Endianness | Field |
|---|---|---|---|
| 0 | 4 | Little-endian | Record size in bytes |
| 4 | 4 | Little-endian | Flags (see below) |
| 8 | 4 | Little-endian | Domain/URL string offset |
| 12 | 4 | Little-endian | Name string offset |
| 16 | 4 | Little-endian | Path string offset |
| 20 | 4 | Little-endian | Value string offset |
| 24 | 8 | -- | End header marker (zeros) |
| 32 | 8 | Little-endian | Expiry time (float64, Core Data timestamp) |
| 40 | 8 | Little-endian | Creation time (float64, Core Data timestamp) |
| 48+ | Var | -- | Null-terminated strings: domain, name, path, value |
Cookie Flags
| Bit Mask | Flag | Description |
|---|---|---|
0x0001 | Secure | Cookie is only sent over HTTPS connections |
0x0004 | HttpOnly | Cookie is not accessible to client-side JavaScript |
Key Fields for Analysis
- Domain: The domain the cookie belongs to (e.g.,
.github.com). Leading dots indicate the cookie applies to all subdomains. - Name: The cookie name. Well-known names like
sessionid,JSESSIONID,__cfduid,_gacan identify the type of service or tracking system. - Path: The URL path scope for the cookie.
/means the cookie applies to the entire domain. - Flags (Secure/HttpOnly): Security-relevant flags. The absence of
Secureon authentication cookies is a security concern.HttpOnlyprevents JavaScript access. - Expiry time: When the cookie expires. Session cookies have a zero or past expiry time. Persistent cookies with far-future expiry dates are typically tracking cookies.
- Creation time: When the cookie was first set. Establishes when the user first interacted with the service.
- Value: Contains the actual cookie data (session tokens, tracking IDs, preferences). This should be treated as sensitive and redacted in reports.
Timestamps
Cookie timestamps (creation and expiry) are stored as Core Data timestamps -- IEEE 754 double-precision floating-point values representing seconds since 2001-01-01 00:00:00 UTC.
Conversion formula:
Unix timestamp = Core Data timestamp + 978307200
Python conversion:
import struct
from datetime import datetime, timezone, timedelta
COREDATA_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc)
def coredata_to_datetime(ts):
return COREDATA_EPOCH + timedelta(seconds=ts)
# Read float64 from bytes (little-endian)
expiry_bytes = data[32:40]
expiry_float = struct.unpack('<d', expiry_bytes)[0]
expiry_dt = coredata_to_datetime(expiry_float)
Analysis Notes
- Authentication evidence: Cookies with names like
session,auth_token,sid, or service-specific names (e.g.,SAPISIDfor Google) prove the user authenticated to those services. - Tracking cookies: Cookies from ad networks and analytics providers (domains like
doubleclick.net,facebook.com, Google Analytics_ga/_gid) reveal cross-site tracking. - Timestamp analysis: Compare cookie creation times with browsing history to corroborate visit timelines. Cookies set at times not reflected in history may indicate private browsing or cleared history.
- Expired vs active sessions: Cookies with future expiry dates represent potentially active sessions. Expired cookies indicate past sessions.
- Domain analysis: Group cookies by domain to identify all services the user has interacted with. This can reveal accounts on services not obvious from history alone.
- Cleared cookies: If
Cookies.binarycookiesis unusually small or missing, the user may have cleared their cookies. Compare the file modification timestamp with expected browsing activity. - Privacy-sensitive: Cookie values may contain active authentication tokens. Handle the raw binary file with the same security as credentials.
Version Differences
| Version | Change |
|---|---|
| macOS 10.12-10.13 | Cookies stored at ~/Library/Cookies/Cookies.binarycookies |
| macOS 10.14 (Mojave)+ | Cookies moved to sandboxed container: ~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies. Legacy path may still exist on upgraded systems |
The binary file format itself has remained stable across Safari versions.
Tool Support
macfor
The browser.safari plugin checks both the sandboxed and legacy cookie paths, preferring the sandboxed location (newer macOS). It parses the binary cookies format and emits browser_cookie records with fields including domain, name, path, value_preview (redacted -- format: [REDACTED - N chars]), value_length, creation_time, expiry_time, secure, and http_only. The raw binary file is also preserved in the evidence container.
Cookie collection can be disabled with the SkipCookies option.
Manual Analysis with Python
#!/usr/bin/env python3
"""Minimal Cookies.binarycookies parser."""
import struct
from datetime import datetime, timezone, timedelta
COREDATA_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc)
def parse_cookies(filepath):
with open(filepath, 'rb') as f:
data = f.read()
# Verify magic
assert data[:4] == b'cook', "Not a binarycookies file"
num_pages = struct.unpack('>I', data[4:8])[0]
page_sizes = [struct.unpack('>I', data[8+i*4:12+i*4])[0]
for i in range(num_pages)]
offset = 8 + num_pages * 4
for page_size in page_sizes:
page = data[offset:offset+page_size]
num_cookies = struct.unpack('<I', page[4:8])[0]
cookie_offsets = [struct.unpack('<I', page[8+i*4:12+i*4])[0]
for i in range(num_cookies)]
for co in cookie_offsets:
rec = page[co:]
flags = struct.unpack('<I', rec[4:8])[0]
url_off = struct.unpack('<I', rec[8:12])[0]
name_off = struct.unpack('<I', rec[12:16])[0]
expiry = struct.unpack('<d', rec[32:40])[0]
creation = struct.unpack('<d', rec[40:48])[0]
size = struct.unpack('<I', rec[0:4])[0]
rec_data = rec[:size]
domain = rec_data[url_off:].split(b'\x00')[0].decode()
name = rec_data[name_off:].split(b'\x00')[0].decode()
print(f"{domain:40s} {name:30s} "
f"created={COREDATA_EPOCH + timedelta(seconds=creation)} "
f"secure={'Y' if flags & 0x1 else 'N'} "
f"httponly={'Y' if flags & 0x4 else 'N'}")
offset += page_size
if __name__ == '__main__':
import sys
parse_cookies(sys.argv[1])
Other Tools
- BinaryCookieReader (Python): Popular open-source parser for
Cookies.binarycookies - AXIOM / Cellebrite: Commercial tools with built-in cookie parsing
- Autopsy: Safari cookie module available
References
- SANS FOR518: Mac and iOS Forensic Analysis
- Safari Binary Cookies Format (reverse-engineered format documentation)
- HTTP Cookie Specification (RFC 6265)