Missing Frame Ancestors Protection

ID

html.missing_clickjacking_protection

Severity

high

Resource

Access Control

Language

Html

Tags

CWE:1021, NIST.SP.800-53, OWASP:2021:A4, PCI-DSS:6.5.4, PCI-DSS:6.5.6

Description

This rule identifies HTML documents that lack the Content Security Policy (CSP) frame-ancestors directive in their meta tags. The frame-ancestors directive controls which parent pages can embed the current page in frames, iframes, or objects, providing critical protection against clickjacking and unauthorized framing attacks.

Rationale

Clickjacking (also known as UI redressing) is a serious attack where malicious sites embed your application in an invisible or disguised iframe, tricking users into performing unintended actions. Without proper frame-ancestors protection, applications are vulnerable to several attack vectors:

Clickjacking Attacks: Attackers overlay transparent iframes containing your application over fake UI elements. Users think they’re clicking on benign content but are actually interacting with your application (e.g., transferring funds, changing passwords, authorizing actions).

Credentials Harvesting: Malicious sites can frame your login page and use sophisticated techniques to steal credentials or session tokens as users authenticate.

Social Engineering: Attackers can embed portions of your application within deceptive contexts, manipulating users into performing sensitive operations under false pretenses.

Context Confusion: Users may not realize they’re interacting with your application when it’s embedded in an attacker’s site, reducing their security vigilance and making phishing more effective.

Unauthorized Embedding: Even without malicious intent, unauthorized framing can damage your brand, create support issues, and violate terms of service when your application is embedded in unexpected contexts.

The frame-ancestors directive in CSP is the modern, flexible replacement for the legacy X-Frame-Options header, providing more granular control over which origins can frame your content.

Consider the following scenario:

<!DOCTYPE html>
<html>
<head>
  <title>Banking Application</title>
  <!-- No CSP frame-ancestors directive -->
</head>
<body>
  <h1>Transfer Funds</h1>
  <form action="/transfer" method="POST">
    <input type="text" name="recipient" />
    <input type="number" name="amount" />
    <button type="submit">Transfer</button>
  </form>
</body>
</html>

Attacker’s malicious site exploiting the missing protection:

<!DOCTYPE html>
<html>
<head>
  <title>Free iPhone Giveaway!</title>
  <style>
    iframe {
      position: absolute;
      width: 500px;
      height: 500px;
      opacity: 0.0001; /* Nearly invisible */
      z-index: 2;
    }
    .fake-button {
      position: absolute;
      width: 200px;
      height: 50px;
      z-index: 1;
      background: green;
      color: white;
      font-size: 24px;
    }
  </style>
</head>
<body>
  <h1>Click here to claim your FREE iPhone!</h1>
  <button class="fake-button">CLAIM NOW</button>

  <!-- Victim's banking app embedded invisibly -->
  <iframe src="https://victim-bank.com/transfer?recipient=attacker&amount=1000"></iframe>
</body>
</html>

When users click the fake button, they unknowingly submit the fund transfer in the invisible iframe.

Remediation

Add a Content Security Policy meta tag with the frame-ancestors directive to control which sites can embed your application. Use the most restrictive policy appropriate for your use case.

Vulnerable Pattern:

<!DOCTYPE html>
<html>
<head>
  <title>My Application</title>
  <!-- No CSP frame-ancestors protection -->
</head>
<body>
  <!-- Application content -->
</body>
</html>

Secure Patterns:

<!DOCTYPE html>
<html>
<head>
  <title>My Application</title>

  <!-- Recommended: Prevent all framing -->
  <meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'">
</head>
<body>
  <!-- Application content -->
</body>
</html>

Alternative configurations:

<!-- Allow only same-origin framing -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'self'">

<!-- Allow specific trusted domains -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'self' https://trusted-partner.com">

<!-- Allow multiple trusted domains -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'self' https://partner1.com https://partner2.com">

<!-- Combined with other CSP directives -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' 'unsafe-inline';
               frame-ancestors 'none'">

Policy Selection Guidelines:

  • Public-facing applications (login pages, payment forms, account settings): Use frame-ancestors 'none'

  • Internal applications: Use frame-ancestors 'self' or specific trusted domains

  • Embeddable widgets: Use frame-ancestors 'self' https://trusted-customer-domains.com

  • Never use: frame-ancestors * (allows any origin to frame your content)

Migration from X-Frame-Options:

The frame-ancestors directive supersedes the legacy X-Frame-Options header:

  • X-Frame-Options: DENYframe-ancestors 'none'

  • X-Frame-Options: SAMEORIGINframe-ancestors 'self'

  • X-Frame-Options: ALLOW-FROM https://example.comframe-ancestors https://example.com

For backward compatibility, set both during migration:

<meta http-equiv="X-Frame-Options" content="DENY">
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'">