Superhuman (formerly Grammarly) 路 Report #389076
`open-url` command allows opening unlimited number of tabs pointing to arbitrary URLs
How it was found
Summary
Attacker could trigger Grammarly extension's open-url command to open any number of tabs pointing to any origin (including internal, e.g. chrome://) and cause "infinite Chrome DoS" if attacker's page is pinned.
Description
Page could Init Grammarly popup editor [no user gesture, helper]
Events have isTrusted property, which allows to determinate, whether current event is trusted(initiated by user). Grammarly popup editor could be initiated by page.
As I understood: injected content script could successfully emit events to background page only if popup was initiated earlier. That means, attacker needs to initiate the popup somehow to communicate with background page through injected content script.
Not sure about the root cause of this behavior. Probably, because popup is created by background page origin, that's why background page becomes accessible after this.
send open-url to content script
Active page could send commands to Grammarly content script using window.postMessage.
Command structure:
window.postMessage({
grammarly: true,
action: 'open-url',
url: 'file:///etc/passwd'
}, "*")
open-url handling in content script
Grammarly content script "parses" commands using this snippet:
function Z(e) {
var t, n = e.action;
"edit" === n && U(e),
"close" === n && te(),
"initialized" === n && (t = "Premium" === e.userType ? "freemium-plus" : "freemium", O.documentElement.setAttribute("data-type", t), setTimeout(function () {
return T.el.style.background = "transparent"
}, 300)),
"socket" === n && p.emitBackground(b.MessageTypes.client, e),
"setSettings" === n && N(e.data),
"tracking" === n && e.method && g.call(e.method, e.param, e.props),
"popup-editor-fix" === n && P(),
"open-url" === n && (!0 === e.upgradeHookUrl && (y.gnar.getPremiumButtonClick(e.placement || "popupEditor"), g.felog.userUpgradeClick(e.placement || "popupEditor")), p.emitBackground("open-url", e.url))
}
There are multiple problems in this code, however, this report only about
open-url.
As you see, open-url commands are sent to background page.
open-url handling on background page
open-url command is handled by this snippet:
e.prototype.open = function (e, t) {
return o.SafePromise.create(function (n, o) {
chrome.tabs.create({
url: e,
active: t
}, function (e) {
r.handleChromeError(function () {
return n(e)
}, o)
})
})
}
More details in "Impact"
Step by Step
- Page sends a
open-urlcommand to content script [without user gesture, with attacker's<url>property] - Content script sends
open-urlcommand to background page - Background page opens new tab using
chrome.tabson<url>
Browsers Verified In:
Chrome 70.0.3508.0 Canary Chrome 68.0.3440.75 Stable
Steps To Reproduce:
file://
- open
file-url.html(no gesture required) - Extension opens two tabs on
file:///etc/passwd
chrome://version
- open
chrome-url.html(no gesture required) - Extension opens two tabs on
chrome://version
PoCs are reliable, however, if it doesn't works for you, try to adjust timers in script.
Supporting Material/References:
screencast for chrome://version and file:///etc/passwd attached.
Screencast for infinite DoS not attached, but I've attached PoC for this case, so that you can try 馃槇
Note: Works against logged in users. Probably, could be modified to work against unlogged in users.
Impact
Infinite number of new tabs
As you see, background page uses chrome.tabs under the hood. As opposite to window.open, chrome.tabs doesn't have limit on number of new tabs to open (only 1 tab is allowed for window.open without user's gestures).
That means page could open as many tabs as it wants using open-url command.
IMPACT: this behavior could be abused by malicious advertisement websites.
Open pages on restricted origins
Grammarly extension has access to <all_urls>. That means it's possible to open URLs from restricted origins (file://, chrome://, chrome-devtools://, chrome-extension://, etc).
IMPACT:
This behavior is extremely helpful when exploiting Chrome vulns or other 3rd-party web extensions (e.g. trigger vuln in chrome-devtools:// like this)
NOTE: I didn't test this on Win machine. AFAIK, it was possible to trigger executables by navigating to local
.lnk/.urlfiles.
"Infinite Chrome DoS" [requires attacker's page being pinned]
Navigation to chrome://quit closes browser. However, if tab is pinned, Chrome reopens this tab on start. As of this vulnerability doesn't require user interaction, that could potentially lead to Chrome DoS.
(browser opens -> pinned page loads -> browser closes -> browser opens -> pinned page loads -> ...)
IMPACT: I guess that most users wouldn't find a smarter solution than uninstalling extensions 馃槈or reinstalling the browser.