Clickjacking in Phoenix
How Clickjacking Manifests in Phoenix
Clickjacking in Phoenix applications typically occurs when a malicious site embeds your Phoenix app within an iframe, tricking users into interacting with your interface without their knowledge. This is particularly dangerous for Phoenix applications with admin panels, financial transactions, or any user actions that could be exploited.
Consider a Phoenix LiveView banking dashboard. An attacker creates a malicious page that embeds your legitimate Phoenix application in an invisible iframe. When users visit the attacker's page, they might see a seemingly harmless button (like "Click here for a prize"), but underneath is your Phoenix app's "Transfer Funds" button. The user thinks they're clicking one thing while actually performing a financial transaction in your app.
# A vulnerable Phoenix controller action
defmodule MyAppWeb.DashboardController do
use MyAppWeb, :controller
def admin_panel(conn, _params) do
render(conn, "admin_panel.html")
end
endThe vulnerability here is that the controller doesn't prevent the page from being embedded in an iframe. An attacker can create a page like:
<!DOCTYPE html>
<html>
<body>
<h2>Click here for a prize!</h2>
<button onclick="clickPrize()" style="position: absolute; top: 100px; left: 100px;">Click Me!</button>
<iframe src="https://your-phoenix-app.com/admin/panel" style="opacity: 0; position: absolute; top: 95px; left: 95px; width: 100px; height: 50px;"></iframe>
<script>
function clickPrize() {
document.querySelector('iframe').contentWindow.click();
}
</script>
</body>
</html>In Phoenix LiveView applications, clickjacking becomes even more dangerous because of real-time interactions. An attacker could intercept WebSocket connections or manipulate LiveView's client-side state, making the attack more sophisticated and harder to detect.
Phoenix-Specific Detection
Detecting clickjacking in Phoenix applications requires both manual inspection and automated scanning. middleBrick's black-box scanner can identify clickjacking vulnerabilities by attempting to embed your Phoenix endpoints in iframes and checking for proper X-Frame-Options headers.
For manual detection in your Phoenix application, examine your controller actions and LiveView mounts. Any endpoint that renders sensitive UI or performs actions should be protected. middleBrick scans for:
- Missing X-Frame-Options headers on sensitive endpoints
- Missing Content-Security-Policy frame-ancestors directives
- Endpoints that should be protected but lack clickjacking defenses
- LiveView endpoints vulnerable to iframe embedding
- Admin panels, dashboards, and transaction interfaces
You can test your Phoenix app's vulnerability by attempting to embed it in an iframe yourself:
# In a Phoenix controller test
defmodule MyAppWeb.DashboardControllerTest do
use MyAppWeb.ConnCase, async: true
test "dashboard is not vulnerable to clickjacking" do
conn = get(build_conn(), "/dashboard")
assert get_resp_header(conn, "x-frame-options") == ["DENY"]
end
endmiddleBrick's scanner goes further by actually attempting to render your Phoenix endpoints in iframes and checking if they can be successfully embedded, providing a practical assessment of your clickjacking defenses.
Phoenix-Specific Remediation
Phoenix provides several native mechanisms to prevent clickjacking. The most straightforward approach is using the :x_frame_options plug, which Phoenix includes by default in new applications but may be missing in older codebases or custom configurations.
# In your Phoenix endpoint configuration (lib/my_app_web/endpoint.ex)
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
# Add this plug to prevent clickjacking
plug :put_x_frame_options_header
# ... rest of your endpoint configuration
endThe :put_x_frame_options_header plug adds the X-Frame-Options: SAMEORIGIN header by default, which prevents your Phoenix app from being embedded in iframes on different domains. For maximum security, you can configure it to DENY:
plug :put_x_frame_options_header, value: "DENY"For more granular control, Phoenix allows you to configure clickjacking protection per controller:
defmodule MyAppWeb.AdminController do
use MyAppWeb, :controller
# Protect all actions in this controller
plug :put_x_frame_options_header, value: "DENY" when action in [:admin_panel, :settings]
def admin_panel(conn, _params) do
render(conn, "admin_panel.html")
end
def settings(conn, _params) do
render(conn, "settings.html")
end
endFor Phoenix LiveView applications, clickjacking protection works the same way since LiveView mounts are controller actions:
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
# Protect this LiveView from clickjacking
plug :put_x_frame_options_header, value: "DENY" when action == :mount
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, message: "Welcome to your dashboard")}
end
endFor comprehensive protection, combine X-Frame-Options with Content-Security-Policy headers:
plug :put_secure_browser_headers,
x_frame_options: "DENY",
content_security_policy: "frame-ancestors 'none'"
This dual-layer approach ensures protection even in browsers that may handle one header differently than another. middleBrick's scanner will verify that these headers are properly configured across all your Phoenix endpoints.
Frequently Asked Questions
Does Phoenix LiveView require different clickjacking protection than regular Phoenix controllers?
:put_x_frame_options_header plug to LiveView modules. The protection works identically whether you're serving static HTML or LiveView's real-time interfaces.Can I selectively allow clickjacking on some Phoenix pages but not others?
X-Frame-Options: SAMEORIGIN for pages that should only be embeddable on your own domain, or use DENY for sensitive pages. The :put_x_frame_options_header plug accepts a value option that you can configure per controller or even per action using conditional plugs.