The id attribute is widely used to identify HTML elements. Unfortunately, cross-origin websites can determine whether a given id is set anywhere on a page by leveraging the focus event and URL fragments. If https://example.com/foo#bar is loaded, the browser attempts to scroll to the element with id="bar". This can be detected cross-origin by loading https://example.com/foo#bar in an iframe; if there is an element with id="bar", the focus event fires. The blur event can also be used for the same purpose 1.
Another way to measure the network timing of a request consists of abusing the socket pool of a browser 1. Browsers use sockets to communicate with servers. As the operating system and the hardware it runs on have limited resources, browsers have to impose a limit. Run demo (Chrome)Run demo (Firefox)
To exploit the existence of this limit, attackers can:
Check what the limit of the browser is, for example 256 global sockets for TCP and 6000 global sockets for UDP. 23
Block
\(255\)
sockets for a long period of time by performing \(255\)
requests to different hosts that simply hang the connection
# Serverfrom http.server import BaseHTTPRequestHandler, HTTPServer
import time
classhandler(BaseHTTPRequestHandler):
defdo_GET(self):
time.sleep(float(100000))
self.send_response(200)
self.send_header('Cache-Control', 'no-store')
self.end_headers()
with HTTPServer(('', 8000), handler) as server:
server.serve_forever()
Use the \(256^{th}\)
socket by performing a request to the target page.
Perform a \(257^{th}\)
request to another host. Since all the sockets are being used (in steps 2 and 3), this request must wait until the pool receives an available socket. This waiting period provides the attacker with the network timing of the \(256^{th}\)
socket, which belongs to the target page. This works because the \(255\)
sockets in step 2 are still blocked, so if the pool received an available socket, it was caused by the release of the socket in step 3. The time to release the \(256^{th}\)
socket is directly connected with the time taken to complete the request.
performance.clearResourceTimings();
await fetch(location.href, {cache:"no-store"});
awaitnewPromise(r => setTimeout(r, 1000));
let data = performance.getEntries().pop();
let type = (data.connectStart === data.startTime) ?'reused':'new';
console.log('Time spent: '+ data.duration +' on '+ type +' connection.');
With HTTP/1.1 (TCP) and HTTP/2 (TCP) and HTTP/3 (UDP) requests may reuse an existing connection for a host to improve performance. 45
HTTP/2 also has Connection Coalescing which allows different hostnames that are accessible from the same web server to reuse a connection. 6
This is currently keyed by if credentials are included in the request.
Since a reused connection is normally faster this could allow for detecting if a site has connected to a host excluding anything that’s been cached and leaking information about the cross-site request by abusing Stream prioritization and HPACK compression. 7
Connections may get closed if they are left idle or the sockets are exhausted, for example 256 connections for HTTP/2 or 30 seconds idle for HTTP/3. 28
This may also leak when the connection happened and the browser can have per connection limits and on how many connections are allowed per host, for example 6 connections per host. 2