Content Security Policy (CSP)

Browser security

Content Security Policy (CSP)

Medium
1 h 30 min

What is CSP?

Content Security Policy (CSP) is a browser security control that websites can voluntarily adopt by sending a Content-Security-Policy header in their HTTP responses.

The basic principle of CSP is to enhance the security of a website by restricting what can happen on the site and from where resources such as scripts can be loaded.

CSP is essentially an implementation of the principle of least privilege on the client side, meaning that the website is only given the necessary privileges. This way, in the event of an attack, the attacker will have limited privileges to cause damage.

At the bottom of the page, there are ten labs where you can practice using CSP in different practical situations!

What does CSP protect against?

CSP is primarily an effective protection against XSS attacks, although for CSP to provide effective protection, it must be implemented correctly.

In addition, CSP can protect against other attacks similar to XSS, in which attempts are made to load styles (CSS) that do not belong to the web application.

Finally, with CSP it is possible to protect against some framing attacks (such as clickjacking) by limiting which (if any) external sites are allowed to frame the page (e.g. using the IFRAME element).

However, XSS protection is the main purpose of CSP.

What are XSS (Cross-Site Scripting) attacks then?

XSS refers to vulnerabilities that allow an attacker to make malicious changes to code executed by a browser in an application. This vulnerability is covered more extensively in Hakatemian's XSS course, but here is a simple example.

Example : XSS vulnerability

Assume that the website has such search functionality:

