logo

Database

Uncontrolled external site redirect

Need

Restriction of redirect targets to paths within the application

Context

• Usage of Elixir 1.15+ for building application services

• Usage of Plug.Router for handling HTTP requests

Description

1. Non compliant code

defmodule MyAppWeb.LoginRouter do
  use Plug.Router

  plug :match
  plug :fetch_query_params
  plug :dispatch

  get "/login/done" do...

The `/login/done` route below sends the user to the value of the `next` query parameter with a `302` response and a `location` header, without checking it. An attacker can send a link such as `https://app.example.com/login/done?next=https://app-example.evil.com/login` by email or chat. The link points to the real domain, so it looks trustworthy, but the user ends up on a copy of the site controlled by the attacker, which asks for the password again or for payment details. Open redirects are also used to bypass URL allowlists in OAuth flows and to leak authorization codes.

2. Steps

• Never copy request data directly into the `location` header of a redirect or into `Phoenix.Controller.redirect(external:)`.

• Accept only relative paths that start with a single `/`, and reject values that start with `//` or `/\` or contain control characters.

• Map external destinations to identifiers that the server resolves against a fixed allowlist of URLs.

• Fall back to a default page when the requested target is not valid.

• Register exact redirect URIs for OAuth and single sign-on clients.

3. Secure code example

defmodule MyAppWeb.LoginRouter do
  use Plug.Router

  @default_target "/dashboard"

  plug :match
  plug :fetch_query_params
  plug :dispatch...

The corrected route passes the parameter through `safe_target/1` before redirecting. The function only accepts relative paths: the value must start with a single `/`. Values starting with `//` or `/\` are rejected because browsers treat them as protocol-relative URLs pointing to another host, and values with control characters are rejected because some browsers strip them and reinterpret the rest. Anything else, including absolute URLs and missing parameters, falls back to `/dashboard`. Because the target is always a path, the browser stays on the same origin. When the application must redirect to other domains, the parameter should carry an identifier that the server maps to a fixed URL instead of the URL itself.