logo

Database

Need

Restriction of file deletion to validated names inside the directory the endpoint manages

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.UploadsRouter do
  use Plug.Router

  plug :match
  plug :fetch_query_params
  plug :dispatch

  delete "/files" do...

The `/files` route below deletes `"uploads/" <> filename` with `File.rm!/1`, where `filename` is a query parameter. The caller decides which file the application deletes. `?filename=../config/runtime.exs` removes its configuration, and other values can delete other users' uploads, logs that record the attack or files the application needs to start. `File.rm_rf!/1` with request data is worse, because it deletes whole directory trees.

2. Steps

• Never pass request data directly to `File.rm/1`, `File.rm!/1`, `File.rm_rf/1` or `File.rmdir/1`.

• Accept only a single file name that matches a strict pattern, or look the file up by an identifier stored in the database.

• Resolve the name with `Path.safe_relative/2` against a fixed directory, and confirm with `File.lstat/1` that it is a regular file.

• Check that the authenticated user owns the file before deleting it.

• Run the application with a filesystem user that can only modify the directories it manages.

3. Secure code example

defmodule MyAppWeb.UploadsRouter do
  use Plug.Router

  @uploads_dir "uploads"
  @file_name ~r/\A[A-Za-z0-9_-][A-Za-z0-9._-]{0,99}\z/

  plug :match
  plug :fetch_query_params...

The corrected route accepts only a single file name made of letters, digits, `.`, `-` and `_`, which cannot start with a dot, so separators, `..` segments and absolute paths never pass. The name is then resolved with `Path.safe_relative/2` against the uploads directory, which also rejects symbolic links that point outside it. Before deleting, `File.lstat/1`, which does not follow symbolic links, must report a `:regular` file, so directories and links are never removed. `File.rm/1` is used instead of `File.rm!/1`, so a failure becomes a response instead of a crash. In a multi-user application, the directory should also be specific to the authenticated user, or the file should be looked up in the database together with its owner.