HIGH container escapephoenixmutual tls

Container Escape in Phoenix with Mutual Tls

Container Escape in Phoenix with Mutual Tls — how this specific combination creates or exposes the vulnerability

A container escape in Phoenix involving mutual TLS (mTLS) typically arises when an API endpoint that enforces client certificate authentication is reachable from within a container and is also misconfigured in a way that allows an attacker to pivot from the compromised container to the host or to other services. Even though mTLS provides strong service-to-service authentication, the vulnerability is not in the TLS handshake itself but in how the application uses the authenticated identity and how the container networking is set up.

In a typical Phoenix deployment with mTLS, the endpoint verifies the client certificate, extracts attributes (such as the Common Name or SANs), and uses those attributes to make authorization decisions. If the application treats the mTLS identity as trusted input without additional validation, an attacker who can run code inside a container might be able to spoof a valid client certificate or manipulate the application into trusting a specially crafted certificate chain. Because the container shares the host network stack (or uses overlay networking), a compromised process can attempt connections to localhost or to other containers on the same network, leveraging the mTLS-secured endpoints to move laterally or escalate privileges.

Another angle is the use of unauthenticated or weakly protected endpoints in the same service. Even when most routes require mTLS, an overlooked route in Phoenix (perhaps a health check or a debug endpoint) might skip certificate verification or accept requests without a client certificate. An attacker who escapes the container can probe the local service and interact with these unprotected paths, bypassing the intended mTLS boundary. Additionally, if the Phoenix application resolves service names via DNS inside the cluster and trusts the hostname presented by a service, an attacker could perform service name spoofing or man-in-the-middle within the cluster to trigger insecure fallback behavior.

The interplay between container networking and mTLS configuration also matters. If the Phoenix application binds to 0.0.0.0 inside the container and the network policies are permissive, a compromised container can reach other containers that expose mTLS-protected APIs. The application might log certificate details for observability; if those logs are not properly protected, sensitive identity information can be exfiltrated, aiding further attacks. In short, a container escape in this context leverages weak service boundaries, overly permissive network policies, and insufficient validation of mTLS-derived attributes to move across security layers.

Mutual Tls-Specific Remediation in Phoenix — concrete code fixes

Remediation focuses on tightening how Phoenix consumes client certificates, validating identities, and limiting what the application trusts. Below are concrete code examples using the Plug.SSL and Phoenix.Endpoint configuration, followed by runtime identity validation patterns.

1. Enforce mTLS at the endpoint and require a client certificate

defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, http: [transport: :cowboy]

  # Enable mTLS by requiring a client certificate
  plug Plug.SSL,
    enable: true,
    cert: "priv/ssl/server.crt",
    key: "priv/ssl/server.key",
    cacertfile: "priv/ssl/ca_bundle.crt",
    verify: :verify_peer,
    fail_if_no_peer_cert: true

  # Other plugs and pipeline configuration...
  plug Plug.Parsers, parsers: [:json]
  plug Plug.MethodOverride
  plug Plug.Head
  plug MyAppWeb.Router
end

2. Validate certificate attributes and avoid trusting raw identity claims

After mTLS verification, explicitly validate the certificate fields and map them to application roles instead of using the certificate subject directly for authorization.

defmodule MyAppWeb.Auth do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case get_peer_cert_subject(conn) do
      {:ok, %{common_name: "svc-order", san: ["order.internal"]}} ->
        # Map to an internal role, do not trust the CN alone
        assign(conn, :service_role, :order_service)

      {:ok, %{common_name: "svc-payment", san: ["payment.internal"]}} ->
        assign(conn, :service_role, :payment_service)

      _ ->
        # Reject unknown or malformed identities
        halt_unauthorized(conn, "invalid client certificate")
    end
  end

  defp get_peer_cert_subject(conn) do
    case conn.status do
      # Plug.SSL sets peer_cert_subject when verify is active
      _ when conn.private[:ssl_peer_cert] ->
        cert = conn.private[:ssl_peer_cert]
        # Parse the certificate DN and extract CN/SANs using :public_key
        # This is a simplified representation; in practice use :public_key.pkix_decode_cert/2
        subject = :public_key.pkix_decode_cert(cert, :otp) |> extract_subject()
        {:ok, subject}

      _ ->
        {:error, :no_cert}
    end
  end

  defp halt_unauthorized(conn, message) do
    conn
    |> put_status(:forbidden)
    |> Phoenix.Controller.json_render(%{error: message})
    |> Plug.Conn.halt()
  end
end

3. Apply network policies and restrict localhost exposure

Ensure that the Phoenix service does not bind to 0.0.0.0 unnecessarily and that Kubernetes or container network policies restrict traffic to only required peers. Combine this with readiness probes that do not expose sensitive endpoints.

# In a Kubernetes NetworkPolicy (example)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: phoenix-mtls-policy
spec:
  podSelector:
    matchLabels:
      app: phoenix
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: trusted-services
    ports:
    - protocol: TCP
      port: 443

4. Use runtime checks and avoid host resolution pitfalls

When calling other services from Phoenix, validate the server hostname against the certificate rather than relying on DNS names alone. This prevents an attacker who can manipulate DNS within the cluster from redirecting traffic to a malicious service.

defmodule MyAppWeb.HttpClient do
  @trusted_ca File.read!("priv/ssl/ca_bundle.crt")

  def get_secure(url) do
    {host, port} = URI.parse(url) |> Map.take([:host, :port]) |> then(fn %{host: h, port: p} -> {h, p || 443} end)

    opts = [
      certfile: "priv/ssl/client.crt",
      keyfile: "priv/ssl/client.key",
      cacertfile: @trusted_ca,
      depth: 2,
      customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
    ]

    case :ssl.connect(String.to_charlist(host), port, opts, 5_000) do
      {:ok, socket} ->
        :ssl.connect(socket, opts)
        # perform request and close
        :ssl.close(socket)

      {:error, reason} ->
        # handle error securely
        {:error, reason}
    end
  end
end

Frequently Asked Questions

Does enabling mTLS in Phoenix prevent all container escape risks?
No. mTLS strengthens service authentication but does not prevent container escape if the application trusts untrusted input, exposes unprotected endpoints, or has permissive network policies. Defense-in-depth with network restrictions and strict identity validation is required.
Can middleBrick detect missing mTLS or weak certificate validation in Phoenix endpoints?
Yes. middleBrick scans API endpoints and can identify missing client certificate requirements, weak cipher suites, and improper certificate validation as part of its Authentication and Encryption checks, providing prioritized findings and remediation guidance.