{"id":1821,"date":"2025-05-29T16:03:20","date_gmt":"2025-05-29T16:03:20","guid":{"rendered":"https:\/\/shreyapohekar.com\/blogs\/?p=1821"},"modified":"2025-05-30T09:31:48","modified_gmt":"2025-05-30T09:31:48","slug":"content-security-policy-csp-a-key-mitigation-for-xss-and-clickjacking","status":"publish","type":"post","link":"https:\/\/shreyapohekar.com\/blogs\/content-security-policy-csp-a-key-mitigation-for-xss-and-clickjacking\/","title":{"rendered":"Content Security Policy (CSP): A Key Mitigation for XSS and Clickjacking"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<p>Content Security Policy (CSP) is a powerful browser mechanism designed to prevent and mitigate common web vulnerabilities such as Cross-Site Scripting (XSS) and Clickjacking. CSP allows developers to specify which sources of content are trusted by the application. This guide will walk you through how CSP works, and how to use it to protect against Clickjacking with <code>frame-ancestors<\/code>. Real-world examples and practical tips are included to help you implement CSP securely.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Mitigating XSS with Content Security Policy<\/h2>\n\n\n\n<p>CSP headers control which scripts can be executed in the browser. By restricting script execution, you can block malicious scripts injected via XSS or SSTI.<\/p>\n\n\n\n<p>A basic CSP looks like following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'; script-src 'self';<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">What it means:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>default-src 'self'<\/code><\/strong><br>This sets the default policy for all content types (like images, fonts, frames, etc.) to only load from the <strong>same origin<\/strong> as the site itself. <code>'self'<\/code> refers to the same domain that served the page.<\/li>\n\n\n\n<li><strong><code>script-src 'self'<\/code><\/strong><br>This specifically restricts where JavaScript can be loaded from. In this case, scripts are only allowed from the same origin (no external JS libraries like CDN-hosted jQuery, for example).<\/li>\n<\/ul>\n\n\n\n<p>This CSP policy <strong>blocks any attempt to load or execute scripts from untrusted sources<\/strong>, making it highly effective against <strong>cross-site scripting (XSS)<\/strong> attacks.<\/p>\n\n\n\n<p>But this CSP is very strict and will not allow developers to execute JS that are needed to run the application. <\/p>\n\n\n\n<p>However, you  may also come across poorly implemented CSPs<\/p>\n\n\n\n<p>One of the <strong>core objectives of CSP<\/strong> is to <strong>prevent the execution of untrusted scripts<\/strong>, especially those injected by attackers via XSS. However, using the <code>'unsafe-inline'<\/code> directive in your CSP header essentially <strong>undoes this protection<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What <code>'unsafe-inline'<\/code> Does<\/h3>\n\n\n\n<p>When you include <code>'unsafe-inline'<\/code> in your <code>script-src<\/code> directive:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">httpCopyEdit<code>Content-Security-Policy: script-src 'self' 'unsafe-inline';\n<\/code><\/pre>\n\n\n\n<p>You are allowing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Inline <code>&lt;script><\/code> tags to execute freely.<\/li>\n\n\n\n<li>JavaScript inside HTML event handlers (<code>onclick<\/code>, <code>onmouseover<\/code>, etc.).<\/li>\n\n\n\n<li>Attacker-injected scripts (in many XSS cases) to run without restriction.<\/li>\n<\/ul>\n\n\n\n<p>Allowing inline scripts means that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Any script injected into the page through an XSS vulnerability <strong>can still run<\/strong>.<\/li>\n\n\n\n<li><strong>Your CSP no longer offers protection<\/strong> against script injection.<\/li>\n\n\n\n<li>The browser treats inline scripts as &#8220;trusted&#8221; regardless of where they come from.<\/li>\n<\/ul>\n\n\n\n<p>In almost all real-world applications, there will be a need to execute certain scripts to allow for its smooth functioning. To permit the execution of specific inline JavaScript while maintaining a strong Content Security Policy (CSP), it&#8217;s advisable to avoid the use of &#8216;unsafe-inline&#8217;. Instead, consider the following more secure methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Nonce<\/li>\n\n\n\n<li>Hashes<\/li>\n<\/ul>\n\n\n\n<p>In Part 2, we&#8217;ll deep-dive on how to configure CSP for XSS mitigation using nonces and hashes<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">CSP to Prevent Clickjacking<\/h2>\n\n\n\n<p>Clickjacking is an attack where users are tricked into clicking on something different from what they perceive, typically by overlaying a hidden iframe.<\/p>\n\n\n\n<p>You can prevent <strong>clickjacking<\/strong> using <strong>Content Security Policy (CSP)<\/strong> by setting the <code>frame-ancestors<\/code> directive. This directive specifies which origins are allowed to embed your site using <code>&lt;iframe&gt;<\/code>, <code>&lt;frame&gt;<\/code>, or <code>&lt;object&gt;<\/code> tags.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Use Cases of CSP for Clickjacking<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Prevent clickjacking<\/li>\n\n\n\n<li>Allow trusted partners to embed your site<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">1. Completely Block Framing<\/h3>\n\n\n\n<p>To prevent your site from being embedded in any iframe, set the <code>frame-ancestors<\/code> directive to <code>'none'<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: frame-ancestors 'none';<\/code><\/pre>\n\n\n\n<p><strong>Effect:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Prevents any website from embedding your site in an iframe.<\/li>\n\n\n\n<li>If an attacker tries to embed your site for clickjacking purposes, the browser will block the iframe.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Allow Framing from Trusted Sources Only<\/h3>\n\n\n\n<p>If you need to allow your site to be embedded only by specific trusted domains (e.g., your own domain), you can specify them like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: frame-ancestors 'self' https:\/\/trusted-domain.com;<\/code><\/pre>\n\n\n\n<p><strong>Effect:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>'self'<\/code> allows embedding only from the same origin.<\/li>\n\n\n\n<li><code>https:\/\/trusted-domain.com<\/code> allows embedding from the specified trusted site only.<\/li>\n\n\n\n<li>Any other origin trying to embed the site will be blocked by the browser.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Example Scenario<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li> <strong>Clickjacking Attack Attempt:<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Attacker creates a hidden iframe of your site and overlays invisible buttons.<\/li>\n\n\n\n<li>Victim unknowingly clicks the hidden button while interacting with what looks like a legitimate interface.<\/li>\n<\/ul>\n\n\n\n<p><strong>With CSP <\/strong><code><strong>frame-ancestors 'none'<\/strong><\/code><strong>:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Browser detects that the frame source is not allowed.<\/li>\n\n\n\n<li>The iframe is blocked, and the attack fails.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Why CSP <code>frame-ancestors<\/code> is Better Than <code>X-Frame-Options<\/code><\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>CSP <code>frame-ancestors<\/code><\/th><th>X-Frame-Options<\/th><\/tr><tr><td>More flexible (can specify multiple trusted origins)<\/td><td>Only allows <code>DENY<\/code>, <code>SAMEORIGIN<\/code>, or one trusted URL<\/td><\/tr><tr><td>Part of modern CSP standard<\/td><td>Legacy header, less flexible<\/td><\/tr><tr><td>Allows defining different framing policies per page<\/td><td>Same policy for the whole site<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>To illustrate the difference between how Content Security Policy (CSP) and X-Frame-Options handle frame embedding, consider the following scenarios:<\/p>\n\n\n\n<p><strong>1. X-Frame-Options Validation:<\/strong><\/p>\n\n\n\n<p>X-Frame-Options primarily validates the top-level frame, determining whether the content can be embedded based on the immediate parent frame&#8217;s origin.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>+-------------------+\n| Top-Level Frame   |  &lt;-- Checked by X-Frame-Options\n| (example.com)     |\n|                   |\n| +---------------+ |\n| | Subframe      | |\n| | (attacker.com)|  &lt;-- Not checked by X-Frame-Options\n| |               | |\n| | +-----------+ | |\n| | | Subframe  | | |\n| | | (victim.com)| |\n| | +-----------+ | |\n| +---------------+ |\n+-------------------+<\/code><\/pre>\n\n\n\n<p>In this structure, if <code>victim.com<\/code> sets <code>X-Frame-Options: SAMEORIGIN<\/code>, it prevents its content from being embedded directly by <code>attacker.com<\/code> at the top level. However, <code>attacker.com<\/code> can embed <code>victim.com<\/code> within a subframe, as X-Frame-Options doesn&#8217;t validate beyond the top-level frame.<\/p>\n\n\n\n<p><strong>2. CSP&#8217;s <\/strong><code><strong>frame-ancestors<\/strong><\/code><strong> Validation:<\/strong><\/p>\n\n\n\n<p>CSP&#8217;s <code>frame-ancestors<\/code> directive validates the entire frame hierarchy, ensuring that all ancestor frames meet the specified policy.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>+-------------------+\n| Top-Level Frame   |  &lt;-- Checked by CSP frame-ancestors\n| (example.com)     |\n|                   |\n| +---------------+ |\n| | Subframe      |  &lt;-- Checked by CSP frame-ancestors\n| | (attacker.com)| |\n| |               | |\n| | +-----------+ | |\n| | | Subframe  |  &lt;-- Checked by CSP frame-ancestors\n| | | (victim.com)| |\n| | +-----------+ | |\n| +---------------+ |\n+-------------------+<\/code><\/pre>\n\n\n\n<p>In this scenario, if <code>victim.com<\/code> sets <code>Content-Security-Policy: frame-ancestors 'self'<\/code>, it ensures that every ancestor frame in the hierarchy originates from <code>victim.com<\/code>. Consequently, <code>attacker.com<\/code> cannot embed <code>victim.com<\/code> at any level within its frame structure, as it doesn&#8217;t satisfy the <code>frame-ancestors<\/code> policy.<\/p>\n\n\n\n<p>This distinction highlights that while X-Frame-Options offers basic protection against framing by unauthorized top-level domains, CSP&#8217;s <code>frame-ancestors<\/code> provides a more robust security measure by validating the entire chain of embedding, thereby offering enhanced protection against clickjacking and other framing attacks.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Best Practice<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <code>frame-ancestors 'none'<\/code> unless you <strong>specifically need<\/strong> to allow trusted embedding.<\/li>\n\n\n\n<li>Remove <code>X-Frame-Options<\/code> if you&#8217;re already using CSP <code>frame-ancestors<\/code> (since CSP overrides it).<\/li>\n<\/ul>\n\n\n\n<p>That&#8217;s all for this blog post. See you in part 2 where I will cover a detailed explanation of how nonce and hashes in CSP works and implemented.<\/p>\n\n\n\n<p>Until then, happy defending!!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Content Security Policy (CSP) is a powerful browser mechanism designed to prevent and mitigate common web vulnerabilities such as Cross-Site Scripting (XSS) and Clickjacking. CSP allows developers to specify which sources of content are trusted by the application. This guide will walk you through how CSP works, and how to use it to protect against [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1828,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","ocean_post_oembed":"","ocean_post_self_hosted_media":"","ocean_post_video_embed":"","ocean_link_format":"","ocean_link_format_target":"self","ocean_quote_format":"","ocean_quote_format_link":"post","ocean_gallery_link_images":"on","ocean_gallery_id":[],"footnotes":""},"categories":[2,440,321,327,257],"tags":[445,447,446,441,8],"class_list":["post-1821","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-information-security","category-mitigations","category-owasp-top-10","category-source-code-review","category-web-application","tag-blue-teaming","tag-clickjacking","tag-csp","tag-defending","tag-xss","entry","has-media"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1821"}],"collection":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/comments?post=1821"}],"version-history":[{"count":7,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1821\/revisions"}],"predecessor-version":[{"id":1831,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1821\/revisions\/1831"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media\/1828"}],"wp:attachment":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media?parent=1821"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/categories?post=1821"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/tags?post=1821"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}