Browser History for Initial Access: Collecting and Querying Chromium History
When the EDR shows PowerShell or a suspicious download but not how the user got there, the browser's SQLite history usually does. Find the right profile, collect a copy, and query it in DB Browser for SQLite.
On this page
- 1. Find the browser and profile that were actually used
- 2. Collect a working copy
- 3. Open it in DB Browser for SQLite
- 4. Build the browser timeline
- What the user searched for
- 5. Review downloads
- 6. Pivot on domains and lure language
- Two patterns from real cases
- ClickFix behind a fake AI page
- A “document” page delivering an executable
- 7. Correlate with endpoint evidence
- 8. Let the findings drive response
- Firefox
- The principle
EDR telemetry tells you what happened on the endpoint: PowerShell ran, an MSI installed, an executable made a network connection. It often can’t tell you how the user got there. The browser’s history database usually can.
Use it to answer questions like:
- What site did the user visit right before the alert?
- Was the file downloaded through the browser, and from which URL?
- Was there a redirect or a fake verification page before execution?
- Did a legitimate search lead to a malicious result?
- Does the browser activity support a phishing, ClickFix, scareware, or malicious-download story, or not?
The whole workflow:
Detection -> identify user -> collect browser DB -> reconstruct visits and downloads
-> correlate with EDR -> determine initial access -> remediate -> document
1. Find the browser and profile that were actually used
Don’t assume Chrome and Default. Users run Edge, Brave, or Firefox, and Chromium browsers keep one database per profile (Default, Profile 1, Profile 2…). The most recently written History file is usually the one that matters.
From a remote response shell:
Get-ChildItem -Force -ErrorAction SilentlyContinue -Path @(
'C:\Users\*\AppData\Local\Google\Chrome\User Data\*\History',
'C:\Users\*\AppData\Local\Microsoft\Edge\User Data\*\History',
'C:\Users\*\AppData\Local\BraveSoftware\Brave-Browser\User Data\*\History',
'C:\Users\*\AppData\Roaming\Mozilla\Firefox\Profiles\*\places.sqlite'
) | Sort-Object LastWriteTimeUtc -Descending |
Select-Object LastWriteTimeUtc, Length, FullName |
Format-Table -AutoSize -Wrap
A History file written a minute before the alert is a strong lead. Check which browsers are running too:
Get-Process chrome, msedge, brave, firefox -ErrorAction SilentlyContinue |
Select-Object Name, Id, StartTime | Format-Table -AutoSize
For PowerShell patterns that keep this output readable, see PowerShell for Responders.
2. Collect a working copy
The browser holds the database open while it runs, so copy it first and collect the copy. Include the -journal file if one exists: it can hold changes that haven’t been written into the main database yet.
$src = 'C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History'
$dest = 'C:\Windows\Temp\<user>_Chrome_History.db'
Copy-Item -LiteralPath $src -Destination $dest
Copy-Item -LiteralPath "$src-journal" -Destination "$dest-journal" -ErrorAction SilentlyContinue
Get-FileHash $dest -Algorithm SHA256
If the copy fails because the file is locked, a volume shadow copy read usually works:
esentutl.exe /y "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History" /vss /d "C:\Windows\Temp\<user>_Chrome_History.db"
Then pull the copy through your EDR’s file retrieval, for example get in a CrowdStrike RTR session or Fetch File in SentinelOne. Record the hash, and remove the temp copy from the endpoint once it’s collected.
On your side, keep the collected file untouched and analyze a second copy.
3. Open it in DB Browser for SQLite
Open the working copy with File > Open Database Read-Only, then use the Execute SQL tab. The Chromium tables that matter:
| Table | What it holds |
|---|---|
urls |
Each unique URL: title, visit count, last visit |
visits |
Every individual visit, how it happened (transition), and which visit led to it (from_visit) |
downloads |
Downloaded files: path, size, state, referrer, tab URL |
downloads_url_chains |
Every URL in a download’s redirect chain |
keyword_search_terms |
Search terms typed into the address bar |
Chromium stores time as microseconds since 1601-01-01 UTC. Every query below converts it the same way:
datetime(visit_time / 1000000 - 11644473600, 'unixepoch')
Don’t review the whole database. Work the window around the alert.
4. Build the browser timeline
Visits in order, with how each one happened and where it came from:
SELECT
datetime(v.visit_time / 1000000 - 11644473600, 'unixepoch') AS visit_utc,
u.url,
u.title,
CASE v.transition & 0xFF
WHEN 0 THEN 'link' WHEN 1 THEN 'typed'
WHEN 2 THEN 'bookmark' WHEN 5 THEN 'generated (search)'
WHEN 7 THEN 'form submit' WHEN 8 THEN 'reload'
ELSE v.transition & 0xFF
END AS how,
prev.url AS came_from
FROM visits v
JOIN urls u ON v.url = u.id
LEFT JOIN visits pv ON v.from_visit = pv.id
LEFT JOIN urls prev ON pv.url = prev.id
WHERE v.visit_time BETWEEN
(strftime('%s', '2026-01-01 17:45:00') + 11644473600) * 1000000
AND (strftime('%s', '2026-01-01 18:05:00') + 11644473600) * 1000000
ORDER BY v.visit_time;
Set the two timestamps (UTC) to a window around the detection, typically 10 to 15 minutes before and a few minutes after. The came_from column rebuilds the chain the user clicked through.
What you’re trying to establish:
Search or email -> landing page -> redirect -> download or lure -> execution
What the user searched for
SELECT
datetime(u.last_visit_time / 1000000 - 11644473600, 'unixepoch') AS last_visit_utc,
k.term,
u.url
FROM keyword_search_terms k
JOIN urls u ON k.url_id = u.id
ORDER BY u.last_visit_time DESC
LIMIT 50;
Searches are often the first link in the chain: a search for a legitimate tool or service, then a malicious result or ad.
5. Review downloads
For an executable, MSI, ZIP, script, or document payload:
SELECT
datetime(d.start_time / 1000000 - 11644473600, 'unixepoch') AS start_utc,
d.target_path,
d.received_bytes,
d.total_bytes,
d.state,
d.referrer,
d.tab_url,
c.chain_index,
c.url AS chain_url
FROM downloads d
LEFT JOIN downloads_url_chains c ON d.id = c.id
ORDER BY d.start_time DESC, c.chain_index;
chain_urlrows are the redirect chain; the highestchain_indexis the URL that actually served the file.referrerandtab_urlusually name the page the user was on, which is often the lure itself.state1 means the download completed.
This identifies the real delivery domain even when the payload itself carries no useful attribution.
6. Pivot on domains and lure language
Search the urls table for anything tied to the case:
SELECT datetime(last_visit_time / 1000000 - 11644473600, 'unixepoch') AS last_visit_utc,
url, title, visit_count
FROM urls
WHERE url LIKE '%verify%' OR url LIKE '%captcha%'
OR title LIKE '%verif%' OR title LIKE '%human%'
OR title LIKE '%download%' OR title LIKE '%document%'
ORDER BY last_visit_time DESC;
Good terms to try: known IOCs, the payload file name, “verify”, “captcha”, “human verification”, “download”, “document”, and whatever brand the lure impersonates (fake AI tools and document portals are common).
When you get a hit, read several visits before and after it. One suspicious URL on its own isn’t proof of the infection chain.
Two patterns from real cases
ClickFix behind a fake AI page
The EDR showed malicious PowerShell and not much else. The browser history filled in the rest:
| Time | Browser | EDR |
|---|---|---|
| Shortly before | Searches for a popular AI chatbot | |
| T-1 min | Visit to a free-hosted site page titled as that chatbot, with a “verification” step | |
| T+0 | Malicious PowerShell launched from the user’s session |
The page was hosted on a legitimate site-builder platform, so the domain on its own looked trustworthy. The sequence is what made the case: a search, a page impersonating the tool the user searched for, and PowerShell about a minute later.
A “document” page delivering an executable
| Time | Browser |
|---|---|
| T+0 | A page titled “Downloading Document” |
| Then | Redirect to a randomized URL on the same domain |
| T+6 s, T+14 s, T+28 s | Three downloads of an executable named like a verification form |
downloads_url_chains identified the delivery domain, a randomized subdomain on a lookalike domain. Just as important, the history didn’t show what sent the user to that first page, so the write-up didn’t claim one. Stating only what the chain supports is what makes the timeline defensible.
7. Correlate with endpoint evidence
Browser history on its own shows what the user looked at, not what executed. Line it up with:
- process creation and script activity
- file creation (the downloaded payload) and hashes
- MSI or installer execution
- DNS requests and network connections
- detection timestamps
- persistence
- email telemetry and user logon sessions
A strong combined timeline reads like this:
T+0 landing page ("Downloading Document")
T+6s executable downloaded
T+28s repeat download
T+1m file launched
T+2m child process and outbound connection
That sequence is much stronger evidence than “a suspicious domain and an executable were both on the system.”
8. Let the findings drive response
Browser evidence helps decide whether to:
- block the delivery domain
- remove downloaded payloads and kill related processes
- contain the endpoint
- reset credentials and revoke sessions
- hunt other endpoints for the same domain or hash
- search email telemetry for the originating lure
- close the browser activity as benign or inconclusive when no execution followed
Firefox
Firefox uses places.sqlite, with times in microseconds since 1970:
SELECT
datetime(h.visit_date / 1000000, 'unixepoch') AS visit_utc,
p.url,
p.title
FROM moz_historyvisits h
JOIN moz_places p ON h.place_id = p.id
ORDER BY h.visit_date DESC
LIMIT 200;
Downloads are stored as page annotations (moz_annos) rather than a dedicated table, so the destination path sits in an annotation on the download URL.
The principle
Use the browser database to answer one question: what did the user do immediately before the endpoint activity we detected?
The EDR explains what happened on the endpoint. The browser explains how the user got there.