Browser forensic artefacts on Linux. Profile paths, SQLite databases, the queries that turn schema into history / downloads / cookies / search activity. Firefox uses Unix epoch microseconds. Chrome uses FILETIME (1601) microseconds. Same column type, different zero point.
Profile locations
| Browser | Path |
|---|---|
| Firefox | ~/.mozilla/firefox/<profile>/ (profile names from profiles.ini) |
| Chrome | ~/.config/google-chrome/Default/ or <ProfileName>/ |
| Chromium | ~/.config/chromium/Default/ |
| Brave | ~/.config/BraveSoftware/Brave-Browser/Default/ |
| Edge (Linux) | ~/.config/microsoft-edge/Default/ |
cat ~/.mozilla/firefox/profiles.ini # find Firefox profile dirname
ls -la ~/.config/google-chrome/ # find Chrome profile dirsFirefox - places.sqlite
navigation history, bookmarks, download annotations all live here. timestamps are microseconds since 1970-01-01 UTC.
cp ~/.mozilla/firefox/<profile>/*.sqlite ./working/ # close Firefox first - locks block reads
sqlite3 ./working/places.sqlite # open shellMost-popular sites
SELECT url, title, visit_count
FROM moz_places
ORDER BY visit_count DESC
LIMIT 10;Last visited site
SELECT url, datetime(last_visit_date / 1000000, 'unixepoch') AS last_utc
FROM moz_places
ORDER BY last_visit_date DESC
LIMIT 1;Full visit history
SELECT p.url, datetime(v.visit_date / 1000000, 'unixepoch') AS d, v.visit_type
FROM moz_historyvisits v
JOIN moz_places p ON p.id = v.place_id
ORDER BY v.visit_date DESC;Downloads (visit_type 7) and via moz_annos
SELECT p.url, datetime(v.visit_date / 1000000, 'unixepoch') AS d
FROM moz_historyvisits v JOIN moz_places p ON v.place_id = p.id
WHERE v.visit_type = 7 ORDER BY v.visit_date DESC;
SELECT datetime(lastModified / 1000000, 'unixepoch') AS down_date,
content AS file, url
FROM moz_places, moz_annos
WHERE moz_places.id = moz_annos.place_id;Bookmarks
SELECT b.title, p.url, datetime(b.dateAdded / 1000000, 'unixepoch') AS added
FROM moz_bookmarks b JOIN moz_places p ON p.id = b.fk
WHERE b.type = 1
ORDER BY b.dateAdded DESC;Search activity (Google example)
SELECT p.url, datetime(v.visit_date / 1000000, 'unixepoch') AS d
FROM moz_historyvisits v JOIN moz_places p ON v.place_id = p.id
WHERE p.url LIKE '%google%search%'
ORDER BY v.visit_date DESC;Firefox visit_type codes
| Code | Meaning |
|---|---|
| 1 | followed link |
| 2 | typed URL |
| 3 | bookmark |
| 4 | from iframe |
| 5 | HTTP 301 redirect |
| 6 | HTTP 302 redirect |
| 7 | downloaded file |
| 8 | followed link inside iframe |
filter WHERE visit_type IN (2, 3) for user-driven navigation only.
Firefox - other databases
| File | Holds |
|---|---|
cookies.sqlite | moz_cookies - host, name, value, expiry |
formhistory.sqlite | autofill form data |
favicons.sqlite | favicon images |
webappsstore.sqlite | HTML5 localStorage / sessionStorage |
permissions.sqlite | per-site permissions |
logins.json + key4.db | encrypted credentials - decrypt with firefox_decrypt |
cache2/entries/ | cache content - parse with MozillaCacheView (Win) or dejavu (Linux) |
# anti-phishing settings - findings if disabled
grep safebrowsing ~/.mozilla/firefox/*/prefs.js
# expect safebrowsing.malware.enabled=true. false value = tampering tellChrome - History (no extension)
timestamps are microseconds since 1601-01-01 (Windows FILETIME). conversion needs the offset to Unix epoch.
cp ~/.config/google-chrome/Default/History ./working/ # close Chrome first
sqlite3 ./working/HistoryMost-popular URLs
SELECT url, title, visit_count
FROM urls
ORDER BY visit_count DESC
LIMIT 10;Visit history (FILETIME → UTC string)
SELECT p.url,
datetime(v.visit_time / 1000000 + (strftime('%s', '1601-01-01')), 'unixepoch') AS d
FROM visits v
LEFT JOIN urls p ON v.url = p.id
ORDER BY d DESC;Last download
SELECT tab_url,
datetime(start_time / 1000000 + (strftime('%s', '1601-01-01')), 'unixepoch') AS d
FROM downloads
ORDER BY start_time DESC
LIMIT 5;Search activity
SELECT p.url,
datetime(v.visit_time / 1000000 + (strftime('%s', '1601-01-01')), 'unixepoch') AS d
FROM visits v LEFT JOIN urls p ON v.url = p.id
WHERE p.url LIKE '%google%search%'
ORDER BY d DESC;strftime('%s', '1601-01-01') returns -11644473600 (Unix-epoch seconds for 1601-01-01). visit_time / 1000000 converts microseconds since 1601 into seconds since 1601. adding the two yields Unix-epoch seconds, which datetime(..., 'unixepoch') renders.
Chrome - other files
| File | Holds |
|---|---|
Cookies (or Network/Cookies from Chrome 96+) | cookies table |
Bookmarks | JSON, not SQLite |
Login Data | encrypted credentials - OS keychain holds the master key |
Web Data | autofill form history (autofill table) |
Preferences | JSON config - search for safebrowsing settings |
Extensions/<id>/ | per-extension manifest + assets |
Cache/ | cache data, custom format |
# Chrome anti-phishing check
grep safebrowsing ~/.config/google-chrome/Default/PreferencesShell + interactive history files
| File | Shell / tool |
|---|---|
~/.bash_history | bash |
~/.zsh_history | zsh - extended format : <epoch>:<duration>;<command> if EXTENDED_HISTORY set |
~/.sh_history | sh, ksh, ash |
~/.history | csh, tcsh |
~/.mysql_history | mysql client |
~/.psql_history | psql client |
~/.python_history | python REPL |
~/.lesshst | less search patterns |
# extract last 50 commands
tail -50 ~/.bash_history
# zsh extended format - convert epochs to readable
awk -F'[:;]' '/^: [0-9]/ {cmd=""; for(i=4;i<=NF;i++) cmd=cmd" "$i; print strftime("%Y-%m-%d %H:%M:%S", $2), cmd}' ~/.zsh_history
# search every history file in a home directory
find /home/<user> -maxdepth 2 -type f -name '.*_history' -o -name '.bash_history' -o -name '.history'History tampering tells
.bash_historyreduced to singleexitline on an actively-used account =history -c; exit(T1070.003).bash_historysymlinked to/dev/nullHISTFILEunset in user’s shell rc files.bash_historysize 0 with recent atime (read-and-truncate)
Decision flow
graph TD q["browser-artefact question"] --> b{"which browser?"} b -->|"Firefox"| ff["~/.mozilla/firefox/<profile>/"] b -->|"Chrome / Brave / Edge"| cr["~/.config/<browser>/Default/"] ff --> ffq["which artefact?"] cr --> crq["which artefact?"] ffq -->|"history / bookmarks / downloads"| pl["places.sqlite"] ffq -->|"cookies"| ck["cookies.sqlite"] ffq -->|"credentials"| ck2["logins.json + key4.db<br/>(firefox_decrypt)"] crq -->|"history / downloads"| ch["History (no ext)"] crq -->|"cookies"| cc["Cookies / Network/Cookies"] crq -->|"credentials"| ld["Login Data + OS keychain"]
Pitfalls
- timestamp epoch confusion: Firefox = 1970, Chrome = 1601. wrong epoch produces dates in the wrong century
- running queries against an open browser: SQLite locks block reads. always copy out first or close the browser
- multi-profile Firefox:
profiles.inilists every profile. process each separately - Chrome
Historyhas no extension: easy to miss when ls-greping for*.sqliteor*.db moz_places.last_visit_datevsmoz_historyvisits.visit_date: the former is the most-recent visit only. the latter is the per-visit log- clear-history does not clear
logins.jsonorLogin Data. credential stores survive a partial wipe - cache files need rendering, not just extraction:
cache2/entries/<hash>files have a Mozilla wrapper around the response body. rawcatwill not produce readable HTML