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

FilePathFormatNotes
Cookies (legacy)~/Library/Cookies/Cookies.binarycookiesBinaryPre-macOS 10.14
Cookies (sandboxed)~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookiesBinarymacOS 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

OffsetSizeEndiannessField
04--Magic bytes: cook (ASCII)
44Big-endianNumber of pages
84*NBig-endianArray of page sizes (one uint32 per page)
OffsetSizeEndiannessField
04Little-endianPage header magic: 0x00010000
44Little-endianNumber of cookies in this page
84*NLittle-endianArray of cookie offsets within the page
8+4*N4Little-endianEnd marker: 0x00000000

Each cookie record begins at the offset specified in the page header.

OffsetSizeEndiannessField
04Little-endianRecord size in bytes
44Little-endianFlags (see below)
84Little-endianDomain/URL string offset
124Little-endianName string offset
164Little-endianPath string offset
204Little-endianValue string offset
248--End header marker (zeros)
328Little-endianExpiry time (float64, Core Data timestamp)
408Little-endianCreation time (float64, Core Data timestamp)
48+Var--Null-terminated strings: domain, name, path, value
Bit MaskFlagDescription
0x0001SecureCookie is only sent over HTTPS connections
0x0004HttpOnlyCookie 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, _ga can 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 Secure on authentication cookies is a security concern. HttpOnly prevents 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., SAPISID for 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.binarycookies is 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

VersionChange
macOS 10.12-10.13Cookies 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

Previous
Bookmarks