Security Alert: Confirmed Malware
EditThisCookie
ID: fngmhnnpilhplaeedifhccceomclgfbg
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- https://editthiscookie.comView Profile
- Help
- Help Center
- Country
- GB
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- Yes
- Mailbox exists
- Yes
- Address
- Ridgdale Street London E32TW GB
- Website
- Visit
EditThisCookie is a cookie manager. You can add, delete, edit, search, protect and block cookies!
The first and best cookie manager for Google Chrome. โ Edit, delete, create cookies โ Make them read-only โ Block them (create filters) โ Export to JSON, Netscape cookie file (perfect for wget and curl), Perl::LPW โ Import from JSON โ Limit the maximum expiration date of any cookie โ Improve the performance, remove old ones โ Import a cookies.txt file
A blocking webRequest listener intercepts all HTTP responses across every URL (<all_urls>) and can strip Set-Cookie response headers before they reach the browser. The blocking + extraHeaders flags grant the ability to suppress cookie-setting for any domain, which could be abused to prevent security cookies (e.g., re-authentication tokens, CSRF tokens) from being applied. Variables headersToForward, headersChanged, and cH are also implicitly global (no var/let/const), creating unintended shared state.
chrome.webRequest.onHeadersReceived.addListener( function(details) { if (details.responseHeaders !== undefined) { headersToForward = []; headersChanged = false; for (var i = 0; i < details.responseHeaders.length; i++) { cH = details.responseHeaders[i]; if (cH.name.toUpperCase() == "SET-COOKIE") { fields = cH.value.split(';'); var cookieName = undefined; var cookieDomain = undefined; var cookieValue = undefined; if (fields.length > 0) { cookieName = fields[0].split('=')[0] cookieValue = fields[0].split('=')[1] } for (var x = 1; x < fields.length; x++) { if (fields[x].split('=')[0].trim() == "domain") { cookieDomain = fields[x].split('=')[1]; break; } } if (cookieName !== undefined && cookieDomain !== undefined && cookieValue !== undefined) { for (var x = 0; x < data.filters.length; x++) { var currentFilter = data.filters[x]; if (!filterMatchesCookie(currentFilter, cookieName, cookieDomain, cookieValue)) { headersToForward.push(cH); } else { headersChanged = true; } } } } } } if (headersChanged) { return { responseHeaders: headersToForward }; } else { return {}; } }, { urls: ["<all_urls>"] }, ["blocking", "responseHeaders", "extraHeaders"]);Google Analytics is loaded dynamically in the background page โ a context with no UI and no user-initiated pageviews โ and fires a 'Heartbeat' event every 4 minutes as a persistent beacon confirming the extension is active. This constitutes unauthorized background telemetry: users are continuously tracked without consent, and GA data includes the browser's IP address and UA string. Loading ga.js in the background also runs alongside chrome.cookies.onChanged, creating a proximity risk for correlating cookie events with GA sessions.
var _gaq = _gaq || [];_gaq.push(['_setAccount', 'UA-33054271-5']);_gaq.push(['_setSessionCookieTimeout', 0]);_gaq.push(['_trackPageview']);(function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = 'https://ssl.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();setInterval(function() { _gaq.push(['_trackEvent', 'Heartbeat', 'Heartbeat']);}, 4 * 60 * 1000);The GA analytics script is fetched and executed at runtime from an external server by dynamically injecting a script tag into the background page. The actual code executed is not contained within the extension package and could be changed server-side at any time. This is remote code loading in the background context โ the most privileged extension context โ where it has access to chrome.cookies and chrome.webRequest.
(function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = 'https://ssl.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();When a block rule is added, chrome.cookies.getAll({}) is called with an empty filter, which retrieves every cookie from every domain in every cookie store. This is a broad credential sweep โ every session token, auth cookie, and tracking cookie for every website the user has visited is enumerated. While the stated intent is to delete matching cookies, the wide enumeration creates an unnecessary attack surface and could be combined with the background GA context to leak cookie inventory data.
function addBlockRule(rule) { var dfilters = data.filters; for (var x = 0; x < dfilters.length; x++) { ... } dfilters[dfilters.length] = rule; data.filters = dfilters; filterURL = {}; ... chrome.cookies.getAll({}, function(cookieL) { for (var x = 0; x < cookieL.length; x++) { var cCookie = cookieL[x]; if (filterMatchesCookie(filterURL, cCookie.name, cCookie.domain, cCookie.value)) { var cUrl = (cCookie.secure) ? "https://" : "http://" + cCookie.domain + cCookie.path; deleteCookie(cUrl, cCookie.name, cCookie.storeId, cCookie) } } });}The select content handler uses eval() to parse a JSON string returned from a server-controlled loadurl endpoint. Any server response or attacker-influenced content reaching this code path is executed as arbitrary JavaScript. This is a code injection vulnerability that can be triggered if the loadurl is pointed at an attacker-controlled URL or if a MITM intercepts the response.
content: function(string, settings, original) { /* IE borks if we do not store select in separate variable. */ var select = jQuery('select', this); if (String == string.constructor) { eval("var json = " + string); for (var key in json) { if ('selected' == key) { continue; } var option = $('<option>').val(key).append(json[key]); select.append(option); }When a cookie field form is submitted via jEditable, it POSTs cookie data (including name, value, domain) to settings.target and writes the raw server response to self.innerHTML without sanitization. The target URL is caller-controlled, meaning cookie data could be exfiltrated to an arbitrary endpoint, and the unsanitized innerHTML write constitutes a stored XSS sink.
/* show the saving indicator */jQuery(self).html(settings.indicator);jQuery.post(settings.target, submitdata, function(str) { self.innerHTML = str; self.editing = false; callback.apply(self, [self.innerHTML, settings]);});A persistent message channel is established between the devtools panel and the background page on load, immediately requesting all cookies for the inspected tab. The devtools panel operates in a privileged context with access to chrome.devtools.inspectedWindow.tabId, enabling silent reads of cookies for any tab being inspected. This creates a persistent pipeline that routes all cookie data through the background page, which co-hosts the GA heartbeat beacon.
var backgroundPageConnection = chrome.runtime.connect({ name: "devtools-page"});backgroundPageConnection.onMessage.addListener(function(message) { if (message.action === "getall") { createTable(message); } else if (message.action === "refresh") { location.reload(true); }});function start() { var arguments = getUrlVars(); if (arguments.url !== undefined) { createList("https://google.com"); return; } var tabId = chrome.devtools.inspectedWindow.tabId; backgroundPageConnection.postMessage({ action: "getall", tabId: tabId });}A global chrome.cookies.onChanged listener fires on every cookie change across all browsing contexts, reading the cookie name, domain, and value for each event. While ostensibly for a read-only cookie protection feature, this gives the background page persistent visibility into every cookie modification event (including session and authentication cookies) for every site the user visits. Combined with the GA heartbeat loaded in the same background page, this creates a co-location risk where real-time cookie events run alongside an active external reporting channel.
chrome.cookies.onChanged.addListener(function(changeInfo) { var removed = changeInfo.removed; var cookie = changeInfo.cookie; var cause = changeInfo.cause; var name = cookie.name; var domain = cookie.domain; var value = cookie.value; if (cause === "expired" || cause === "evicted") return; for (var i = 0; i < data.readOnly.length; i++) { var currentRORule = data.readOnly[i]; if (compareCookies(cookie, currentRORule)) { if (removed) { chrome.cookies.get({ 'url': "http" + ((currentRORule.secure) ? "s" : "") + "://" + currentRORule.domain + currentRORule.path, 'name': currentRORule.name, 'storeId': currentRORule.storeId }, function(currentCookie) { if (compareCookies(currentCookie, currentRORule)) return; var newCookie = cookieForCreationFromFullCookie(currentRORule); chrome.cookies.set(newCookie); ++data.nCookiesProtected; }); } return; } }A heartbeat event fires every 4 minutes from the background page to Google Analytics, continuously reporting to a third-party server that the user's browser is open and the extension is active. This leaks behavioral/presence data without user awareness or consent, and the regular interval makes it function as a persistent tracking beacon rather than ordinary analytics.
setInterval(function() { _gaq.push(['_trackEvent', 'Heartbeat', 'Heartbeat']);}, 4 * 60 * 1000);The options page reads the 'page' parameter directly from the URL query string via getUrlVars() and uses it unsanitized to construct a redirect target via location.href. This is an open redirect vulnerability โ a malicious page could craft a URL pointing to the options page with an arbitrary 'page' parameter to redirect the user to an unintended location within the chrome-extension:// origin.
var panel = JSON.parse(localStorage.getItem("option_panel"));var arguments = getUrlVars();var element;if (panel === "null" || panel === null || panel === undefined) { element = "support";} else { element = panel;}if (arguments.page !== undefined) { element = arguments.page;}location.href = "/options_pages/" + element + ".html";The panel directly calls chrome.cookies.getAll() for any URL, retrieving all cookies associated with that origin including session tokens and auth cookies. Retrieved cookies are stored in module-level variables (tabURL, cookieList) that persist for the lifetime of the devtools panel and are accessible to any code sharing that scope.
function createList(url) { tabURL = url; chrome.cookies.getAll({ url: tabURL }, function(cks) { createTable({ url: tabURL, cks: cks }); });}The options page opens URLs to editthiscookie.com over plain HTTP (not HTTPS), exposing users to MITM attacks on those navigations. The id attribute from DOM elements is also directly interpolated into a navigation path, which could allow path traversal if the id value is not strictly constrained by the HTML.
function setPageCooserEvents() { $(".chooser").click(function() { var panel = $(this).attr("id"); if ($(this).hasClass("selected")) return; var id = $(this).attr("id"); if (id == "getting_started") { openExtPage("http://www.editthiscookie.com/start/"); return; } else if (id == "help") { openExtPage("http://www.editthiscookie.com/faq/"); return; } ls.set("option_panel", panel); location.href = "/options_pages/" + id + ".html"; });}The localizePage function uses jQuery's .html() to inject translated strings and i18n_argument attribute values into the DOM without sanitization. If any translated message string or attribute value contains HTML markup or JavaScript, it will be parsed and executed by the browser, creating a DOM-based XSS path within the extension's privileged pages.
function localizePage() { //translate a page into the users language $('[i18n]:not(.i18n-replaced)').each(function() { //Append text to element content $(this).html($(this).html() + translate($(this).attr('i18n'), $(this).attr('i18n_argument'))); $(this).addClass('i18n-replaced'); });User-supplied filter strings (domain, name, value) are passed directly to new RegExp() without sanitization, creating a ReDoS (Regular Expression Denial of Service) vulnerability. A pathological regex in a cookie value or filter rule could freeze the extension's background page. This function is also called inside the blocking webRequest listener, meaning a freeze there would stall all network responses.
function filterMatchesCookie(rule, name, domain, value) { var ruleDomainReg = new RegExp(rule.domain); var ruleNameReg = new RegExp(rule.name); var ruleValueReg = new RegExp(rule.value); if (rule.domain !== undefined && domain.match(ruleDomainReg) === null) { return false; } if (rule.name !== undefined && name.match(ruleNameReg) === null) { return false; } if (rule.value !== undefined && value.match(ruleValueReg) === null) { return false; } return true;}The cookie serialization functions (json, semicolonPairs, Netscape format) use an implicitly global cookie variable (no var/let/const), meaning the last-processed full cookie object โ including its value, httpOnly flag, and expirationDate โ is accessible from any script sharing the same background page scope after serialization completes.
"json": function(cookies, url) { var string = ""; string += "[\n"; for (var i = 0; i < cookies.length; i++) { cookie = cookies[i]; cookie.id = i + 1; string += JSON.stringify(cookie, null, 4); if (i < cookies.length - 1) string += ",\n"; } string += "\n]"; return string;},The help button in the popup links to an external HTTP (not HTTPS) URL opened from the privileged extension context. Plain HTTP navigations from extensions are susceptible to MITM interception and could be used to serve phishing content or malicious scripts to a user who trusts the extension.
<div id="submitDiv"> <i id="submitButton" class="fa fa-check" i18n_title="Alert_submitAll"></i> <a id="helpButton" i18n="help" href="http://www.editthiscookie.com/start/" target="_blank"> </a></div>The support page references getlocalization.com over plain HTTP. This third-party service appears defunct and the domain may have been re-registered by an unknown party. Links opened via chrome.tabs.create to plain HTTP URLs from a privileged extension context are vulnerable to MITM substitution.
<div class="linkify" lnk="http://www.getlocalization.com/editthiscookie/"> <div class="section-title" i18n="translation"></div> <i style="color: #8820BD; font-size: 4em; margin-top:5px; margin-right:5px;" class="fa fa-comment fa-fw"></i> <span i18n="translationIntro"></span></div>By severity
Versions scanned
Showing 4 of 6 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 1.6.2 | 4 |
| 1.6.1 | 17 |
| 1.6 | 14 |
| 1.5.0 | 3 |
Files with findings
14 distinct paths โ top paths by unique finding count:
- js/ga.js9
- js/background.js8
- js/utils.js3
- devtools/background-devtools.js2
- devtools/panel.js2
- js/cookie_helpers.js2
- js/options_main_page.js2
- lib/i18n_translator.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.