Clickjacking in Phoenix with Mutual Tls
Clickjacking in Phoenix with Mutual Tls — how this specific combination creates or exposes the vulnerability
Clickjacking relies on tricking a user into interacting with a hidden or disguised UI element inside an iframe. In a typical web app, a page can be embedded as an iframe on a malicious site, and user actions (clicks, form submissions) are captured by the attacker. Phoenix provides mechanisms to protect against this, such as setting HTTP response headers that prevent framing.
When Mutual TLS (mTLS) is used, the server authenticates the client using a client certificate. This strengthens channel authentication but does not directly prevent clickjacking. In a Phoenix deployment using mTLS, the client presents a certificate during the TLS handshake, which the server validates before the application layer processes the request. However, mTLS operates at the transport layer and does not affect the rendering or framing behavior of responses. Therefore, an endpoint protected by mTLS can still be embedded in an iframe unless additional anti-framing controls are in place.
The combination of clickjacking and mTLS can expose subtle risks if developers assume mTLS alone mitigates UI redressing. For example, an authenticated client with a valid certificate might access a sensitive endpoint (such as confirming a transaction) that is embedded in an attacker’s page. Since mTLS ensures the client is who they claim to be, the request may be processed with the client’s privileges, but the UI interaction is still coerced. Phoenix applications must therefore apply frame-denial headers regardless of mTLS usage to ensure the browser does not render the page in an iframe.
Consider an endpoint /api/confirm that performs a state-changing action. With mTLS, the client certificate identifies the user, but without proper headers, a malicious site can load this endpoint in an invisible iframe and trigger the action via a disguised button. The server sees a valid client certificate and authorized user, yet the context is hostile. This shows that mTLS secures identity and confidentiality in transit, but does not replace content-security policies and frame-ancestors directives that govern how pages are embedded.
To assess this risk in practice, scans can test whether endpoints served over mTLS are vulnerable to clickjacking by checking the presence and correctness of framing defenses. middleBrick runs checks such as Content Security Policy (CSP) and X-Frame-Options headers alongside other security controls, providing findings and remediation guidance independent of the transport-layer authentication method.
Mutual Tls-Specific Remediation in Phoenix — concrete code fixes
Remediation focuses on two layers: ensuring mTLS is correctly configured in Phoenix and adding anti-framing headers to prevent clickjacking. Below are concrete code examples for a Phoenix application using the Plug pipeline and the Cowboy adapter with mTLS.
1. Configure mTLS in your endpoint. In config/config.exs, set up SSL options including cert and key paths, and enforce client certificate verification:
config :my_app, MyAppWeb.Endpoint,
url: [host: "example.com", port: 443],
https: [
port: 443,
cipher_suite: :strong,
keyfile: "priv/cert/key.pem",
certfile: "priv/cert/cert.pem",
cacertfile: "priv/cert/ca_bundle.pem",
verify: :verify_peer,
fail_if_no_peer_cert: true
]
This tells Cowboy to require and validate client certificates. The verify: :verify_peer option ensures that only clients with a trusted certificate can complete the handshake.
2. Add anti-framing headers in a plug. Create a plug that sets X-Frame-Options and Content-Security-Policy headers to prevent embedding:
defmodule MyAppWeb.FramingPlug do
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
conn
|> put_resp_header("x-frame-options", "DENY")
|> put_resp_header("content-security-policy", "frame-ancestors 'none';")
end
end
Insert this plug early in the pipeline (e.g., in endpoint.ex) so it applies to all requests, including those authenticated via mTLS:
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
plug(MyAppWeb.FramingPlug)
plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json])
plug(PhoenixMiddleware)
# ... other plugs
end
3. For granular control, allow framing only from specific ancestors if needed. Adjust the CSP header accordingly:
put_resp_header(conn, "content-security-policy", "frame-ancestors 'self' https://trusted.example.com;")
This permits embedding only from your own domain and a trusted partner, reducing risk while supporting legitimate use cases such as embedded dashboards.
4. Validate and log client certificates at the application level if you need to make authorization decisions based on certificate fields. Extract the subject or serial from the peer certificate in a plug:
defmodule MyAppWeb.CertificatePlug do
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
case Plug.Conn.get_peer_certificate(conn) do
nil ->
# mTLS not presented or invalid; connection should have been rejected earlier
halt_unauthorized(conn)
cert_pem ->
# Parse PEM and extract details as needed
subject = extract_subject(cert_pem)
assign(conn, :client_subject, subject)
end
end
defp halt_unauthorized(conn) do
conn
|> Plug.Conn.send_resp(403, "Forbidden")
|> Plug.Conn.halt()
end
defp extract_subject(cert_pem) do
# Use :public_key.pkix_decode_cert/2 in production; simplified here
"CN=example"
end
end
Combine the framing plug and certificate plug in the pipeline to ensure both transport-layer authentication and UI-level protection. With these measures, Phoenix applications can safely use mTLS while remaining resilient to clickjacking attacks.