Security Alert: Confirmed Malware
NetRef: Classroom Management
ID: abmmkhcfmfmkgeeopaedjcnckmchannj
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- NetRefView Profile
- Privacy
- Privacy Policy
- Country
- US
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- Yes
- Mailbox exists
- Yes
- Address
- 11545 West Bernardo Court Suite 204 San Diego, CA 92127 US
Manage. Monitor. Know.
NetRef is a comprehensive EdTech management and evaluation tool that enables teachers to create individual Internet policies in their classroom that create focus, eliminate distractions, and foster the most productive learning environments. It also gives administrators insight into software usage to increase time on task, identify gaps and improve EdTech ROI. This extension provides the following features for teachers and school administrators that have partnered with NetRef. Without authorization from a school's Google Domain Super Administrator, the extension lies dormant. The extension only works when force-pushed to accounts owned by the partnered school. All data given to the partnered school by this extension is owned and fully controllable by the partnered school. View Student Information - NetRef allows authorized teachers and administrators to view student's email address and their current website. View Students Browsing Activity - NetRef allows authorized teachers and administrators to view a students browsing history. This history is transported and stored securely and is only accessible by the partnered school. View Student's Current Tab - NetRef allows authorized teachers and administrators to view a screenshot of a students screen. The screenshots are transported and stored securely and is only accessibly for two weeks by the partnered school. Control Students Browsing - NetRef allows authorized teachers and administrators to control what websites their students are allowed to visit. NetRef also allows teachers and administrators to open and close tabs for students. ScreenSharing - Allow teacher to view students screens as well as share their screens with their students This is a free app but requires a partnership with NetRef to function. Please visit https://net-ref.com for more information.
The remote configuration server at webserver.net-ref.com can dynamically overwrite all three data-upload endpoints (extensionHost for activity data, filesHost for screenshot uploads, denyHost for URL blocking). A compromised server or man-in-the-middle attack could silently redirect the full stream of student screenshots, email addresses, device serial numbers, directory IDs, and browsing history to an attacker-controlled server without any client-side integrity check on the returned URLs.
parseUpdatesConfiguration(a) { a.text().then(function(b) { b = JSON.parse(b); console.log("URL config", b); browser.extensionHost = b.extensionHost; browser.filesHost = b.filesHost; browser.denyHost = b.denyHost; browser.configIsDefault = browser.extensionHost === browser.defaultExtensionHost && browser.filesHost === browser.defaultFilesHost && browser.denyHost === browser.defaultDenyHost; browser.configIsDefault && console.log("Configuration is still the default. Waiting to start updates...") })}resetUpdatesConfiguration() { this.extensionHost = this.defaultExtensionHost; this.filesHost = this.defaultFilesHost; this.denyHost = this.defaultDenyHost}The extension continuously harvests four distinct pieces of PII on a 5-second polling loop: the user's Google account email address (via chrome.identity), the device's enterprise directory ID, device serial number, and asset ID (all via chrome.enterprise.deviceAttributes). These identifiers are bundled together and transmitted to the remote server with every activity update, creating a comprehensive device-and-user fingerprint that is exfiltrated continuously.
getEmailAddress() { try { chrome.identity.getProfileUserInfo({ accountStatus: "ANY" }, function(a) { try { browser.emailAddress = encodeURIComponent(a.email).toLowerCase() } catch (b) { browser.emailAddress = "" } setTimeout(browser.getEmailAddress, 5E3) }) } catch (a) { setTimeout(browser.getEmailAddress, 5E3) }}getDirectoryId() { try { chrome.enterprise.deviceAttributes.getDirectoryDeviceId(function(a) { browser.directoryId = 0 < a.length ? a : ""; setTimeout(browser.getDirectoryId, 5E3) }) } catch (a) { setTimeout(browser.getDirectoryId, 5E3) }}getSerialNumber() { try { chrome.enterprise.deviceAttributes.getDeviceSerialNumber(function(a) { browser.machineName = 0 < a.length ? a : ""; setTimeout(browser.getSerialNumber, 5E3) }) } catch (a) { setTimeout(browser.getSerialNumber, 5E3) }}getAssetId() { try { chrome.enterprise.deviceAttributes.getDeviceAssetId(function(a) { browser.machineName = 0 < a.length ? a : ""; setTimeout(browser.getAssetId, 5E3) }) } catch (a) { setTimeout(browser.getAssetId, 5E3) }}The extension captures a JPEG screenshot of the currently visible tab using chrome.tabs.captureVisibleTab on every tab switch and on a configurable periodic timer (as low as every 5 seconds). Screenshots are stored in memory and then uploaded to files.net-ref.com. Critically, the upload endpoint is dynamically configurable by the remote server (see parseUpdatesConfiguration), meaning the destination of all screenshot data can be changed at any time without user notification.
function getCurrentSiteAndScreenshot() { clearTimeout(siteManagementId); try { chrome.tabs.query({ active: !0, lastFocusedWindow: !0 }, function(a) { if (browser.getCurrentSiteShared(a)) { let d = a[0].url; chrome.tabs.captureVisibleTab(null, { format: "jpeg", quality: config.screenshotQuality }, function(b) { void 0 === b ? console.log("Invalid screenshot, unable to continue.") : previousScreenshot !== b ? (previousScreenshot = b, browser.saveScreenshot(d, b), config.studentIsActive = !0) : console.log("Screenshot is the same.") }) } }) } catch (a) { console.log("error getting current site", a) } finally { let a = 5; config.queryDelay < a && (a = config.queryDelay); config.screenshotDelay < a && (a = config.screenshotDelay); siteManagementId = setTimeout(getCurrentSiteAndScreenshot, 1E3 * a) }}Screenshots are uploaded via a FormData POST to browser.filesHost (default: files.net-ref.com). The file is named using a path that embeds the district name and student database ID, creating an indexed, timestamped archive of a student's screen activity on the remote server. Because browser.filesHost is server-controlled and dynamically updatable, this upload can be redirected to any external endpoint.
if (e) { let g = this.districtName + "/" + this.studentDbId + "/" + moment().format("YYYYMMDD[/]H[/]m[_]s") + ".jpg"; a.append("name", g); (c = browser.getScreenshotBlob()) ? (console.log("Attempting to upload screenshot...."), a.append("screenshot", c), fetch(browser.filesHost, { method: "POST", body: a }).then(function(k) { k.ok ? (console.log("Upload screenshot success."), b.screenshotFileName = g, browser.resetScreenshot(), browser.markScreenshotSite(), d.lastScreenshotUploadTime = Date.now()) : console.log("Error uploading screenshot, code: " + k.status); d.sendStudentActivity(b) }).catch(function(k) { console.log("Failed to upload screenshot: ", k); d.sendStudentActivity(b) })) : (console.log("Invalid screenshot..."), d.sendStudentActivity(b))}Every activity update POST includes the student's email address and machine serial number in the URL query string (unencrypted beyond HTTPS), alongside a JSON body containing encoded current URL, page title, per-site byte maps, and blocked site lists. The server response to this POST is parsed as JSON and fed directly into config.parseConfig(), which can issue remote commands (open tabs, close tabs, push notifications, change endpoints). This bidirectional channel gives the remote server full remote control over the browser session.
sendStudentActivity(a) { let b = browser.extensionHost, d = browser.emailAddress, c = browser.machineName, f = void 0 !== a.screenshotFileName && 0 !== a.screenshotFileName.length; a = JSON.stringify(a); console.log("Sending update for: " + browser.currentTitle); console.log("Sending update for: " + browser.currentWebsite); fetch(b + "?type\x3dchrome\x26email\x3d" + d + "\x26machine\x3d" + c + "\x26version\x3d" + extensionVersion, { method: "POST", headers: { "Content-Type": "application/json; charset\x3dUTF-8" }, body: a }).then(function(e) { 200 === e.status ? e.text().then(function(h) { try { let g = JSON.parse(h); config.parseConfig(g) } catch (g) { console.log(g), config.reset() } }) : (205 === e.status ? console.log("Outside School Network") : console.log("Request: " + b + "?type\x3dchrome : Email:" + d + " :: Status: " + e.status), config.reset()) }).catch(function(e) { console.log("ERROR", e); config.reset() })}The server response to the periodic activity update can include commands: oS (open tab to arbitrary URL), cS (close specified tabs), mS/mT (push notification with server-controlled title and message text), tL (set tab limit), and sST (trigger network speed test). This constitutes a remote command-and-control channel embedded inside what appears to be a configuration response, allowing the server to manipulate the student's browser state at will.
if (this.shouldControlTimeRange()) { b && limitTabs(a.tL); try { openTab(a.oS), closeTab(a.cS) } catch (c) { console.log("Error configuring tabs", c) } b = a.mS; d = a.mT; "" !== b && "" !== d && pushNotification(b, d); if (!0 === a.sST) try { browser.performSpeedTest() } catch (c) { browser.lastSpeedTestDate = 0, console.log("Error performing speed test", c) }}The extension intercepts the responseHeaders of every completed web request across all URLs to build a per-site bandwidth usage map (storeSiteByteMap), which is then transmitted to the server. Additionally, active tab URLs are polled every 500ms and collected into activeTabUrls, which is included in the activity upload. Together these provide the remote server with a near-real-time record of every site visited and how much data was transferred.
chrome.webRequest.onCompleted.addListener(config.storeSiteByteMap, { urls: ["\x3call_urls\x3e"]}, { "responseHeaders"});...function getActiveTabUrls() { try { chrome.tabs.query({ active: !0 }, function(a) { activeTabUrls = []; for (let d of a) activeTabUrls.push(d.url) }) } catch (a) { console.log("error getting active tab URLs", a) }}...setInterval(getActiveTabUrls, 500);A content script injected into every page via the matches:<all_urls> rule tracks mouse movement events and fires chrome.runtime.sendMessage to the background page at most once per 5 seconds. While throttled, this creates a persistent behavioral tracking signal (user presence/activity) from inside every webpage the student visits, reported to the background script which relays it to the remote server as studentIsActive status.
'use strict';let canSendMessage = !0;var timeoutResetInterval = setInterval(() => { canSendMessage = !0}, 5E3);document.onmousemove = function(a) { canSendMessage && (chrome.runtime.sendMessage({}, function(b) {}), canSendMessage = !1, clearInterval(timeoutResetInterval), timeoutResetInterval = setInterval(() => { canSendMessage = !0 }, 5E3))};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 |
|---|---|
| 2023.7 | 8 |
Files with findings
4 distinct paths — top paths by unique finding count:
- background/config.min.js3
- background/browser.min.js2
- background/netrefPlugin.min.js2
- background/helpers/mouseMoveDetection.min.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.