Last Update: December 23, 2024
BYeric
Keywords
Background
Recently, emails sent by a client of mine to a government body were rejected. The error information provided by the email server was "451 4.7.5 Temporary error evaluating DMARC policy". This is not a common error, and there isn't much information available online. Some sources suggested it could be due to a DNS issue, where the DMARC record couldn't be found, at least temporarily. However, many attempts to resend the emails failed with the same error. This ruled out a DNS record issue because: 1) The record was double-checked and verified manually (_dmarc.______.com.au. 300 IN TXT "v=DMARC1; p=none; aspf=r"
); 2) Emails to all other addresses were successful; and 3) While a name server might experience temporary difficulties, it wouldn't persist for an extended period. I contacted the government agency's IT department, who provided helpful details about the situation.
They explained that when an email reaches their server (mail gateway), their security products flagged it as a threat due to the embedded links to my client's website (included in the email signature). The email security system considered my client's website to contain malicious code, posing a potential threat.
The website in question runs on WordPress with many popular plugins installed. A quick check revealed some JavaScript code flagged as malware by a security checking website.
This was concerning. How dangerous could this be? Was my client's website compromised?
A compromised website had several implications:
- Some WordPress plugins might contain malicious code.
- If the malicious code wasn't intentionally included in the plugin, how did it get there? Was there a security flaw or a backdoor?
Another possibility was: "Is this code actually malicious?"
Melicious Code Definition
Malicious code is any software or script intentionally designed to cause harm, exploit vulnerabilities, disrupt operations, or compromise the confidentiality, integrity, or availability of systems, networks, or data. Most importantly, malicious code aims to cause harm, steal information, disrupt services, damage systems, or perform cryptojacking. For attacks involving information theft or monetary gain, the code must contact a third-party server (the mothership). This type is the most dangerous, as sensitive information could be leaked to anyone visiting my client's website.
I'm not a security researcher and can't read obfuscated code, but we have AI tools like ChatGPT to analyze it.
ChatGPT
I submitted the code from plugins/google-analytics-premium/assets/js/frontend.min-7.11.0.js
to ChatGPT (model: o1) for analysis. A small portion of the code looked like this:
;var MonsterInsights=function(){var t=[],a='';this.setLastClicked=function(e,n,i){e=typeof e!=='undefined'?e:[];n=typeof n!=='undefined'?n:[];i=typeof i!=='undefined'?i:!1;t.valuesArray=e;t.fieldsArray=n};this.getLastClicked=function(){return t};this.setInternalAsOutboundCategory=function(e){a=e};this.getInternalAsOutboundCategory=function(){return a};this.sendEvent=function(t){e([],t)};function s(){if(window.monsterinsights_debug_mode){return!0}
else{return!1}};function e(e,n){e=typeof e!=='undefined'?e:[];n=typeof n!=='undefined'?n:{};__gaTracker('send',n);t.valuesArray=e;t.fieldsArray=n;t.tracked=!0;i('Tracked: '+e.type);i(t)};function n(e){e=typeof e!=='undefined'?e:[];t.valuesArray=e;t.fieldsArray=[];t.tracked=!1;i('Not Tracked: '+e.exit);i(t)};function i(e){if(s()){console.dir(e)}};function o(e){return e.replace(/^\s+|\s+$/gm,'')};function f(){var n=0,e=document.domain,i=e.split('.'),t='_gd'+(new Date()).getTime();while(n<(i.length-1)&&document.cookie.indexOf(t+'='+t)==-1){e=i.slice(-1-(++n)).join('.');document.cookie=t+'='+t+';domain='+e+';'};document.cookie=t+'=;expires=Thu, 01 Jan 1970 00:00:01 GMT;domain='+e+';';return e};function u(e){e=e.toString();e=e.substring(0,(e.indexOf('#')==-1)?e.length:e.indexOf('#'));e=e.substring(0,(e.indexOf('?')==-1)?e.length:e.indexOf('?'));e=e.substring(e.lastIndexOf('/')+1,e.length);if(e.length>0&&e.indexOf('.')!==-1){e=e.substring(e.indexOf('.')+1);return e}
else{return''}};...
The response from ChatGPT looks like this:
This code is designed to hook into user interactions and send detailed information about their behavior—such as what links they click, what kind of links they are (internal, external, mailto, tel, downloads), and even text labels associated with those links—to a tracking function (__gaTracker). While code like this is often part of legitimate analytics frameworks (in this case, it appears related to the MonsterInsights plugin that integrates Google Analytics into websites), it can be considered “malicious” from a privacy or security standpoint for the following reasons:
User Behavior Tracking Without Explicit Consent: The code listens to all click events on the page (document.body.addEventListener('click', v, !1)) and extracts information about each link the user interacts with. It captures details such as the link’s destination, the link text, the type of link (e.g., download, outbound, internal-as-outbound), and sends these details to __gaTracker. If implemented without proper user consent or notification, this can be deemed an invasion of privacy.
Covert Data Transmission to External Services: The code relies on __gaTracker('send', ...) calls to push user interaction data to Google Analytics (or a similar analytics endpoint). This means that every time a user clicks a link, their action is recorded and transmitted off-site. Without proper disclosure, users may not realize their behavior is being monitored and analyzed.
Altering Default Link Behavior to Ensure Data Collection: For outbound or special types of links, the code prevents the browser’s default navigation behavior and delays it until an event (analytics hit) is sent. This involves using onbeforeunload and setTimeout calls to ensure the tracking event is dispatched before the user leaves the page, effectively modifying the normal user experience for the sake of capturing data. Manipulating navigation flow for data collection can be seen as intrusive.
Gathering Potentially Sensitive Data: The code attempts to determine link text or labels (g(e), x(e) functions) and uses attributes like data-vars-ga-label. If these labels or link texts include user-generated content or sensitive details, it could expose information the user may not expect to be tracked. Even if it’s not explicitly stealing passwords or personal identifiable information, the aggregated behavioral data can be considered sensitive.
Conclusion: While the snippet may not be malicious in the sense of injecting malware or stealing login credentials, it is definitely engineered for comprehensive behavioral tracking. In a setting where users are unaware or have not consented to such detailed monitoring, this code can be considered malicious due to its invasive surveillance nature, undisclosed data transmission, and interference with normal user navigation.
So the code is not malicious or may not be as malicious as you might think, but probably it is enough for some security products to flag it. Another question here is if we can actually trust ChatGPT's analysis? How about let's just read the code ourselves.
But ironically, we still need ChatGPT's help to decompile the obfuscated code. The result is:
/**
* MonsterInsights - A JavaScript tracking library that handles
* various user click events and sends data to Google Analytics via __gaTracker.
*/
;(function() {
// MonsterInsights constructor
var MonsterInsights = function() {
// Store information about last clicked element
// (used for capturing context of the tracked event)
var lastClickedData = {
valuesArray: [],
fieldsArray: [],
tracked: false
};
// Category to label internal links as outbound
// (used when the site wants to treat certain internal paths as external/outbound)
var internalAsOutboundCategory = '';
/**
* Set last clicked element data.
* @param {Array} values - Additional values to track.
* @param {Array} fields - GA fields/parameters.
* @param {Boolean} tracked - Whether the event was tracked.
*/
this.setLastClicked = function(values, fields, tracked) {
values = (typeof values !== 'undefined') ? values : [];
fields = (typeof fields !== 'undefined') ? fields : [];
tracked = (typeof tracked !== 'undefined') ? tracked : false;
lastClickedData.valuesArray = values;
lastClickedData.fieldsArray = fields;
lastClickedData.tracked = tracked;
};
/**
* Get data related to the last clicked element.
*/
this.getLastClicked = function() {
return lastClickedData;
};
/**
* Set the category string used to label "internal-as-outbound" links.
*/
this.setInternalAsOutboundCategory = function(category) {
internalAsOutboundCategory = category;
};
/**
* Retrieve the "internal-as-outbound" category.
*/
this.getInternalAsOutboundCategory = function() {
return internalAsOutboundCategory;
};
/**
* Send an event to GA.
* @param {Object} payload - GA fields object.
*/
this.sendEvent = function(payload) {
trackEvent([], payload);
};
/**
* Check if debug mode is enabled.
* If monsterinsights_debug_mode is set on the window, we log debug data.
*/
function isDebugMode() {
return !!window.monsterinsights_debug_mode;
}
/**
* Actually send the data to Google Analytics (__gaTracker).
* @param {Array} values - Additional info about the event.
* @param {Object} fields - GA field-object, e.g. { hitType: 'event', eventCategory: ... }
*/
function trackEvent(values, fields) {
values = (typeof values !== 'undefined') ? values : [];
fields = (typeof fields !== 'undefined') ? fields : {};
// Send event to GA
__gaTracker('send', fields);
// Update lastClickedData
lastClickedData.valuesArray = values;
lastClickedData.fieldsArray = fields;
lastClickedData.tracked = true;
debugLog('Tracked: ' + values.type);
debugLog(lastClickedData);
}
/**
* Mark an event as not tracked (for example, if the conditions to send an event were not met).
* @param {Array} values - Additional info about the event that was not tracked.
*/
function notTracked(values) {
values = (typeof values !== 'undefined') ? values : [];
lastClickedData.valuesArray = values;
lastClickedData.fieldsArray = [];
lastClickedData.tracked = false;
debugLog('Not Tracked: ' + values.exit);
debugLog(lastClickedData);
}
/**
* Print debug information to the console if debug mode is active.
*/
function debugLog(msg) {
if (isDebugMode()) {
console.dir(msg);
}
}
/**
* Trim whitespace from the beginning and end of a string.
*/
function trimString(str) {
return str.replace(/^\s+|\s+$/gm, '');
}
/**
* Find the root domain by gradually moving up through the subdomains
* and setting a test cookie. Once the cookie is accepted, we found the root domain.
*/
function getRootDomain() {
var domain = document.domain,
parts = domain.split('.'),
testCookie = '_gd' + (new Date()).getTime(),
i = 0;
// Move from subdomain to main domain until the cookie is accepted
while (i < parts.length - 1 && document.cookie.indexOf(testCookie + '=' + testCookie) === -1) {
domain = parts.slice(-1 - (++i)).join('.');
document.cookie = testCookie + '=' + testCookie + ';domain=' + domain + ';';
}
// Remove the test cookie
document.cookie = testCookie + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;domain=' + domain + ';';
return domain;
}
/**
* Extract the file extension from a given URL (without query or hash).
* Example: "http://site.com/path/image.jpg?size=large#view" -> "jpg"
*/
function getFileExtension(url) {
url = url.toString();
// Cut off query string and hash
url = url.substring(0, (url.indexOf('#') === -1) ? url.length : url.indexOf('#'));
url = url.substring(0, (url.indexOf('?') === -1) ? url.length : url.indexOf('?'));
// Extract the portion after the last slash
url = url.substring(url.lastIndexOf('/') + 1, url.length);
if (url.length > 0 && url.indexOf('.') !== -1) {
return url.substring(url.indexOf('.') + 1);
}
return '';
}
/**
* Check if the GA tracker (__gaTracker) is loaded.
*/
function isGALoaded() {
return (
typeof __gaTracker !== 'undefined' &&
__gaTracker &&
__gaTracker.hasOwnProperty('loaded') &&
__gaTracker.loaded === true
);
}
/**
* Check if the event involved a click that we want to track:
* left-click (which==1), middle-click (which==2), or if meta/ctrl/shift/alt keys were pressed.
*/
function isValidClick(e) {
return e.which === 1 || e.which === 2 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey;
}
/**
* Retrieve the list of allowed download extensions from monsterinsights_frontend.
*/
function getDownloadExtensions() {
var exts = [];
if (typeof monsterinsights_frontend.download_extensions === 'string') {
exts = monsterinsights_frontend.download_extensions.split(',');
}
return exts;
}
/**
* Retrieve configured inbound paths from monsterinsights_frontend.
* (Links that should be treated as 'internal-as-outbound'.)
*/
function getInboundPaths() {
var paths = [];
if (typeof monsterinsights_frontend.inbound_paths === 'string') {
paths = JSON.parse(monsterinsights_frontend.inbound_paths);
}
return paths;
}
/**
* Helper to describe which mouse/keyboard modifier triggered the click (for debugging).
*/
function getClickTypeDesc(ev) {
if (ev.which === 1) {
return 'event.which=1';
} else if (ev.which === 2) {
return 'event.which=2';
} else if (ev.metaKey) {
return 'metaKey';
} else if (ev.ctrlKey) {
return 'ctrlKey';
} else if (ev.shiftKey) {
return 'shiftKey';
} else if (ev.altKey) {
return 'altKey';
}
return '';
}
/**
* Determine link type:
* - internal
* - external
* - mailto
* - tel
* - download
* - internal-as-outbound
* - cross-hostname
* This logic also references configured inbound paths for treating certain internal paths as outbound.
*/
function determineLinkType(link) {
var downloadExts = getDownloadExtensions(),
inboundPaths = getInboundPaths(),
linkType = 'unknown',
linkHref = link.href.toString(),
ext = getFileExtension(linkHref),
rootDomain = getRootDomain(),
linkHost = link.hostname,
linkProtocol = link.protocol,
linkPath = link.pathname,
customCategory = link.getAttribute('data-vars-ga-category');
// If link has a custom GA category set, return that
if (customCategory) {
return customCategory;
}
// Various checks to label the link
if (linkHref.match(/^javascript:/i)) {
linkType = 'internal';
} else if (linkProtocol && (trimString(linkProtocol) === 'tel' || trimString(linkProtocol) === 'tel:')) {
linkType = 'tel';
} else if (linkProtocol && (trimString(linkProtocol) === 'mailto' || trimString(linkProtocol) === 'mailto:')) {
linkType = 'mailto';
}
// If the link's hostname doesn't match root domain, it's external
else if (
linkHost &&
rootDomain &&
linkHost.length > 0 &&
rootDomain.length > 0 &&
!linkHost.endsWith('.' + rootDomain) &&
linkHost !== rootDomain
) {
linkType = 'external';
}
// "Internal-as-outbound" logic: If there's an inbound path that matches the link's path
else if (linkPath && JSON.stringify(inboundPaths) !== '{}' && linkPath.length > 0) {
for (var i = 0; i < inboundPaths.length; i++) {
if (
inboundPaths[i].path &&
inboundPaths[i].label &&
inboundPaths[i].path.length > 0 &&
inboundPaths[i].label.length > 0 &&
linkPath.startsWith(inboundPaths[i].path)
) {
linkType = 'internal-as-outbound';
internalAsOutboundCategory = 'outbound-link-' + inboundPaths[i].label;
break;
}
}
}
// Experimental cross-domain checks
else if (
linkHost &&
window.monsterinsights_experimental_mode &&
linkHost.length > 0 &&
document.domain.length > 0 &&
linkHost !== document.domain
) {
linkType = 'cross-hostname';
}
// If extension matches a download extension, override to "download"
if (
ext &&
(linkType === 'unknown' || linkType === 'external') &&
downloadExts.length > 0 &&
ext.length > 0
) {
for (var j = 0; j < downloadExts.length; j++) {
if (
downloadExts[j].length > 0 &&
(linkHref.endsWith(downloadExts[j]) || downloadExts[j] === ext)
) {
linkType = 'download';
break;
}
}
}
// Default to "internal" if still unknown
if (linkType === 'unknown') {
linkType = 'internal';
}
return linkType;
}
/**
* Determine the link's target attribute to see if it opens in a new window/tab.
*/
function getLinkTarget(event, eventObject) {
var target = (event.target && !event.target.match(/^_(self|parent|top)$/i))
? event.target
: false;
// If user did Ctrl+click, Shift+click, etc., force new tab
if (
eventObject.ctrlKey ||
eventObject.shiftKey ||
eventObject.metaKey ||
eventObject.which === 2
) {
target = '_blank';
}
return target;
}
/**
* Heuristic to retrieve a user-friendly label for the element:
* check data-vars-ga-label, title, innerText, aria-label, alt, or textContent.
*/
function getElementLabel(el) {
if (el.getAttribute('data-vars-ga-label') &&
el.getAttribute('data-vars-ga-label').replace(/\n/ig, '')) {
return el.getAttribute('data-vars-ga-label').replace(/\n/ig, '');
} else if (el.title && el.title.replace(/\n/ig, '')) {
return el.title.replace(/\n/ig, '');
} else if (el.innerText && el.innerText.replace(/\n/ig, '')) {
return el.innerText.replace(/\n/ig, '');
} else if (
el.getAttribute('aria-label') &&
el.getAttribute('aria-label').replace(/\n/ig, '')
) {
return el.getAttribute('aria-label').replace(/\n/ig, '');
} else if (el.alt && el.alt.replace(/\n/ig, '')) {
return el.alt.replace(/\n/ig, '');
} else if (el.textContent && el.textContent.replace(/\n/ig, '')) {
return el.textContent.replace(/\n/ig, '');
}
return undefined;
}
/**
* If the element has no direct label, we check its children up to 99 children deep.
*/
function getNestedLabel(el) {
var children = el.children,
count = 0,
child, label;
for (var i = 0; i < children.length; i++) {
child = children[i];
label = getElementLabel(child);
if (label) {
return label;
}
count++;
if (count === 99) {
// Limit how many children we check
return undefined;
}
}
return undefined;
}
/**
* The main click handler (attached to the body).
* It walks up to find the nearest anchor, identifies link type, and sends an event if necessary.
*/
function handleClick(event) {
var srcElement = event.srcElement || event.target,
trackingPayload = {},
linkElement,
linkHref,
linkType,
linkTarget,
gaFields;
// Prepare info for debugging
trackingPayload.el = srcElement;
trackingPayload.ga_loaded = isGALoaded();
trackingPayload.click_type = getClickTypeDesc(event);
// If GA isn't loaded or the click is invalid, we bail out.
if (!isGALoaded() || !isValidClick(event)) {
trackingPayload.exit = 'loaded';
notTracked(trackingPayload);
return;
}
// Walk up the DOM tree until we find an anchor tag with an href.
while (
srcElement &&
(typeof srcElement.tagName === 'undefined' ||
srcElement.tagName.toLowerCase() !== 'a' ||
!srcElement.href)
) {
srcElement = srcElement.parentNode;
}
// If we found a link
if (srcElement && srcElement.href && !srcElement.hasAttribute('xlink:href')) {
linkElement = srcElement;
linkHref = linkElement.href;
linkType = determineLinkType(linkElement);
linkTarget = getLinkTarget(linkElement, event);
trackingPayload.el = linkElement;
trackingPayload.el_href = linkElement.href;
trackingPayload.el_protocol = linkElement.protocol;
trackingPayload.el_hostname = linkElement.hostname;
trackingPayload.el_port = linkElement.port;
trackingPayload.el_pathname = linkElement.pathname;
trackingPayload.el_search = linkElement.search;
trackingPayload.el_hash = linkElement.hash;
trackingPayload.el_host = linkElement.host;
trackingPayload.debug_mode = isDebugMode();
trackingPayload.download_extensions = getDownloadExtensions();
trackingPayload.inbound_paths = getInboundPaths();
trackingPayload.home_url = monsterinsights_frontend.home_url;
trackingPayload.link = linkHref;
trackingPayload.extension = getFileExtension(linkHref);
trackingPayload.type = linkType;
trackingPayload.target = linkTarget;
trackingPayload.title = getElementLabel(linkElement);
// If no direct label, try to get from children
if (!trackingPayload.label && !trackingPayload.title) {
trackingPayload.title = getNestedLabel(linkElement);
}
// For non-internal, non-javascript links, we track them.
if (linkType !== 'internal' && linkType !== 'javascript') {
var navigated = false;
var navigateNow = function() {
if (navigated) return;
navigated = true;
window.location.href = linkHref;
};
// Callbacks for final fallback
var fallbackExternal = function() {
trackingPayload.exit = 'external';
notTracked(trackingPayload);
};
var fallbackInternalAsOutbound = function() {
trackingPayload.exit = 'internal-as-outbound';
notTracked(trackingPayload);
};
var fallbackCrossHostname = function() {
trackingPayload.exit = 'cross-hostname';
notTracked(trackingPayload);
};
// GA field object
gaFields = {};
// If the link opens in a new tab or is mailto/tel, track event and do not block navigation
if (linkTarget || linkType === 'mailto' || linkType === 'tel') {
if (linkType === 'download') {
gaFields = {
hitType: 'event',
eventCategory: 'download',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'tel') {
gaFields = {
hitType: 'event',
eventCategory: 'tel',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel:
linkElement.getAttribute('data-vars-ga-label') ||
(trackingPayload.title ? trackingPayload.title.replace('tel:', '') : '')
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'mailto') {
gaFields = {
hitType: 'event',
eventCategory: 'mailto',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel:
linkElement.getAttribute('data-vars-ga-label') ||
(trackingPayload.title ? trackingPayload.title.replace('mailto:', '') : '')
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'internal-as-outbound') {
gaFields = {
hitType: 'event',
eventCategory: internalAsOutboundCategory,
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'external') {
gaFields = {
hitType: 'event',
eventCategory: 'outbound-link',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'cross-hostname') {
gaFields = {
hitType: 'event',
eventCategory: 'cross-hostname',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title
};
trackEvent(trackingPayload, gaFields);
} else {
// Default
if (linkType && linkType !== 'internal') {
gaFields = {
hitType: 'event',
eventCategory: linkType,
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title
};
trackEvent(trackingPayload, gaFields);
} else {
trackingPayload.exit = 'type';
notTracked(trackingPayload);
}
}
}
// If the link does not open in a new tab (and isn't mailto/tel),
// we block the default navigation to ensure the event is sent first.
else {
if (
linkType !== 'cross-hostname' &&
linkType !== 'external' &&
linkType !== 'internal-as-outbound'
) {
// Prevent default
if (!event.defaultPrevented) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
}
// Build fields for GA event
if (linkType === 'download') {
gaFields = {
hitType: 'event',
eventCategory: 'download',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title,
hitCallback: navigateNow
};
trackEvent(trackingPayload, gaFields);
} else if (linkType === 'internal-as-outbound') {
// Use onbeforeunload to catch the link click
window.onbeforeunload = function(unloadEvent) {
if (!event.defaultPrevented) {
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
}
gaFields = {
hitType: 'event',
eventCategory: internalAsOutboundCategory,
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title,
hitCallback: navigateNow
};
if (navigator.sendBeacon) {
gaFields.transport = 'beacon';
}
trackEvent(trackingPayload, gaFields);
setTimeout(navigateNow, 1000);
};
} else if (linkType === 'external') {
window.onbeforeunload = function(unloadEvent) {
if (!event.defaultPrevented) {
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
}
gaFields = {
hitType: 'event',
eventCategory: 'outbound-link',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title,
hitCallback: navigateNow
};
if (navigator.sendBeacon) {
gaFields.transport = 'beacon';
}
trackEvent(trackingPayload, gaFields);
setTimeout(navigateNow, 1000);
};
} else if (linkType === 'cross-hostname') {
window.onbeforeunload = function(unloadEvent) {
if (!event.defaultPrevented) {
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
}
gaFields = {
hitType: 'event',
eventCategory: 'cross-hostname',
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title,
hitCallback: navigateNow
};
if (navigator.sendBeacon) {
gaFields.transport = 'beacon';
}
trackEvent(trackingPayload, gaFields);
setTimeout(navigateNow, 1000);
};
} else {
// Default case
if (linkType && linkType !== 'internal') {
gaFields = {
hitType: 'event',
eventCategory: linkType,
eventAction: linkElement.getAttribute('data-vars-ga-action') || linkHref,
eventLabel: linkElement.getAttribute('data-vars-ga-label') || trackingPayload.title,
hitCallback: navigateNow
};
trackEvent(trackingPayload, gaFields);
} else {
trackingPayload.exit = 'type';
notTracked(trackingPayload);
}
}
// Delay navigation for 1s if not external/cross/inbound-outbound
if (
linkType !== 'external' &&
linkType !== 'cross-hostname' &&
linkType !== 'internal-as-outbound'
) {
setTimeout(navigateNow, 1000);
} else {
// For those link types, we set a fallback
if (linkType === 'external') {
setTimeout(fallbackExternal, 1100);
} else if (linkType === 'cross-hostname') {
setTimeout(fallbackCrossHostname, 1100);
} else {
setTimeout(fallbackInternalAsOutbound, 1100);
}
}
}
} else {
// If it's an internal or javascript link, do not track
trackingPayload.exit = 'internal';
notTracked(trackingPayload);
}
} else {
// If not an <a> with href, not tracked
trackingPayload.exit = 'notlink';
notTracked(trackingPayload);
}
}
/**
* Hashchange handler. If configured, we send a pageview on hash changes.
*/
var currentHash = window.location.hash;
function handleHashChange() {
if (monsterinsights_frontend.hash_tracking === 'true' && currentHash !== window.location.hash) {
currentHash = window.location.hash;
__gaTracker('set', 'page', location.pathname + location.search + location.hash);
__gaTracker('send', 'pageview');
debugLog('Hash change to: ' + location.pathname + location.search + location.hash);
} else {
debugLog('Hash change to (untracked): ' + location.pathname + location.search + location.hash);
}
}
// Attach event listeners depending on browser (addEventListener vs attachEvent)
var win = window;
if (win.addEventListener) {
// On load, attach click handler
win.addEventListener('load', function() {
document.body.addEventListener('click', handleClick, false);
}, false);
// On hashchange
window.addEventListener('hashchange', handleHashChange, false);
} else if (win.attachEvent) {
// Older IE
win.attachEvent('onload', function() {
document.body.attachEvent('onclick', handleClick);
});
window.attachEvent('onhashchange', handleHashChange);
}
/**
* Polyfills
*/
// endsWith
if (typeof String.prototype.endsWith !== 'function') {
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
}
// startsWith
if (typeof String.prototype.startsWith !== 'function') {
String.prototype.startsWith = function(prefix) {
return this.indexOf(prefix) === 0;
};
}
// lastIndexOf for Arrays
if (typeof Array.prototype.lastIndexOf !== 'function') {
Array.prototype.lastIndexOf = function(searchElement) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var n, k,
t = Object(this),
len = t.length >>> 0;
if (len === 0) {
return -1;
}
n = len - 1;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n !== n) {
n = 0;
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
}; // End MonsterInsights constructor
// Create a single instance of MonsterInsights for usage
var MonsterInsightsObject = new MonsterInsights();
})();
Going through the code, it doesn't actually do anything suspicious other than recording browser events (user behavior) aggressively and sending them to google analytics. Just to make sure ChatGPT didn't make up the code, I went through the obfuscated code and the deobfuscated code with some key functions, variables and polyfills such as onbeforeunload
, monsterinsights_frontend.inbound_paths
, __gaTracker
and document.body.addEventListener
. It looks like the deobfuscated code matches the obfuscated code.
With the same procedure, ChatGPT decompiled and analysed the obfuscated code from other files that were marked as malicious by the security check website, and ChatGPT concluded that those code was not malicious.
Other Options
There are some tools (online) that provide services to decompile the obfuscated code, such as:
However the results were not ideal, for the same obfuscated code from file plugins/google-analytics-premium/assets/js/frontend.min-7.11.0.js
,
Error compiling input:
Line 21: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 22: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 23: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 24: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 25: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 26: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 27: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 30: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 32: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 34: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 36: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
Line 37: Parse error. IE8 (and below) will parse trailing commas in array and object literals incorrectly. If you are targeting newer versions of JS, set the appropriate language_in option.
;
var MonsterInsights = function() {
var t = [],
a = '';
this.setLastClicked = function(e, n, i) {
e = typeof e !== 'undefined' ? e : [];
n = typeof n !== 'undefined' ? n : [];
i = typeof i !== 'undefined' ? i : !1;
t.valuesArray = e;
t.fieldsArray = n
};
this.getLastClicked = function() {
return t
};
this.setInternalAsOutboundCategory = function(e) {
a = e
};
this.getInternalAsOutboundCategory = function() {
return a
};
this.sendEvent = function(t) {
e([], t)
};
function s() {
if (window.monsterinsights_debug_mode) {
return !0
} else {
return !1
}
};
function e(e, n) {
e = typeof e !== 'undefined' ? e : [];
n = typeof n !== 'undefined' ? n : {};
__gaTracker('send', n);
t.valuesArray = e;
t.fieldsArray = n;
t.tracked = !0;
i('Tracked: ' + e.type);
i(t)
};
function n(e) {
e = typeof e !== 'undefined' ? e : [];
t.valuesArray = e;
t.fieldsArray = [];
t.tracked = !1;
i('Not Tracked: ' + e.exit);
i(t)
};
function i(e) {
if (s()) {
console.dir(e)
}
};
function o(e) {
return e.replace(/^\s+|\s+$/gm, '')
};
function f() {
var n = 0,
e = document.domain,
i = e.split('.'),
t = '_gd' + (new Date()).getTime();
while (n < (i.length - 1) && document.cookie.indexOf(t + '=' + t) == -1) {
e = i.slice(-1 - (++n)).join('.');
document.cookie = t + '=' + t + ';domain=' + e + ';'
};
document.cookie = t + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;domain=' + e + ';';
return e
};
function u(e) {
e = e.toString();
e = e.substring(0, (e.indexOf('#') == -1) ? e.length : e.indexOf('#'));
e = e.substring(0, (e.indexOf('?') == -1) ? e.length : e.indexOf('?'));
e = e.substring(e.lastIndexOf('/') + 1, e.length);
if (e.length > 0 && e.indexOf('.') !== -1) {
e = e.substring(e.indexOf('.') + 1);
return e
} else {
return ''
}
};
function h() {
return typeof(__gaTracker) !== 'undefined' && __gaTracker && __gaTracker.hasOwnProperty('loaded') && __gaTracker.loaded == !0
};
function y(e) {
return e.which == 1 || e.which == 2 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey
};
function c() {
var e = [];
if (typeof monsterinsights_frontend.download_extensions == 'string') {
e = monsterinsights_frontend.download_extensions.split(',')
};
return e
};
function d() {
var e = [];
if (typeof monsterinsights_frontend.inbound_paths == 'string') {
e = JSON.parse(monsterinsights_frontend.inbound_paths)
};
return e
};
function b(e) {
if (e.which == 1) {
return 'event.which=1'
} else if (e.which == 2) {
return 'event.which=2'
} else if (e.metaKey) {
return 'metaKey'
} else if (e.ctrlKey) {
return 'ctrlKey'
} else if (e.shiftKey) {
return 'shiftKey'
} else if (e.altKey) {
return 'altKey'
} else {
return ''
}
};
function m(e) {
var h = c(),
i = d(),
t = 'unknown',
g = e.href,
p = u(e.href),
v = f(),
l = e.hostname,
r = e.protocol,
y = e.pathname;
g = g.toString();
var s, b, m = e.getAttribute('data-vars-ga-category');
if (m) {
return m
};
if (g.match(/^javascript\:/i)) {
t = 'internal'
} else if (r && r.length > 0 && (o(r) == 'tel' || o(r) == 'tel:')) {
t = 'tel'
} else if (r && r.length > 0 && (o(r) == 'mailto' || o(r) == 'mailto:')) {
t = 'mailto'
} else if (l && v && l.length > 0 && v.length > 0 && !l.endsWith('.' + v) && l !== v) {
t = 'external'
} else if (y && JSON.stringify(i) != '{}' && y.length > 0) {
var w = i.length;
for (var n = 0; n < w; n++) {
if (i[n].path && i[n].label && i[n].path.length > 0 && i[n].label.length > 0 && y.startsWith(i[n].path)) {
t = 'internal-as-outbound';
a = 'outbound-link-' + i[n].label;
break
}
}
} else if (l && window.monsterinsights_experimental_mode && l.length > 0 && document.domain.length > 0 && l !== document.domain) {
t = 'cross-hostname'
};
if (p && (t === 'unknown' || 'external' === t) && h.length > 0 && p.length > 0) {
for (s = 0, b = h.length; s < b; ++s) {
if (h[s].length > 0 && (g.endsWith(h[s]) || h[s] == p)) {
t = 'download';
break
}
}
};
if (t === 'unknown') {
t = 'internal'
};
return t
};
function w(e, t) {
var n = (e.target && !e.target.match(/^_(self|parent|top)$/i)) ? e.target : !1;
if (t.ctrlKey || t.shiftKey || t.metaKey || t.which == 2) {
n = '_blank'
};
return n
};
function g(e) {
if (e.getAttribute('data-vars-ga-label') && e.getAttribute('data-vars-ga-label').replace(/\n/ig, '')) {
return e.getAttribute('data-vars-ga-label').replace(/\n/ig, '')
} else if (e.title && e.title.replace(/\n/ig, '')) {
return e.title.replace(/\n/ig, '')
} else if (e.innerText && e.innerText.replace(/\n/ig, '')) {
return e.innerText.replace(/\n/ig, '')
} else if (e.getAttribute('aria-label') && e.getAttribute('aria-label').replace(/\n/ig, '')) {
return e.getAttribute('aria-label').replace(/\n/ig, '')
} else if (e.alt && e.alt.replace(/\n/ig, '')) {
return e.alt.replace(/\n/ig, '')
} else if (e.textContent && e.textContent.replace(/\n/ig, '')) {
return e.textContent.replace(/\n/ig, '')
} else {
return undefined
}
};
function x(e) {
var i = e.children,
a = 0,
r, n;
for (var t = 0; t < i.length; t++) {
r = i[t];
n = g(r);
if (n) {
return n
};
if (a == 99) {
return undefined
};
a++
};
return undefined
};
function v(i) {
var o = i.srcElement || i.target,
t = [],
l;
t.el = o;
t.ga_loaded = h();
t.click_type = b(i);
if (!h() || !y(i)) {
t.exit = 'loaded';
n(t);
return
}
while (o && (typeof o.tagName == 'undefined' || o.tagName.toLowerCase() != 'a' || !o.href)) {
o = o.parentNode
};
if (o && o.href && !o.hasAttribute('xlink:href')) {
var v = o.href,
E = u(o.href),
D = c(),
I = d(),
M = monsterinsights_frontend.home_url,
S = f(),
r = m(o),
C = w(o, i),
p = o.getAttribute('data-vars-ga-action'),
k = o.getAttribute('data-vars-ga-label');
t.el = o;
t.el_href = o.href;
t.el_protocol = o.protocol;
t.el_hostname = o.hostname;
t.el_port = o.port;
t.el_pathname = o.pathname;
t.el_search = o.search;
t.el_hash = o.hash;
t.el_host = o.host;
t.debug_mode = s();
t.download_extensions = D;
t.inbound_paths = I;
t.home_url = M;
t.link = v;
t.extension = E;
t.type = r;
t.target = C;
t.title = g(o);
if (!t.label && !t.title) {
t.title = x(o)
};
if (r !== 'internal' && r !== 'javascript') {
var A = !1,
T = function() {
if (A) {
return
};
A = !0;
window.location.href = v
},
L = function() {
t.exit = 'external';
n(t)
},
O = function() {
t.exit = 'internal-as-outbound';
n(t)
},
K = function() {
t.exit = 'cross-hostname';
n(t)
};
if (C || r == 'mailto' || r == 'tel') {
if (r == 'download') {
l = {
hitType: 'event',
eventCategory: 'download',
eventAction: p || v,
eventLabel: k || t.title,
};
e(t, l)
} else if (r == 'tel') {
l = {
hitType: 'event',
eventCategory: 'tel',
eventAction: p || v,
eventLabel: k || t.title.replace('tel:', ''),
};
e(t, l)
} else if (r == 'mailto') {
l = {
hitType: 'event',
eventCategory: 'mailto',
eventAction: p || v,
eventLabel: k || t.title.replace('mailto:', ''),
};
e(t, l)
} else if (r == 'internal-as-outbound') {
l = {
hitType: 'event',
eventCategory: a,
eventAction: p || v,
eventLabel: k || t.title,
};
e(t, l)
} else if (r == 'external') {
l = {
hitType: 'event',
eventCategory: 'outbound-link',
eventAction: p || v,
eventLabel: k || t.title,
};
e(t, l)
} else if (r == 'cross-hostname') {
l = {
hitType: 'event',
eventCategory: 'cross-hostname',
eventAction: p || v,
eventLabel: k || t.title,
};
e(t, l)
} else {
if (r && r != 'internal') {
l = {
hitType: 'event',
eventCategory: r,
eventAction: p || v,
eventLabel: k || t.title,
};
e(t, l)
} else {
t.exit = 'type';
n(t)
}
}
} else {
if (r != 'cross-hostname' && r != 'external' && r != 'internal-as-outbound') {
if (!i.defaultPrevented) {
if (i.preventDefault) {
i.preventDefault()
} else {
i.returnValue = !1
}
}
};
if (r == 'download') {
l = {
hitType: 'event',
eventCategory: 'download',
eventAction: p || v,
eventLabel: k || t.title,
hitCallback: T,
};
e(t, l)
} else if (r == 'internal-as-outbound') {
window.onbeforeunload = function(n) {
if (!i.defaultPrevented) {
if (i.preventDefault) {
i.preventDefault()
} else {
i.returnValue = !1
}
};
l = {
hitType: 'event',
eventCategory: a,
eventAction: p || v,
eventLabel: k || t.title,
hitCallback: T,
};
if (navigator.sendBeacon) {
l.transport = 'beacon'
};
e(t, l);
setTimeout(T, 1000)
}
} else if (r == 'external') {
window.onbeforeunload = function(n) {
if (!i.defaultPrevented) {
if (i.preventDefault) {
i.preventDefault()
} else {
i.returnValue = !1
}
};
l = {
hitType: 'event',
eventCategory: 'outbound-link',
eventAction: p || v,
eventLabel: k || t.title,
hitCallback: T,
};
if (navigator.sendBeacon) {
l.transport = 'beacon'
};
e(t, l);
setTimeout(T, 1000)
}
} else if (r == 'cross-hostname') {
window.onbeforeunload = function(n) {
if (!i.defaultPrevented) {
if (i.preventDefault) {
i.preventDefault()
} else {
i.returnValue = !1
}
};
l = {
hitType: 'event',
eventCategory: 'cross-hostname',
eventAction: p || v,
eventLabel: k || t.title,
hitCallback: T,
};
if (navigator.sendBeacon) {
l.transport = 'beacon'
};
e(t, l);
setTimeout(T, 1000)
}
} else {
if (r && r !== 'internal') {
l = {
hitType: 'event',
eventCategory: r,
eventAction: p || v,
eventLabel: k || t.title,
hitCallback: T,
};
e(t, l)
} else {
t.exit = 'type';
n(t)
}
};
if (r != 'external' && r != 'cross-hostname' && r != 'internal-as-outbound') {
setTimeout(T, 1000)
} else {
if (r == 'external') {
setTimeout(L, 1100)
} else if (r == 'cross-hostname') {
setTimeout(K, 1100)
} else {
setTimeout(O, 1100)
}
}
}
} else {
t.exit = 'internal';
n(t)
}
} else {
t.exit = 'notlink';
n(t)
}
};
var l = window.location.hash;
function p() {
if (monsterinsights_frontend.hash_tracking === 'true' && l != window.location.hash) {
l = window.location.hash;
__gaTracker('set', 'page', location.pathname + location.search + location.hash);
__gaTracker('send', 'pageview');
i('Hash change to: ' + location.pathname + location.search + location.hash)
} else {
i('Hash change to (untracked): ' + location.pathname + location.search + location.hash)
}
};
var r = window;
if (r.addEventListener) {
r.addEventListener('load', function() {
document.body.addEventListener('click', v, !1)
}, !1);
window.addEventListener('hashchange', p, !1)
} else {
if (r.attachEvent) {
r.attachEvent('onload', function() {
document.body.attachEvent('onclick', v)
});
window.attachEvent('onhashchange', p)
}
};
if (typeof String.prototype.endsWith !== 'function') {
String.prototype.endsWith = function(e) {
return this.indexOf(e, this.length - e.length) !== -1
}
};
if (typeof String.prototype.startsWith !== 'function') {
String.prototype.startsWith = function(e) {
return this.indexOf(e) === 0
}
};
if (typeof Array.prototype.lastIndexOf !== 'function') {
Array.prototype.lastIndexOf = function(e) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError()
};
var t, n, a = Object(this),
i = a.length >>> 0;
if (i === 0) {
return -1
};
t = i - 1;
if (arguments.length > 1) {
t = Number(arguments[1]);
if (t != t) {
t = 0
} else if (t != 0 && t != (1 / 0) && t != -(1 / 0)) {
t = (t > 0 || -1) * Math.floor(Math.abs(t))
}
};
for (n = t >= 0 ? Math.min(t, i - 1) : i - Math.abs(t); n >= 0; n--) {
if (n in a && a[n] === e) {
return n
}
};
return -1
}
}
},
MonsterInsightsObject = new MonsterInsights();
Beautifier.io produced a relatively readable version of the code, but when compared to the deobfuscated version generated by ChatGPT, it becomes clear that ChatGPT performed exceptionally well. The functions in ChatGPT's output matched the original functionality, and it went a step further by suggesting more intuitive variable and function names. This highlights ChatGPT's ability to not only decompile code but also enhance its clarity. Overall, ChatGPT's decompilation and analysis proved to be accurate and reliable. In the realm of human-crafted code deobfuscation, ChatGPT demonstrated outstanding accuracy and efficiency. AI for the win!
___ ___
( \/ )
( ┌─────┐ ) <-- AI Brain/Circuit
( │ AI │ )
( └─────┘ )
\ /\ /\ /
\/ V \/
( 0 1 0 ) <-- Streams of Binary
\ 1 0 /
\ /
.--------------------------------.
| MALICIOUS CODE ANALYZER |
|--------------------------------|
| if (suspicious) { |
| trace(); |
| quarantine(); |
| } else { |
| allow(); |
| } |
'--------------------------------'
||
|| (detected threat)
\/
(BUG) <--[X]--> (/!\ WARNING)
Conclusions
Some popular WordPress plugins may not be malicious in the sense of injecting malware or stealing credentials, but they are often engineered for extensive behavioral tracking. In environments where users are unaware or have not explicitly consented to such tracking, these plugins can be considered malicious due to their invasive surveillance nature, undisclosed data transmission, and potential interference with normal user navigation.
In government agency settings, where security protocols are typically very strict, the use of such plugins could pose a significant risk. Businesses that interact with government agencies should be cautious, as their websites may be flagged as malicious by security systems if these plugins are detected.
Moreover, ChatGPT's capabilities in decompilation and code analysis are highly reliable and efficient, making it a valuable tool for identifying potentially malicious code. IT professionals, even those who are not specialized in security, can leverage ChatGPT to detect and address malicious code early, enabling them to take appropriate preventive or corrective actions.
Previous Article
Jan 10, 2025
Snowy Day Robbery: Identifying the Thief
Solving this puzzle with AI: Who is the thief? The answer is that the thief is the one who is not telling the truth.
Next Article
Dec 09, 2024
What Is Your Thesis for Shorting?
This article explores the fundamental aspects of short selling in trading, emphasizing the importance of having a well-defined thesis before taking short positions.