echo &quot;<p> Search results for: &quot; . $_GET(&#39;search&#39;) . &quot;</p> &quot;

The functionality is vulnerable to XSS attacks because the application builds HTML in an insecure manner. The URL parameter search is directly inserted into the HTML string, allowing the manipulation of the HTML structure.

An attacker can exploit the vulnerability by constructing the following types of links in the application:

https://www.example.com/?search= <script>alert('XSS')</script>

When the victim opens the link, the application insecurely constructs an HTML response and sends it to the victim's browser as follows:

<p>Search results for:<script>
    alert('XSS')
  </script></p>

Instead of the code alert('XSS'), the attacker would, of course, do something else such as stealing user information or performing unwanted actions in the application.

How does CSP protect against XSS vulnerabilities in practice?

Consider Hakatemian's website as a practical example. Load the page and simulate an attack by adding <script>alert("XSS")</script> to the HTTP response using the Burp Suite tool.

"XSS" alert window does not appear. Why? The answer can be found in the browser console. Hackademy's CSP prevents the attack because it does not allow inline scripts like this.

How does CSP work?

In practice, CSP works so that the web server returns in its HTTP response (in the Content-Security-Policy header) a policy that defines, for example, what kind of resources the site is allowed to load or execute.

HTTP-vastaus
Content-Security-Policy: script-src 'self'; img-src https://images.google.com/

The browser then interprets this policy whenever a supported CSP function occurs on your website, and either allows it or denies it. If denied, a red error message will appear in the browser console, as well as a report to your CSP report handler if you have enabled one with the report-uri directive (more on this later).

Directives

Politics consists of directives. In the example above, there are two directives: Script restriction (script-src) and image restriction (img-src).

The directives are separated by a semicolon (;) and include sources from which the specific resource can be downloaded.

Each resource that CSP can restrict has its own directive.

Directive behavior

If the directive is not set at all, the resource is not restricted in any way. This is the default state for all web pages.

If the directive is set, the resource is initially completely blocked and only the sources listed in the directive are allowed.

If the default directive (default-src, this section is regarded) is set, some directives will take their value from it if they are not set directly.

Certain sources listed in the directives override other sources. For example, the 'hash-' and 'nonce-' directives override the 'unsafe-inline' keyword, and the 'strict-dynamic' directive overrides both the 'unsafe-inline' keyword and URL-based allowances. We will come back to what these mean a little later.

Restricting execution of JavaScript code on a page

The most important task of CSP is undoubtedly to restrict the execution of JavaScript code on a website. The purpose is relatively simple: Allow all JavaScript that is intended to be executed on the site and block everything else.

Implementation can be either easy or it may require some tinkering and modifications to the protected application.

Understanding the different directives of CSP and their effects on each other is essential for creating an effective CSP.

Next, let's see how script restriction is typically done.

Start by either completely blocking scripts or allowing them only from your own domain

If your application downloads scripts from its own server (like most applications do), you can allow it as follows:

script-src: 'self'

If your application does not load scripts from your own server at all, you can block scripts completely by default.

script-src: 'none'

Note that quotes are important. In CSP, 'none' means "nothing" and none (without quotes) means a URL that starts with the word "none".

Allowing Script Based on URL Address

If you want to allow a specific script from the internet or perhaps all scripts from a specific address, you can do so with URL-based whitelisting. Note that 'wildcards' are not used in URL-based whitelisting.

Allowing a specific script from https://cdn.example.com address

script-src: https://cdn.tailwindcss.com/script.js

Allowing all scripts from https://cdn.example.com address

script-src: https://cdn.tailwindcss.com

Allowing scripts from any HTTPS address:

script-src: https:

Safely allowing inline scripts with CSP

If your application uses inline scripts and you cannot or do not want to get rid of them, CSP offers two completely insecure and two reasonably secure ways to allow them.

What are 'inline scripts' in CSP?

Inline scripts, in the context of CSP, practically refer to anything that an attacker could inject into HTML that would directly execute a script.

The most common example of an inline script is a script tag that contains JavaScript code:

<script>
  console.log("Tämä on inline-skripti")
</script>

CSP considers inline scripts as well as a wide range of other XSS vectors such as JavaScript event handlers (<img src=x onerror=alert("XSS")>) or JavaScript links (<a href="javascript:alert('XSS')">XSS</a>).

Insecure methods

Completely insecure methods are:

  • Be careful not to totally omit the script-src restriction.
  • Set the script-src value to 'unsafe-inline' which, as the name implies, allows insecure inline scripts and renders the entire CSP useless.

Important note about the 'unsafe-inline' keyword is that its usage is not actually always wrong but it is an important part of compatibility with older browsers, but we will come back to this later.

Safe methods

Safe ways are again:

  • Hashes (hash): Inline scripts are allowed, for example based on the SHA256 hash of the script's content.
  • Nonce (nonce): Inline script (or even external script) is allowed to be re-generated on each page load based on a randomly generated value provided as an attribute to script tags. This dynamic approach requires a specific kind of traditional shaped web application where HTML is built on the server side for each page load.
script-src 'unsafe-inline'

Enabling inline scripts with hash

Allowing inline script, for example with SHA256 hash, is fairly simple. Just return the following keyword in the script-src directive:

script-src: 'sha256-HERE_COMES_BASE64_OF_THE_SCRIPT_256_COMPRESSION'

What is the value of the hash?

But how do you get the Base64 value from the content of the script SHA256 hash? The easiest way is to let Google Chrome browser do the work for you. Follow these steps:

  • Set (in your local development environment) a CSP header that completely blocks inline scripts, for example script-src 'none' or script-src 'self'.
  • Make sure that the script you want to allow is on the website.
  • Download the page in Google Chrome browser. The script does not execute because CSP blocks it. Now open the console and find the CSP error message that blocked the script. The error message includes a precalculated Base64-SHA256 of the script that you can add script-src directive as it is.

The images are from an exercise found at the bottom of the page.

Important notice about seals and backward compatibility

When you enable script hashing, 'unsafe-inline' automatically loses power. In other words, the keyword 'sha256' overrides the keyword 'unsafe-inline'. This is important because very old browsers do not support the 'sha256' keyword, so it is recommended to also add 'unsafe-inline' if you use hashes to prevent the website from breaking on old browsers.

Allowing inline scripts with a random identifier (nonce)

Nonce-style CSP can be well suited for traditional web applications that build HTML with a template library for each HTTP response.

Nonce is not suitable for all applications

It is not suitable for applications with a modern static frontend (React, Svelte, Vue, Angular, etc.) because to function, nonce requires that the HTML code containing the script tag be rebuilt with every page load. This also excludes the use of caching (such as CDNs) for HTML files where script tags exist, even if they are otherwise static.

Nonce operation principle

Nonce functions work as follows:

  • When the page loads, generate a random identifier (nonce) on the server.
  • Update the CSP in the HTTP response so that it has script-src 'nonce-GENERATED_VALUE'
  • Update the HTML script tags built into the HTTP response so that they have the attribute nonce which value is generated nonce.

The intention is to recover something like this in the browser:

HTTP-vastaus
HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: script-src 'nonce-12345'

<html>
<body>
<script nonce="12345">
    alert("Hey!")
</script>
</body>
</html>

Nonce in practice

If you choose this option, avoid self-made implementations as they can easily contain errors. There are ready-made libraries available for popular software frameworks that can handle this for you. For example, Django has the django-csp library which can handle this automatically, and you can use the request.csp_nonce variable in your HTML templates.

<script nonce="{{request.csp_nonce}}">
        var hello="world";
</script>

Backward Compatibility

Like hashes, nonces also do not work with ancient browsers and replace the 'unsafe-inline' keyword in the same way. Therefore, 'unsafe-inline' can be used together with it to prevent the application from breaking on old browsers that do not support nonces.

Allowing external script with integrity

In addition to inline scripts, external scripts can also be allowed with tags. So an external script refers to something like this:

<script src="https://www.example.com/script.js"/>

The mechanism is identical to inline scripts. You simply calculate, for example, the SHA256 hash of the script's content, base64 encode the value, and set it in the script-src directive.

script-src: 'sha256-HERE_COMES_BASE64_OF_THE_SCRIPT_COMPRESSION'

However, there is one additional requirement before this works: SRI.

Additional requirement: SRI (Subresource Integrity)

SRI (Subresource Integrity) is a security feature that allows the browser to be instructed to load an external script only if its content has not changed. And the expected content is provided as a base64-encoded hash, similar to CSP (Content Security Policy) as well.

This does not yet work with the above CSP:

<script src="https://www.example.com/script.js"/>

Required:

<script integrity="sha256-TÄHÄN_TULEE_BASE64_SKRIPTIN_TIIVISTEESTÄ" src="https://www.example.com/script.js"/>

Enabling JavaScript event handlers ('unsafe-hashes' keyword)

Inline scripts can be allowed with hashes, but by default this does not apply to JavaScript event handlers.

So if you had a script like this:

<script>alert("Hello")</script>

...you could allow it with the following hash:

script-src 'sha256-h0KcPydp+ZJA83Cf3imWAKpq/DAOmTiLCuLkAJsLHhM='

But if you do have an event handler:

<button onclick='alert("Hello")'>Click</button>

...the same CSP wouldn't work anymore. Except that it can be made to work if desired. You can provide the keyword 'unsafe-hashes' in the script-src directive, after which inline scripts in event handlers can also be allowed with a hash.

script-src 'unsafe-hashes' 'sha256-h0KcPydp+ZJA83Cf3imWAKpq/DAOmTiLCuLkAJsLHhM='

Example : 'unsafe-hashes'

You have such a button and you cannot or do not want to refactor it into a form where inline event handler is not needed.

<button onclick="alert('Hello!')">

Instead of significantly reducing security by adding the '&#x27;unsafe-inline&#x27;' keyword to the 'script-src' directive, you can use the '&#x27;unsafe-hashes&#x27;' keyword with a hash to allow a specific function, as follows:

script-src 'unsafe-hashes' 'sha256-0KAUGUNSAE9SmnRfyY+2MhbftgtqjBwh4sNjtk8aJKM='

You can obtain the hash from the report-uri service.

Allowing eval

When 'unsafe-eval' is allowed in the script-src directive, it means that the website can execute JavaScript code that includes dynamic code execution such as eval(), Function(), setTimeout(), setInterval(), WebAssembly.compile(), etc.

If you don't use JavaScript libraries that require such functions, you can leave it out. However, it is common that it is needed, in which case you can allow it just fine.

No need to be alarmed by the ominous-sounding name. The 'unsafe-eval' keyword is not the most dangerous. It does allow for certain types of DOM-based XSS attacks, but these attacks are quite rare.

script-src 'unsafe-inline'

Allow loading additional scripts by a preloaded trusted script with the 'strict-dynamic' keyword

It is quite common nowadays for applications to load scripts that load additional scripts. For example, GTM (Google Tag Manager) is such a solution.

Before the 'strict-dynamic' keyword, CSPs of large applications in particular grew to absurd sizes and were difficult to maintain. Adding a script to a page, for example with GTM, should be easy, but it's not when you also have to update the application's CSP, which typically requires a complete redeployment of the application.

To solve this problem, 'strict-dynamic' was developed. Its impact is as follows:

  • Disable 'unsafe-inline' keyword is depreciated (it no longer has an effect).
  • Disable the keyword 'self'.
  • Disable URL-based permissions.
  • Allow scripts that are allowed by nonce or hash (must be one of these) to load additional scripts and have those loaded scripts load additional scripts and so on.

Important note regarding backward compatibility when using the 'strict-dynamic' keyword

Since 'strict-dynamic' is not supported in old browsers, it is important to build CSP in a way that the site does not break even in old browsers. This is done by adding the 'unsafe-inline' and https: keywords to the script-src directive.

Why?

  • 'unsafe-inline' because inline scripts are loaded even on browsers that do not support for example hashes or nonces.
  • https: that the additional scripts loaded by scripts would also be loaded on browsers that do not support the 'strict-dynamic' keyword.
Example : Enabling GTM with hash and 'strict-dynamic' keyword

Let's assume that we have GTM (Google Tag Manager) enabled and the intention is to allow all scripts loaded by GTM to execute on the page.

Let's start by adding the GTM script to the page.

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->

Let's open the page, check the error message that appeared in the Google Chrome browser console, and then allow the script in the script-src directive. Now the script executes, but it still cannot load additional scripts onto the page.

In order to do this, the 'strict-dynamic' keyword is also added to the script-src directive. Now a pre-trusted script is allowed to freely load additional scripts to the page.

GTM also needs the keyword 'unsafe-eval' to function.

Finally, add 'unsafe-inline' and https: to ensure the website continues to function properly even on very old browsers.

script-src 'sha256-abcdefg' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https:

Limiting where forms can be sent from the website

Even if the attacker cannot execute JavaScript code on the page, simply embedding HTML on the page by the attacker is always dangerous.

One technique that an attacker may use is to build an HTML form on a website that appears to indicate that the user's session has expired and asks the user to "log in again", sending the username and password to the attacker's page.

form-action

form-action directive restricts where forms can be sent from the site. For example, this could be used to restrict form submissions to the same origin and prevent the aforementioned attack.

form-action 'self'

Restricting which pages can frame the page

There are various browser-side attacks that exploit frameworks (such as iframe) to load the target site within the attacker's site.

One typical example is the so-called "clickjacking" attack where the target page is brought in as an invisible frame on the attacker's page and the browser user is tricked into clicking on things on the target page with their own username, even though the victim thinks they are clicking on the attacker's page.

Such framing used to be prevented using the X-Frame-Options header in the old days, but it is becoming outdated and currently CSP is used for this.

frame-ancestors

The frame-ancestors directive specifies from which sources the content of a web page can be embedded in <frame>, <iframe>, <object>, or <embed> elements.

For example, you can define the frame-ancestors directive in the HTTP response that comes from the website:

HTTP-vastaus
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com;

In this example, frame-ancestors allows embedding only within the site itself ('self') and also allows embedding content from the website https://trusted.example.com.

If the website does not need to be framed at all, as is often the case, use the value 'none'.

HTTP-vastaus
Content-Security-Policy: frame-ancestors 'none';

Limiting from where different resources on the website can be downloaded

The CSP has many directives that can be used to protect the page by restricting where resources can be loaded from.

Here are the directives and their purposes.

child-src

Defines valid sources for web workers and nested browsing contexts that are loaded using elements such as <frame> and <iframe>. You can also use the frame-src and worker-src directives separately if you want to restrict only one of them.

connect-src

Restrict URL addresses to which JavaScript code can establish HTTP connections. This makes it more difficult for an attacker to create an interactive XSS command channel, for example.

Unfortunately, data exfiltration (leaking data to the attacker) cannot be completely prevented with CSP because JavaScript code can redirect the browser window to the attacker's page and leak the data in URL parameters. To protect against this, a navigate-to directive was being developed but unfortunately, it was abandoned.

default-src

Acts as a fallback for other directives in this listing. There will soon be a separate paragraph about this.

font-src

Specify valid sources for fonts (which have been loaded for example using the @font-face CSS rule).

frame-src

Specifies valid sources for nested browsing contexts that are loaded using elements such as <frame> and <iframe>.

img-src

Defines valid sources for images and icons.

manifest-src

Specify valid sources for the application's manifest files.

media-src

Specifies valid sources for downloading media files using the <audio>, <video>, and <track> elements.

object-src

Specifying valid sources for <object> and <embed> elements. These legacy elements are very rarely needed nowadays, so it is advisable to almost always set the value of the object-src directive to 'none'.

script-src

Define valid sources for JavaScript and WebAssembly resources. This was covered comprehensively above.

script-src-elem

Specifies valid sources for JavaScript <script> elements. By default, uses the script-src value, so you don't need this directive unless you specifically want to give different rules for <script> elements than event handlers. Typically, you do not want.

script-src-attr

Specifies valid sources for JavaScript event handlers. Uses the default script-src value so you do not need this directive unless you specifically want to provide different rules for event handlers than for <script> tags. Typically, you do not want to.

style-src

Specifies valid sources for CSS style sheets. Like scripts, you can use hashes or nonces if desired.

If you want to allow styles with the style attribute (<button style="color: red">) inline, you also need 'unsafe-hashes'.

Example : style-src

Let's assume that you want to load styles from your own origin as well as from the address https://cdn.example.com, you could create the following policy.

style-src 'self' https://cdn.example.com/

style-src-elem

Sets the valid sources for <style> elements and <link> elements with a rel="stylesheet" attribute. Uses the default value of style-src.

style-src-attr

Define valid sources for individual DOM element internal styles. Uses style-src value as default.

worker-src

Set valid sources for Worker, SharedWorker, or ServiceWorker scripts.

Which directives inherit their values by default from the default-src directive?

The default-src directive of CSP can be used to set the default value for the following directives:

  • child-src
  • connect-src
  • font-src
  • frame-src
  • img-src
  • manifest-src
  • media-src
  • object-src
  • prefetch-src
  • script-src
  • script-src-elem
  • script-src-attr
  • style-src
  • style-src-elem
  • style-src-attr
  • worker-src

The directive is used, for example, as follows. This policy would set the value of all the mentioned directives to 'self', so that these resources would be allowed to load from its own server but not from elsewhere.

HTTP-vastaus
Content-Security-Policy: default-src 'self';

default-src directive value overrides the directive for the resource. For example, in this policy, none of the aforementioned resources should be loaded at all, but scripts can be loaded from the own server.

HTTP-vastaus
Content-Security-Policy: default-src 'none'; script-src 'self'

Limiting the Use of HTML base Element

Base element can sometimes be abused in an HTML injection attack, causing the application's links and forms to be redirected to a malicious website.

&quot;&gt;<base href="https://evil.example.com/">

Fortunately, CSP has a directive to prevent this attack vector.

base-uri

Limit the URLs that can be used in the <base> element of a document.

base-uri 'self'

How to set the HTTP response in the sandbox

The Iframe element supports the sandbox attribute, which can be used to define restrictions for the framed page. With the CSP sandbox directive, you can apply the same restrictions on your own page.

sandbox

Set a sandbox for the requested resource similarly to the sandbox attribute of the <iframe> element.

For example:

Content-Security-Policy: sandbox allow-scripts;

You can read about different parameters that you can restrict with sandbox here.

Get real-time notifications when CSP policy blocks something

It is very useful to receive information about when the CSP policy blocks something in your application, because ideally this should not happen at all. To address this need, there are reporting directives in CSP, report-uri and report-to. In practice, for now we are only interested in report-uri because report-to is its successor for the future, which has not yet been implemented in all browsers.

report-uri

Direct the user agent to report attempts to violate content security policy. These violation reports consist of JSON documents that are sent with an HTTP POST request to the specified URI.

Content-Security-Policy: report-uri https://example.com/csp-reports

Is the report-uri nonfunctional?

The CSP report-uri directive is currently being replaced by the report-to directive in the future, but report-to is not yet supported in all browsers, so use only the report-uri directive for now.

You can check the current state of support for the report-to directive here.

Content of CSP violation reports

Abnormality reports are sent as a POST request to the URL address defined in the report-uri directive. The Request Content-Type is application/csp-report, and the body contains the following JSON structure:


blocked-uri

The resource URI that was blocked from loading due to Content Security Policy. If the blocked URI is from a different origin than the document URI, the blocked URI will be truncated to include only the protocol, host, and port.

disposition

Either "enforce" or "report" depending on whether the Content-Security-Policy header or the Content-Security-Policy-Report-Only header is used.

document-uri

Document URI where the violation occurred.

effective-directive

Instructions that resulted in a violation.

original-policy

Broken CSP policy.

referrer

If the site has been accessed from another site when the violation occurred, the referrer field contains the URL address of that site

script-sample

The first 40 characters of the JavaScript code or style that caused the exception.

status-code

HTTP status code of the page that caused the deviation.

Handling CSP violation reports is convenient in the Sentry.io service

If you use Sentry (sentry.io), you can send CSP reports directly to Sentry where they will become their own issues. You can read more about it here. In practice, you will receive the value of the report-uri directive from Sentry.

Detecting threats with CSP violation reports in the Report URI service

If you do not already use Sentry but only want CSP reports, Report URI (https://report-uri.com/) is a good alternative. In addition to normal CSP reporting, the service can ingeniously use report-only mode (which we will examine later) to monitor suspicious behavior of the web application on the browser side.

Handling CSP violation reports manually

Exceptions can also be handled manually if you do not want to use Sentry, for example. Here is an example of code that handles CSP exception reports and prints the fields:

@app.route('/csp-report', methods=['POST'])
def handle_csp_report():
    if request.method == 'POST':
        # Get CSP anomaly report details on request
        csp_report_data = request.get_json()

        # Process CSP anomaly report data
        process_csp_report(csp_report_data)

        # Respond to the browser with 200 OK status
        return jsonify({'status': 'success'}), 200

def process_csp_report(csp_report_data):
    # Print the CSP deviation report fields
    print("Received CSP anomaly report:")
    print(f"Event type: {csp_report_data.get('type', '')}")
    print(f"File source: {csp_report_data.get('document-uri', '')}")
    print(f"Restrictions: {csp_report_data.get('blocked-uri', '')}")
    print(f"Timestamp: {csp_report_data.get('timestamp', '')}")
    print(f"Message: {csp_report_data.get('message', '')}")

How to configure a CSP report-only policy

CSP policy can also be provided in report-only mode where the policy does not yet block anything on the page but still logs to the console and reports according to the report-uri directive if something were to be blocked. This is recommended when you first enable CSP for a production application. Switch to blocking mode once you have ensured that it does not break the application for legitimate users.

You can do this by using the Content-Security-Policy header instead of the Content-Security-Policy-Report-Only header.

HTTP-Pyyntö
Content-Security-Policy-Report-Only: policy

You can define both Content-Security-Policy and Content-Security-Read-Only headers

If you are, for example, making significant changes to an existing policy, you can first deploy the changes in the Content-Security-Policy-Report-Only header and later update the Content-Security-Policy header if there are no issues. The headers are completely independent of each other, and you are free to set both in the same HTTP response.

Content Security Policy can also be defined with a meta-tag

If for some reason it is not possible to add headers to HTTP responses but you still want to protect your website with CSP policy, you can do it in HTML using the meta tag as follows.

<meta http-equiv="Content-Security-Policy" content="policy" />

This policy is slightly more limited than the one defined by the HTTP header, but you can still utilize almost all protections. However, you cannot use the report-uri, report-to, frame-ancestors, or sandbox directives.

Summary and further reading

CSP, despite its simple operating principle, is a surprisingly complex tool. However, it is incredibly important as an additional protection against XSS attacks, and all web applications should definitely implement it.

When building CSP policy, you may come across questions that were not covered in this article. In such cases, the best place to read is directly from the specification itself, which is quite readable and includes many good examples: https://w3c.github.io/webappsec-csp/

Next, do the exercises below to gain practical experience in building CSP!

Exercises

CSP Challenge - Level 1

Oh no! Someone has hacked the website and added a malicious script to the page. Can you fix the CSP to prevent the script from running and still keep the rest of the website operational? While you are at it, prevent the dog image from showing but keep the cat.

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 2

Oh no! Someone has hacked the website and added a malicious script to the page. Can you fix the CSP to prevent the script from running, still allowing the legitimate inline script to execute?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 3

Oh no! Someone has hacked the website and added three malicious scripts on the page, one inline, one from the Internet and one from the application's own domain! Can you fix the CSP to prevent the evil scripts from running and still keep the rest of the website operational? Most importantly let's pretend you don't really know what scripts the legit inline script will load (as is often the case with third-party scripts, think Google Tag Manager) so it should be able to load any other script.

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 4

Oh no! You sent the CSP header you built in level 3 to the QA team and they said it broke the website for old browsers completely! Can you fix the CSP by adding fallbacks for old browsers?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 5

Oh no! Someone has hacked the website and injected a fake login form to the page. It sends the user's credentials to the attacker's server. Can you fix the CSP to prevent the form from being submitted.

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 6

Oh no! Someone has hacked the website and injected a base tag to the page. It changes the base URL of the page, redirecting all links to the attacker's page. Can you fix the CSP to prevent the base tag from being applied? While you are at it, prevent frames from external sources and legacy HTML elements such as embed or object from being used on the page. You may have some problems even submitting the update CSP form because of the rogue base tag, perhaps you should use the browser's developer tools to remove the base tag from the page first?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 7

Oh no! Someone has hacked the website and injected a CSS file that makes everything look like a 90s website. Can you fix the CSP to prevent the CSS file from being loaded?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 8

Oh no! Someone has hacked the website and injected a library card skimming script to the page that sends your customer's library card information to the atacker's server. Can you fix the CSP to prevent the HTTP connection to the attacker's server from being established?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 9

Oh no! The security team has informed you that the website is vulnerable to clickjacking. Can you fix the CSP to prevent the website from being loaded in an iframe?

Exercises

Flag

Find the flag from the lab environment and enter it below.

CSP Challenge - Level 10

The final challenge. You get hired by a company to implement a strict, backwards compatible CSP on their website. Can you do it?

Exercises

Flag

Find the flag from the lab environment and enter it below.

hakatemia pro

Ready to become an ethical hacker?
Start today.

As a member of Hakatemia you get unlimited access to Hakatemia modules, exercises and tools, and you get access to the Hakatemia Discord channel where you can ask for help from both instructors and other Hakatemia members.