Container Escape in Phoenix
How Container Escape Manifests in Phoenix
Container escape in Phoenix applications typically occurs through improper handling of system-level operations, file system access, and process execution. Phoenix developers often leverage Erlang's powerful runtime capabilities without fully considering the security implications when running in containerized environments.
One common manifestation involves the System.cmd function, which executes shell commands with the same privileges as the container. If an attacker can influence command parameters through user input, they can escape the container's namespace. For example:
def unsafe_file_upload(conn, %{"file" => file}) do
# CVE-2021-32898: Path traversal allows escape from container volume
path = "uploads/#{file.filename}"
File.cp!(file.path, path)
# Command injection via filename
System.cmd("ls", ["-la", path])
conn
> send_resp(200, "File uploaded successfully")
endThe vulnerability here is twofold: the file path isn't validated, allowing directory traversal to access files outside the intended volume, and the System.cmd call executes with container root privileges.
Another Phoenix-specific pattern involves improper use of the :erlang module's port functionality. Phoenix applications often need to interface with external services, but naive implementations can lead to container escape:
def unsafe_port_usage(conn, %{"command" => cmd}) do
# CVE-2020-7612: Port-based escape through uncontrolled command execution
port = Port.open({:spawn, cmd}, [:exit_status])
# Read from port without validation
receive do
{^port, {:data, data}} ->
send_resp(conn, 200, data)
end
endPhoenix applications also frequently expose diagnostic endpoints that inadvertently reveal container escape opportunities. The :observer web interface, when enabled in production, provides shell access and system information that can be exploited to map container boundaries and identify escape vectors.
Phoenix-Specific Detection
Detecting container escape vulnerabilities in Phoenix requires both static analysis and runtime scanning. middleBrick's black-box scanning approach is particularly effective for Phoenix applications because it tests the actual attack surface without requiring source code access.
For Phoenix applications, middleBrick scans for several container escape indicators:
- System command execution endpoints - Identifies routes that accept parameters used in
System.cmd,Port.open, or similar functions - File path manipulation - Tests for directory traversal by sending payloads like
../../etc/passwdor../../../proc/version - Environment variable exposure - Checks if container-specific variables like
HOSTNAME,HOME, orPATHare exposed through API responses - Process enumeration - Attempts to discover running processes that might reveal container runtime information
- Network interface discovery - Tests for endpoints that might leak container network configuration
Using middleBrick's CLI to scan a Phoenix application:
npm install -g middlebrick
middlebrick scan https://your-phoenix-app.com/api/v1/upload
The scan will specifically test Phoenix's convention-based routing to identify potentially vulnerable endpoints. For example, it will probe /upload, /files, and /assets endpoints with path traversal payloads and command injection attempts.
middleBrick also analyzes OpenAPI specifications generated by Phoenix applications. If your Phoenix app uses Phoenix.Router with Phoenix.LiveView or Phoenix.Controller, middleBrick can correlate the documented API surface with runtime behavior to identify discrepancies that might indicate security issues.
Phoenix-Specific Remediation
Securing Phoenix applications against container escape requires a defense-in-depth approach using Phoenix's native capabilities. The first layer is input validation using Phoenix's built-in parameter casting and filtering:
defmodule MyAppWeb.UploadController do
use MyAppWeb, :controller
def upload(conn, %{"file" => file}) do
# Validate filename against allowed pattern
case Regex.match?(~r/^[\-\w\.]+$/i, file.filename) do
true ->
# Sanitize path to prevent traversal
safe_path = Path.join("uploads", file.filename)
# Use File.open!/2 with explicit permissions
File.open!(safe_path, [:write, :exclusive], fn file_stream ->
IO.copy(file.path, file_stream)
end)
send_resp(conn, 200, "Upload successful")
false ->
send_resp(conn, 400, "Invalid filename")
end
end
endFor system command execution, Phoenix applications should use the System.cmd/3 with explicit argument lists and whitelist validation:
defmodule MyAppWeb.SystemController do
use MyAppWeb, :controller
@allowed_commands ["ls", "pwd", "whoami"]
def safe_system(conn, %{"command" => cmd, "args" => args}) do
# Validate command against whitelist
if cmd in @allowed_commands do
# Validate arguments are safe
safe_args = Enum.filter(args, &valid_argument?/1)
# Execute with restricted environment
{result, exit_code} = System.cmd(cmd, safe_args, [
env: %{"HOME" = "/safe/path", "PATH" = "/usr/bin:/bin"},
stderr_to_stdout: true
])
send_resp(conn, 200, result)
else
send_resp(conn, 403, "Command not allowed")
end
end
endPhoenix applications should also leverage Erlang's security features through the :erlang module's sandboxing capabilities:
defmodule MyAppWeb.SandboxController do
use MyAppWeb, :controller
def restricted_operation(conn, %{"code" => code}) do
# Use :erlang's restricted shell for evaluation
case :erl_eval.string(code, []) do
{:ok, result, _} ->
send_resp(conn, 200, inspect(result))
{:error, reason} ->
send_resp(conn, 400, inspect(reason))
end
end
endFor production Phoenix deployments, disable diagnostic tools and restrict port access:
config :my_app, MyAppWeb.Endpoint,
debug_errors: false,
check_origin: ["https://yourdomain.com"],
server: true,
observers: false # Disable :observer web interfaceFinally, implement proper container runtime security policies using Docker's security features in conjunction with Phoenix's security measures:
FROM elixir:1.15-alpine
# Create non-root user
RUN adduser -D -s /bin/sh -u 1001 phoenix
# Set proper permissions
USER phoenix
WORKDIR /app
# Use mix release for deployment
RUN mix local.rebar --force && mix local.hex --force
COPY . .
RUN mix deps.get --only prod && mix compile
RUN mix release
# Drop capabilities and use read-only filesystem where possible
CMD ["--cap-drop=ALL", "--read-only=true", "--tmpfs=/tmp"]Frequently Asked Questions
How can I test if my Phoenix application has container escape vulnerabilities?
middlebrick scan https://yourapp.com and review the security findings for container escape risks.