Skip to content
العربية

Open Redirect

How a trusted-looking link can bounce a victim to an attacker's site, and why checking the URL as text never holds.

Estimated time
20 min
Level
Intermediate
Your progress in this track0%

Before this lesson

An open redirect is a page that sends the browser to a URL you supply, with no check that the destination belongs to the site. On its own it does not steal data from the server. Its danger is trust: the link starts on a domain the victim knows, so they click it, and it quietly lands them somewhere else — a phishing page, or a URL that captures a token passed along in the redirect.

What is the vulnerability?

A login page wants to send you back where you came from after you sign in, so it takes a next parameter and redirects to it:

login.js
app.post('/login', (req, res) => {
  // Redirects to whatever the user put in next.
  res.redirect(req.body.next);
});

A link like https://login.ravens.lab/login?next=https://falcon.owl.lab/phish begins on the trusted domain but ends on the attacker’s. The victim reads only the first part.

Why does it happen?

The server treats a user-supplied value as a safe destination. Defenders then try to “fix” it by checking the URL as text — does it start with /, does it contain our domain — instead of parsing it. A URL has structure the text does not reveal: //host is absolute, and https://trusted@attacker/ has its real host after the @. Every text check has a spelling it misses.

How do you stop it?

Do not accept a full URL from the user. Prefer an id mapped to a known internal path, or a pure relative path. When you must compare a destination, parse it and compare the resulting host against an allowlist by exact match — never a substring of the raw string.

login.js — fixed
const ALLOWED = new Set(['login.ravens.lab', 'www.ravens.lab']);
app.post('/login', (req, res) => {
  const url = new URL(req.body.next, 'https://login.ravens.lab');
  res.redirect(ALLOWED.has(url.host) ? url.href : '/dashboard');
});

The three labs below walk this ladder: no check, a leading-slash check bypassed by //host, and a substring allowlist bypassed by the @ userinfo trick.

Labs

  • A redirect to anywhere

    EasyBrowser lab

    Make the redirect land on an external site you control, falcon.owl.lab.

  • Relative URLs only

    MediumBrowser lab

    Make the redirect land on the external site falcon.owl.lab despite the leading-slash check.

  • A substring allowlist

    HardBrowser lab

    Make the redirect land on falcon.owl.lab even though the URL contains the trusted domain.