Llevaba tiempo queriendo escribir sobre este tema porque, seamos honestos, las extensiones de navegador son ese punto ciego que todos tenemos. Ya sabes: los usuarios instalan cualquier cosa que les prometa «facilitar el trabajo» o darles alguna ventaja navegando, y por lo menos si se le da permisividad a los usuarios hay que estar de alerta a quién hay que sacarle el látigo.
Durante el último año he ido cazando información por artículos especializados, foros varios y noticias, recopilando IOCs y dándole vueltas a cómo plantear esto en mis entornos con Defender y Sentinel.
Porque el problema no es nuevo, pero sigue siendo brutal: esas extensiones tan «relucientes» muchas veces están ahí para robarte credenciales o cosas peores.
Así que aquí va mi experiencia implementando controles y detecciones para este tema. Spoiler: no todo lo que reluce es oro.
Para cazar posibles extensiones maliciosas me basé en la KQL de Jai Kerai en su repositorio de Github la cual busca tanto en navegadores Google Chrome y Microsoft Edge.
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);
let RiskyExtensionsWithNames = externaldata (ExtensionID: string,ExtensionURL:string, ExtensionName:string) [@'https://raw.githubusercontent.com/jkerai1/SoftwareCertificates/refs/heads/main/Bulk-IOC-CSVs/Intune/Unsanctioned_extensions_with_names.csv'] with (format=csv, ignoreFirstRecord = true);
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
//| where ExtensionID != "kbfnbcaeplbcioakkpcpgfkobkghlhen" //Grammarly
//| where RiskyExtension == "Yes"
| join kind=leftouter RiskyExtensionsWithNames on ExtensionID //if name is present in the risky list present it
| project-away ExtensionID1,ExtensionURL1
El problema de utilizar una KQL con una fuente externa es que si los indicadores son demasiados puedes saturar la consulta y acabe dando Timeout como me sucedió a mi con solo 157 indicadores de extensiones.

En un primer momento dije. Bueno, voy a filtrar a ver cuales han sido eliminadas de las tiendas d extensiones.
Para ello me generé un programita en PowerShell la cual mediante la API de Google podía validar si seguían activas.
Este Powershell disponible en mi repositorio Github carga un fichero txt con todos los ID de las extensiones que quieras verificar.
Un fichero TXT totalmente plano, una línea debajo de la otra con los ID’s. Y saca un CSV de salida donde nos mostrará el Extension ID, Extension Name, Satus y el ChromeStore URL

Las que no estén activas las eliminará del CSV.
Ahora con nos dirigiremos a nuestro Microsoft Sentinel y clickaremos en Watchlist.

Las Watchlists en Microsoft Sentinel son tablas personalizadas donde almacenas datos de referencia en formato clave-valor (normalmente desde CSVs) para correlacionar y enriquecer eventos.
Básicamente, subes listas de IPs, usuarios, hashes, activos críticos o lo que necesites, y luego las consultas en tus reglas de detección, threat hunting o playbooks usando funciones.
Clickamos sobre New para crear nuestra lista.

Le asignamos un nombre y un Alias y luego Next Source.

En la siguiente ventana nos cargará la opción para subir nuestro CSV con nuestros indicadores.

Si nuestro CSV está bien generado justo debajo nos debería aparecer algo como la siguiente imagen.

Si está todo correcto clickamos sobre Next: review + create.
Nos validará las opciones que hemos introducido y simplemente tendremos que darle a Create para que cree la Watchlist.
Ahora viene la parte más «divertida». La KQL que llama a esta Watchlist
// Load malicious extensions from Watchlist
let MaliciousExtensions = _GetWatchlist('MaliciousExtensions')
| project ExtensionID = tostring(column_ifexists("ExtensionID", "")),
ExtensionName = tostring(column_ifexists("ExtensionName", "")),
Description = tostring(column_ifexists("Description", "")),
Source = tostring(column_ifexists("Source", ""));
let MaliciousIDs = toscalar(MaliciousExtensions | summarize make_set(ExtensionID));
DeviceFileEvents
| where TimeGenerated > ago(30d)
| where ActionType == "FileCreated"
| where FileName endswith ".crx"
| where FolderPath contains "Webstore Downloads"
// Extract Extension ID from file name
| extend ExtensionID = trim_end(@"_\d{2,6}.crx", FileName)
// Early filter using hash set
| where ExtensionID in (MaliciousIDs)
// Generate extension URLs
| extend ExtensionURL = strcat("https://chrome.google.com/webstore/detail/", ExtensionID)
| extend EdgeExtensionURL = strcat("https://microsoftedge.microsoft.com/addons/detail/", ExtensionID)
// Aggregate installation information
| summarize
InstallCount = count(),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
SHA1List = make_set(SHA1),
SHA256List = make_set(SHA256)
by InitiatingProcessAccountName, DeviceName, ExtensionID, ExtensionURL, EdgeExtensionURL
// Join with extension details
| join kind=inner (MaliciousExtensions) on ExtensionID
// Project relevant fields
| project
DeviceName, InitiatingProcessAccountName, ExtensionName, ExtensionID, Description, Source,
InstallCount, FirstSeen, LastSeen, ExtensionURL, EdgeExtensionURL, SHA1List, SHA256List
// Sort by criticality
| order by InstallCount desc, FirstSeen desc
Aquí debería mostrarnos las extensiones maliciosas de los indicadores que hayamos subido.
Ahora bien, es posible que como a mi ya haya exterminado a los usu.. digo las extensiones de los equipos de los usuarios y no aparezca ninguna.

Para validar que nos está funcionando, tan fácil como realizar la prueba de agregar una extensión que tengamos identificada en nuestra Watchlist.
Por ejemplo podemos utilizar la terrible y peligrosa extensión Laser Cat, donde un gato podría destrurir nuestra web disparando rayos láser por los ojos.

Para ello volvemos a nuestra Watchlist y en el menú Update Watchlist > Edit watchlist items

Nos mostrará nuestra lista y simplemente agregamos una línea con los datos de nuestra extensión.

Clickamos y Save y esperamos unos minutos a que «la nube» haga su magia.

Este invento no solo sirve para cazar extensiones maliciosas, sino también para realizar un poco de segimiento a extensiones que no queramos que utilicen los usuarios, como VPN’s, IA’s de dudosa procedencia, etc…
En mi repositorio de Github espero poder ir haciendo acopio de indicadores actualizados de extensiones a medida que me tope con ellos.