Metastatic.Analysis.BusinessLogic.UnrestrictedFileUpload (Metastatic v0.10.4)

View Source

Detects unrestricted file upload vulnerabilities (CWE-434).

This analyzer identifies code patterns where file uploads are processed without proper validation of file type, size, or content.

Cross-Language Applicability

Unrestricted file upload is a universal web vulnerability:

  • Elixir/Phoenix: File.write!(path, upload.content) without validation
  • Python/Flask: file.save(path) without checking extension
  • JavaScript/Express: multer without file filter
  • Ruby/Rails: file.attach without validation
  • PHP: move_uploaded_file() without checks
  • Java/Spring: transferTo() without validation
  • C#/ASP.NET: SaveAs() without file type check

Problem

When file uploads lack validation:

  • Attackers can upload executable files (web shells)
  • Server can be compromised through uploaded malware
  • Denial of service through large file uploads
  • Storage exhaustion attacks

Detection Strategy

Detects patterns where:

  1. File save/write operations receive uploaded content
  2. No file type/extension validation is apparent
  3. No file size validation is apparent
  4. Original filename is used directly without sanitization

Examples

Bad (Elixir)

def upload(conn, %{"file" => upload}) do
  path = "/uploads/#{upload.filename}"
  File.write!(path, upload.path |> File.read!())
  json(conn, %{status: "uploaded"})
end

Good (Elixir)

@allowed_extensions ~w[.jpg .jpeg .png .gif]
@max_size 5_000_000

def upload(conn, %{"file" => upload}) do
  ext = Path.extname(upload.filename) |> String.downcase()
  size = File.stat!(upload.path).size

  cond do
    ext not in @allowed_extensions ->
      conn |> put_status(400) |> json(%{error: "Invalid file type"})

    size > @max_size ->
      conn |> put_status(400) |> json(%{error: "File too large"})

    true ->
      safe_name = "#{UUID.uuid4()}#{ext}"
      File.copy!(upload.path, "/uploads/#{safe_name}")
      json(conn, %{status: "uploaded", filename: safe_name})
  end
end