CSRF bypass in builderio/qwik
Reported on
Apr 19th 2023
Description
URL parsing with Qwik uses the new URL(a, b)
constructor. A little-known fact about this constructor is that if an attacker controls a
they have complete control of the finally resolved URL.
For example:
const url = new URL(attacker_value, "http://localhost")
By entering //test.com
, we can change the origin to resolve to http://test.com
.
Qwik URL Resolution
The getUrl
function is used in production and development node servers link.
export function getUrl(req: IncomingMessage) {
const origin = ORIGIN ?? getOrigin(req);
return new URL((req as any).originalUrl || req.url || '/', origin);
}
This follows the earlier vulnerable pattern, and the origin can be controlled.
Cloudflare, Vercel and other deployments which do not use this mechanism are not vulnerable.
Proof of Concept
Start any node Qwik Server.
For simplicity, I am using curl to simulate the request from the browser. In this case, the origin is "attacker.com". An actual attack would use the victim's web browser to send these requests and be unable to edit headers manually.
Observe curl -X POST http://localhost:5173/ -H "Origin: http://attacker.com"
causes a CSRF error
Observe curl -X POST http://localhost:5173//attacker.com/ -H "Origin: http://attacker.com"
does not cause a CSRF error!
The internal URL now points to http://attacker.com
; Qwik thinks it's attacker.com
!
Impact
Bypass of CSRF protections.
The result is the ability to modify user resources, provided they have an active session and are using SameSite: None
for session cookies.
Occurrences
http.ts L23
Bad new URL(a, b)
References
https://github.com/BuilderIO/qwik/releases/tag/v0.104.0