Identity Threat Concepts - Cookie Stealing Part 2 - KQL
- brencronin
- 4 days ago
- 9 min read
In the 1st part of this series, we discussed commonly used Threat Actor techniques to steal session cookies directly from web browsers:
Remote Browser Debugging Abuse
Browser Process Memory Dumping
Direct Cookie File Extraction (e.g., Chrome/Edge user data paths)
Malicious Browser Extensions with elevated privileges
The next part of this series will review some Microsoft KQL queries to detect these types of activities.
Remote Browser Debugging Abuse
Identify browser processes started with both --remote-debugging-port and --headless, which is a strong indicator of covert browser-based data theft.
DeviceProcessEvents
| where FileName in~ ("chrome.exe", "msedge.exe", "firefox.exe")
| where InitiatingProcessCommandLine has_any ("--remote-debugging-port", "--headless", "--user-data-dir")
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName
One issue you may encounter with the previous query, as written above, is that it essentially acts as an "OR" condition. This means it would match on a browser launched with only one of the suspicious options, such as --remote-debugging-port or --headless.
You will sometimes see browser test tools like Puppeteer, Selenium, Playwright, and custom Chrome DevTools clients running in --headless mode, and sometimes with the option --remote-debugging-port=0, which assigns a random remote debugging port.
The KQL query below looks for browser launches that include both --remote-debugging-port and --headless options.
DeviceProcessEvents
| where FileName in~ ("chrome.exe", "msedge.exe", "opera.exe")
| where ProcessCommandLine has "--remote-debugging-port"
and ProcessCommandLine has "--headless"
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName
The query below flags the use of netsh to configure a port proxy, which is commonly used to expose local-only services like browser debugging interfaces. If you want to be more specific, you can also look for access to port 9222, which is the default remote debugging port for browsers and is frequently targeted by many infostealers.
DeviceProcessEvents
| where FileName =~ "netsh.exe"
| where ProcessCommandLine has_all ("interface", "portproxy", "add")
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName
Browser Process Memory Dumping
????Need to test and fix these?????
DeviceProcessEvents
| where ActionType == "ProcessAccessed"
| where TargetProcessName in~ ("chrome.exe", "msedge.exe", "firefox.exe", "brave.exe")
| where InitiatingProcessFileName !in~ ("chrome.exe", "msedge.exe", "firefox.exe", "brave.exe") // exclude self-access
| where InitiatingProcessFileName !in~ ("csrss.exe", "explorer.exe", "taskmgr.exe", "svchost.exe") // filter common legit processes
| extend timestamp = TimeGenerated,
TargetProcess = TargetProcessName,
SourceProcess = InitiatingProcessFileName,
SourcePath = InitiatingProcessFolderPath,
TargetPath = TargetProcessFolderPath
| summarize Count = count() by SourceProcess, SourcePath, TargetProcess, TargetPath, bin(timestamp, 1h)
| where Count > 2
| sort by Count desc
DeviceProcessEvents logs process interactions like access handles (via OpenProcess API).
ActionType == "ProcessAccessed" filters for memory or handle access events.
The query focuses on access to browser processes, excluding common benign sources like taskmgr.exe.
This version filters by known browsers, and raises alerts for non-browser processes accessing them multiple times within an hour.
DeviceProcessEvents
| where InitiatingProcessFileName !~ "chrome.exe"
and InitiatingProcessFileName !~ "msedge.exe"
and InitiatingProcessFileName !~ "firefox.exe"
| where FileName in~ ("chrome.exe", "msedge.exe", "firefox.exe")
| where ActionType in~ ("ProcessInjection", "OpenProcess", "ReadProcessMemory", "VirtualAllocEx", "WriteProcessMemory")
| project Timestamp, DeviceName, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, ActionType, ReportId, InitiatingProcessAccountName, InitiatingProcessId
DeviceEvents
| where ActionType == "HandleOpened"
| where AdditionalFields contains "ReadProcessMemory"
| where TargetImage in~ ("chrome.exe", "msedge.exe", "firefox.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, TargetImage, ReportId
Pay attention to access from unsigned binaries or unknown paths (%AppData%, %Temp%, etc.).
High count of access events in a short window is a key indicator of memory scraping attempts.
Consider correlating with Sysmon Event ID 10 or Defender DeviceNetworkEvents if C2 communication follows.
Add process signer status using InitiatingProcessSigner from DeviceProcessEvents.
Add InitiatingProcessCommandLine for visibility into suspicious execution patterns.
Use DeviceNetworkEvents join to see if the originating process also communicates externally.
Tie into Defender XDR hunting to track subsequent use of session tokens (e.g., via unusual browser logins).
Direct Cookie File Extraction (e.g., Chrome/Edge user data paths)
KQL Detection – Registry Downgrade of ABE
DeviceRegistryEvents
| where RegistryKey has @"Software\Policies\Microsoft\Edge\ApplicationBoundEncryption"
| where RegistryValueName == "Enabled"
| where RegistryValueData == "0"
| project Timestamp, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine, RegistryKey, RegistryValueName, RegistryValueData, ReportId
KQL Detection – ABE Bypass via SYSTEM + Chrome COM DecryptData Abuse
DeviceProcessEvents
| where InitiatingProcessAccountName =~ "SYSTEM"
| where ProcessCommandLine has "chrome_elevation_service.exe"
| where ProcessCommandLine has "DecryptData"
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessParentFileName, InitiatingProcessCommandLine
chrome_elevation_service.exe: This binary is responsible for privileged Chrome operations, including access to encrypted data.
DecryptData: A common method exposed through the COM interface used to request decryption from the Elevation Service.
SYSTEM context: This detection is scoped to cases where the process is spawned under elevated privileges, a red flag for cookie exfiltration via malware or privilege abuse.
Malicious Browser Extensions with elevated privileges
Identifying and Assessing Risky Browser Extensions in the Enterprise
Browser extensions are a powerful, and often overlooked, mechanism for accessing and manipulating sensitive data. While many serve legitimate purposes, malicious or overly-permissive extensions pose significant security risks, especially in enterprise environments where data exfiltration or session hijacking is a concern.
Unfortunately, managing browser extension risk isn't straightforward. Organizations face two main challenges:
Discovering which extensions are installed across endpoints
Assessing the security and risk profile of those extensions
While step #2 often garners attention, it’s step #1, discovery, that many organizations struggle with most. Standard vulnerability scanners and endpoint detection agents frequently fail to reliably detect installed browser extensions, particularly in environments using multiple browsers or unmanaged user settings.
Step #1 - Tools for Extension Discovery and Risk Assessment
Once you’ve identified which extensions are in use, you can begin to analyze their permissions and behaviors. Tools like the Chrome Web Store's permission descriptions and the open-source project CRXcavator can help.
CRXcavator is a powerful tool that:
Loads and scans browser extensions
Analyzes their permissions, domains contacted, and code behavior
Assigns a risk score
Identifies extensions by their unique 32-character extension ID
If You Don’t Have CRXcavator: Microsoft & KQL-Based Alternatives
In Microsoft Defender for Endpoint or Microsoft 365 Defender environments, you can use Kusto Query Language (KQL) to search for evidence of browser extension installations.
Method: 'Threat and vulnerability Management' (DeviceTvm) data
DeviceTvm data is gathered through the Microsoft Defender for Endpoint sensor, which collects telemetry from onboarded Windows, Linux, and macOS endpoints. Microsoft Defender for Endpoint (MDE) agent runs on the endpoint and performs local scans to collect inventory and vulnerability data. The TVM data from endpoints is then sent to the Microsoft 365 Defender portal for centralized visibility. DeviceTvm* tables (e.g., DeviceTvmSoftwareInventory, DeviceTvmSoftwareVulnerabilities, DeviceTvmSecureConfiguration, DeviceTvmBrowserExtensions, DeviceTvmSoftwareEvidenceBeta (for more detailed software evidence)) are part of the Microsoft 365 Defender advanced hunting schema. First, to have access to the DeiveTvm data you need to have the Microsoft Defender Vulnerability Management (MDVM) add-on license which you might have. Furthermore, in some deployments the DeviceTvm tables may not have all extension data and other searches for device browser extensions i n use will be more effective.
Method: Detecting .crx Files
.crx files are the packaged format for Chromium-based browser extensions. You can use this KQL query to look for these files being created on endpoints:
DeviceFileEvents
| where ActionType == "FileCreated" | where FileName endswith ".crx"
Note: Not all extensions will appear this way. Chrome also supports loading extensions from unpacked directories, which won’t use .crx files.
Browser Extension Locations and Detection Patterns
This guide outlines common file paths for browser extensions across major browsers and operating systems, along with example KQL patterns to help detect them using file path analysis.
Notes
<username> and <profilefolder> are placeholders and should be dynamically handled or filtered in queries.
Chrome, Edge, and Chromium extensions typically have 32-character folder names representing their extension IDs.
Firefox uses .xpi files with UUID-style names.
Safari (modern) uses .appex bundles; legacy uses .safariextz.
Microsoft Edge
Edge Windows
\Microsoft\Edge\User Data\Default\Extensions\
Edge Linux
~/.config/microsoft-edge/Default/Extensions/
Edge macOS
/Users/<username>/Library/Application Support/Microsoft Edge/<profilefolder>/Extensions/
KQL Pattern for Edge:
| where (((FolderPath contains @"Microsoft\Edge\User Data" and FolderPath contains @"\Extensions") or (FolderPath contains @"microsoft-edge" and FolderPath contains @"/extensions/") or (FolderPath contains @"/Microsoft Edge/" and FolderPath contains @"/Extensions/")) and (FolderPath matches regex @"[a-z]{32}")
Google Chrome
Chrome Windows
\users\username\AppData\Local\Google\Chrome\User Data\default\Extensions
Chrome Linux
/home/username/.config/google-chrome/default/Extensions
Chrome macOS
/users/username/Library/Application Support/Google/Chrome/Default/Extensions
KQL Pattern for Chrome:
(((FolderPath contains @"Google\Chrome\User Data\" and FolderPath contains @"\Extensions\") or (FolderPath contains @"/Application Support/Google/Chrome/" and FolderPath contains @"/Extensions/") or (FolderPath contains "config/google-chrome/" and FolderPath contains @"/Extensions")) and (FolderPath matches regex @"[a-z]{32}"))
Chromium
Chromium Windows
C:\Users\<username>\AppData\Local\Chromium\User Data\Default\Extensions\
Chromium Linux
~/.config/chromium/Default/Extensions/
Chromium macOS
/Users/<username>/Library/Application Support/Chromium/Default/Extensions/
KQL Pattern for Chromium
(((FolderPath contains @"\chromium\" and FolderPath contains @"\extensions\") or (FolderPath contains @""/chromium/" and FolderPath contains @"/extensions/")) and (FolderPath matches regex @"[a-z]{32}"))
Mozilla Firefox
Firefox Windows
C:\Users\<username>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile_folder>\extensions\
C:\Users\<username>\AppData\Local\Mozilla\Firefox\User Data\Default\Extension\
Firefox Linux
~/.mozilla/firefox/<profile_folder>/extensions/
Firefox macOS
~/Library/Application Support/Firefox/Profiles/<profile_folder>/extensions/
KQL Pattern for FireFox
(((FolderPath contains @"\Mozilla\Firefox" and FolderPath contains @"\Extension\") or (FolderPath contains @"/Firefox/Profiles" and FolderPath contains @"/extensions/") or (FolderPath contains @"mozilla/firefox" and FolderPath contains @"/extensions/") or (FolderPath contains @"\Firefox" and FolderPath contains @"\Extensions\") or (FolderPath contains @"Mozilla" and FolderPath contains @"\extensions\")) and (FolderPath matches regex @"(?i){[0-9a-f-A-F]{8}-[0-9a-f-A-F]{4}-[0-9a-f-A-F]{4}-[0-9a-f-A-F]{12}\}\.xpi"))
Safari
Safari Linux (New)
/Users/username/Applications/
Safari Linux (Legacy)
/Library/Safari/Extensions
Safari macOS (Modern)
/Users/username/Application
Safari macOS (Legacy)
/Library/Safari/Extensions/<extension>.safariextz
KQL Pattern
@"^(?i)(/Applications|/Users/[^/]+Applications)/[^/]+\.app/Contents/Plugins/[^/]+\.appex/?") or (FolderPath contains "/Library/Safari/Extensions" and FolderPath contains ".safari\extz"))
Searching for known bad extensions 'Hunting Malicious Chrome Extension'
In the below KQL query from Steven Lim the threat hunt is searching the environment for a defined list of malicious browser extensions which are defined in a variable called 'MaliciousChromeExtensionID'. DeviceFileEvents are then searched for matches to these extensions. Extensions are located in DeviceFileEvents based on files ending with the extension pattern .crx.
let MaliciousChromeExtensionID = dynamic(["bibjgkidgpfbblifamdlkdlhgihmfohh","pkgciiiancapdlpcbppfkmeaieppikkk","epdjhgbipjpbbhoccdeipghoihibnfja","bbdnohkpnbkdkmnkddobeafboooinpla","befflofjcniongenjmbkgkoljhgliihe","cedgndijpacnfbdggppddacngjfdkaca","nnpnnpemnckcfdebeekibpiijlicmpom","dpggmcodlahmljkhlmpgpdcffdaoccni","cplhlgabfijoiabgkigdafklbhhdkahj","egmennebgadmncfjafcemlecimkepcle","acmfnomgphggonodopogfbmkneepfgnh","mnhffkhmpnefgklngfmlndmkimimbphc","oaikpkmjciadfpddlpjjdapglcihgdle","fbmlcbhdmilaggedifpihjgkkmdgeljh","kkodiihpgodmdankclfibbiphjkfdenh","oeiomhmbaapihbilkfkhmlajkeegnjhe"]);
DeviceFileEvents
| where ActionType == "FileCreated" and FileName endswith ".crx"
| where FileName has_any(MaliciousChromeExtensionID)
????Cyberhaven records every event for every piece of data – every move, copy, edit, and share to fully understand how data moves throughout your company.??/ cyberhaven hunt
Searching for bad extensions from external data source 'Classifying Browser Extension By Type And Risk Severity'
This KQL example from Sergio Albea on kqlsearch.com is a great use of the externaldata operator. It pulls in a curated list of browser extensions, complete with metadata indicating whether each extension is flagged as malicious.

The query then joins that external list with browser extensions detected in your environment via DeviceTvmBrowserExtensions. While this data source can sometimes be inconsistent, it’s relatively straightforward to extract extension information directly from DeviceFileEvents, as shown in earlier examples.
let Browser_Extension_info = externaldata(browser_extension:string ,metadata_category:string ,metadata_type:string ,metadata_link:string ,metadata_comment:string)[@"https://raw.githubusercontent.com/mthcht/awesome-lists/refs/heads/main/Lists/Browser%20Extensions/browser_extensions_list.csv"] with (format="csv", ignoreFirstRecord=True);
Browser_Extension_info
| join kind= inner (DeviceTvmBrowserExtensions) on $left.browser_extension == $right.ExtensionName
| project metadata_type, Extension_Group= browser_extension, Severity= metadata_link, metadata_comment, DeviceId, ExtensionDescription, ExtensionVersion
Organizations can also flip this approach: instead of matching against known bad extensions, you can maintain a list of approved extensions and alert when unapproved ones are detected. Depending on your environment, this type of "allow-list" monitoring may be even more effective when implemented using watchlists.
Analyzing Bad extensions from external data source - 'Browser Extension Downloads Using Device File Events'
This one from Jay Kerai is similar to the previous query where it pulls a list that you may cerate of unauthorized browser extensions and then searches your environment for them.

Other slight differences are the referenced extension list does not have the extension meta-data 9you may use your own list anyways), and extensions are gathered from DeviveFileEvents rather than DeviceTvmBrowserExtensions.
let UnsanctionedExtensions = externaldata (ExtensionID: string) [@'https://raw.githubusercontent.com/jkerai1/SoftwareCertificates/refs/heads/main/Bulk-IOC-CSVs/Intune/Intune%20Browser%20Extension_IDs_the_user_should_be_prevented_from_installing.csv'] with (format=txt);
DeviceFileEvents
| where TimeGenerated > ago(90d)
| where ActionType == "FileCreated"
| where FileName endswith ".crx"
//| where InitiatingProcessFileName == "chrome.exe" //if you need to filter down to chrome vs edge
| where FolderPath contains "Webstore Downloads"
| extend ExtensionID = trim_end(@"_\d{2,6}.crx", FileName)
| extend ExtensionURL = strcat("https://chrome.google.com/webstore/detail/",ExtensionID)
| extend EdgeExtensionURL = strcat("https://microsoftedge.microsoft.com/addons/detail/",ExtensionID)
| extend RiskyExtension = iff((ExtensionID in~(UnsanctionedExtensions)), "Yes","N/A")
| summarize count() by ExtensionID,ExtensionURL, EdgeExtensionURL, RiskyExtension
KQL Detection – Extension-Originated Suspicious Network Connections
???check???
DeviceNetworkEvents
| where InitiatingProcessFileName in~ ("chrome.exe", "msedge.exe")
| where RemoteUrl has_any ("pastebin", "discord", ".onion", "ipfs", "anonfiles", "telegram") // Suspicious exfil paths
| where InitiatingProcessCommandLine has "--extension" or RemotePort in (443, 80)
| project Timestamp, DeviceName, RemoteUrl, InitiatingProcessCommandLine, ReportId
Chrome Extension Stealth Persistence Detection
Another one from Steven Lim was created based off of some search on surreptitiously installing browser extensions through manipulation of the Secure Preferences file: https://syntax-err0r.github.io/Silently_Install_Chrome_Extension.html
The Secure Preferences file in Chrome’s user data directory is a special JSON-formatted configuration file used to store sensitive profile-related settings that require integrity protection. It plays a role in ensuring browser settings haven't been tampered with, especially by malware or unauthorized software. The Secure Preferences file contains:
Extension settings and permissions, Extension state (enabled/disabled), Indicators of managed (enterprise-controlled) settings, Cryptographic hashes to detect tampering with extensions or policies.
let EPNewChromeConfig =
DeviceFileEvents
| where ActionType == "FileCreated"
| where FileName has "Secure Preferences" and FolderPath has "Chrome"
| distinct DeviceName;
DeviceFileEvents
| where ActionType == "FileCreated" and FileName endswith ".crx"
| where DeviceName has_any(EPNewChromeConfig)
| summarize arg_max(Timestamp, *) by SHA1
| where isnotempty(SHA1)
| invoke FileProfile(SHA1,10000)
| where GlobalPrevalence <= 100
Browser Extension Installed Extensions With Notification Permissions
This KQL query from Bert-Jan Pals looks for extensions in the DeviceTvmBrowserExtenionKB data that have the 'notification' permission set. When an extension has the notifications permission, it can do things like, display system-level pop-up alerts (e.g., in the Windows or macOS notification tray). This can be abused by Threat Actors using malicious extensions to do things like, phish users with fake system alerts (e.g., “Your PC is infected, click here”), trick users into downloading malware or visiting scam pages. These browser extension notification pop-ups can look legitimate and bypass pop-up blockers.
let ExtentionsWithNotification = DeviceTvmBrowserExtensionsKB
| where PermissionId contains "Notification"
| summarize make_set(ExtensionId) by ExtensionId;
DeviceTvmBrowserExtensions
| where ExtensionId in (ExtentionsWithNotification)
| distinct DeviceId, ExtensionName
| summarize TotalInstalledDevices = count() by ExtensionName
| sort by TotalInstalledDevices
Top 100 critical browser extensions with the most permissions required
Another query by bert-Jan Pals that looks at the ExtensionRisk field from the DeviceTvmBrowserExtension data for any Extension that has an ExtensionRisk of "critical".
DeviceTvmBrowserExtensions
| where ExtensionRisk == "Critical"
| summarize TotalExtentions = count(), ExtentionNames = make_set(ExtensionName) by DeviceId
| join DeviceInfo on DeviceId
| project DeviceName, TotalExtentions, ExtentionNames
| top 100 by TotalExtentions
?????reseacrh????
KQL Detection – Suspicious Chrome Extension Behavior
DeviceFileEvents
| where FolderPath has "Google\\Chrome\\User Data\\Default\\Extensions"
| where FileName endswith ".js" or FileName endswith ".json"
| where InitiatingProcessFileName !in~ ("chrome.exe", "msedge.exe") // Unusual process modifying extensions
| extend ExtensionId = extract(@"Extensions\\([^\\]+)", 1, FolderPath)
| project Timestamp, DeviceName, FolderPath, FileName, ExtensionId, InitiatingProcessFileName, InitiatingProcessCommandLine
KQL Detection – Extension Installation with Unusual Permissions (From WEF or DeviceRegistryEvents)
DeviceRegistryEvents
| where RegistryKey has "Extensions"
| where RegistryValueName in~ ("permissions", "host_permissions")
| where RegistryValueData has_any ("tabs", "webRequest", "<all_urls>", "cookies", "storage", "webRequestBlocking")
| project Timestamp, DeviceName, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName
KQL Detection – Browser Developer Mode Enabled
This rule looks for browsers launched with flags commonly associated with Developer Mode, such as --load-extension, --disable-extensions-file-access-check, or --disable-extensions-http-throttling.
DeviceProcessEvents
| where FileName in~ ("chrome.exe", "msedge.exe")
| where ProcessCommandLine has_any ("--load-extension", "--disable-extensions-file-access-check", "--disable-extensions-http-throttling")
| project Timestamp, DeviceName, InitiatingProcessAccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
--load-extension: Allows manual loading of unpacked extensions—often used for testing or unsigned extensions.
--disable-extensions-file-access-check: Disables certain security checks, opening paths for abuse.
--disable-extensions-http-throttling: Often used in conjunction with dev/test extensions to reduce delays, can indicate tampering.
References
DeviceTvmBrowserExtensions (Preview)
How to check and block “malicious” browser extensions with Microsoft Defender and Intune?
Comments