logo

Database

Lack of data validation - Path Traversal

Need

Restriction of downloadable files to a fixed directory

Context

• Usage of Elixir 1.16+ for building application services

• Usage of Plug.Router for handling HTTP requests

Description

1. Non compliant code

defmodule MyAppWeb.ReportsRouter do
  use Plug.Router

  plug :match
  plug :fetch_query_params
  plug :dispatch

  get "/download" do...

The `/download` route below appends the `file` query parameter to `"priv/reports/"` and sends the result with `send_file/3`. The caller controls the path. A request to `/download?file=../../config/runtime.exs` climbs out of the reports directory with `..` segments and downloads the configuration of the application, and similar values reach `/etc/passwd`, private keys or the release files. `File.read/1` and `File.stream!/1` called with request data have the same problem.

2. Steps

• Never pass request data directly to `send_file/3`, `File.read/1`, `File.stream!/1` or `Path.expand/1`.

• Resolve user-supplied names with `Path.safe_relative/2` against a fixed base directory and reject `:error`.

• Confirm with `File.regular?/1` that the resolved path is a regular file before sending it.

• Prefer looking files up by an identifier stored in the database instead of by a path sent by the client.

• Run the application with a filesystem user that can only read the directories it serves.

3. Secure code example

defmodule MyAppWeb.ReportsRouter do
  use Plug.Router

  @reports_dir "priv/reports"

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

The corrected route resolves the parameter with `Path.safe_relative/2` before touching the filesystem. `Path.safe_relative/2` returns `:error` for absolute paths, for any path that uses `..` to leave the base directory and for symbolic links inside the base directory that point outside it. Only when it returns `{:ok, relative}` is the value joined to the reports directory. `File.regular?/1` then confirms that the target is a regular file, so directories and special files are never sent. Anything that fails these checks receives `404 Not Found`, which does not reveal whether the file exists.