top of page

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:


  1. Remote Browser Debugging Abuse

  2. Browser Process Memory Dumping

  3. Direct Cookie File Extraction (e.g., Chrome/Edge user data paths)

  4. Malicious Browser Extensions with elevated privileges


The next part of this series will review some Microsoft KQL queries to detect these types of activities.


  1. 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

  1. 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).


  1. 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.


  1. 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:


  1. Discovering which extensions are installed across endpoints

  2. 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?

 
 
 

Recent Posts

See All
Key Cybersecurity Metric Concepts

The Importance, and Challenge of Cybersecurity Metrics Metrics are foundational to driving and refining business processes, and...

 
 
 

Comments


Post: Blog2_Post
  • Facebook
  • Twitter
  • LinkedIn

©2021 by croninity. Proudly created with Wix.com

bottom of page