Cover photo

Why is the Content-Security-Policy Header so Important?

It's just a low / informational finding right?

TL;DR: Defense-in-depth. There's a reason a lot of these protections exist in the first place. Please use them.

I know most places will auto-categorize a missing Content-Security-Policy (CSP) as an informational severity finding until cross-site scripting (XSS) is found, as there isn't technically a vulnerability (yet). I'm a huge fan of defense-in-depth, so while I support a default low rating, I know it'll never happen to the current specifications and rating schemes. Just keep in mind that if XSS ends up being present, the severity of it can be raised from informational to low (or medium with a good reason)!


A CSP can be the one final item in a series of protections that should exist within an application that prevents an XSS attack (and more) from being successful.

Handling User Data

In the big picture, when user data is handled it should be checked, and rechecked; Once on entry, once on exit. Sometimes these aren't "checks" even, if I'm referring to an action / functionality that only sometimes activates based on given parameters being met. Sometimes it's done automatically to all input and output, like HTML encoding. Other times it might be some specific type of sanitization, literally removing specific characters from a string. Then past that, it could be whitelisting, rejecting everything that doesn't meet an exact regex pattern.

When there are giant conglomerations of micro-services being used by a million different projects within an organization, it always pays to be careful by checking and double checking everything. An app that takes user data and passes it to multiple services that may not be owned by you introduces risk. You'll want to make sure all of the user data, API data, and third-party data is handled and re-displayed safely according to your expected standards / schemas.

But sometimes, even that can fail. Humans aren't perfect, and when building large apps with lots of tie-ins, things are sometimes missed (which is totally understandable). Maybe the team in charge of the file uploads forgot to include upload checks that stopped HTML files from being uploaded then displayed. Mistakes happen. That's why defense-in-depth is important.

Make Sure It's Configured Correctly

Nothing is worse than trying to be proactive about security, only to have something misconfigured so it ends up being next-to-useless anyways. Thoroughly understanding what each directive in the CSP does and why it's needed for that specific application is huge. Luckily there are tools that exist to ensure that a minimum level of security is being correctly enforced on an app, and that there aren't any insecure directives being used.

Using something like CSP Evaluator can help to see if the policy being looked at has an obvious "holes" in it:

And this can be used as a quick reference. Security Headers (by Probely) can also be used to scan live sites.

My Server Only Services JSON / Images, I Don't Need It... Right?"

TL;DR: Yes you need it. Every endpoint needs it. Just bake it into the middleware, and if specific pages need a different one to work correctly, change it on a case-by-case basis.

Every endpoint, no matter what, should have a CSP. It can be configured to accommodate whatever scripts are being used, so there's no excuse to not have it. No one knows how an endpoint's behavior might change in the future, so the best option is to always protect what you can control to the highest degree without impeding users or developers.

Your endpoint might only service JSON or images right now, but what will it serve in the future? What happens if extra features get added on to a micro-service? For example: An endpoint is a part of a separate micro-service and is dedicated to only handling profile pictures. It's supposed to only handle PNGs, and only serves PNGs. But the service its built on, can obviously do so much more (in most cases). If a file upload bypass enables a user to upload an arbitrary HTML file and the service serves it, stored XSS in now possible if CSP isn't present. It's still a valid vulnerability if a CSP is present, but it makes the impact less as the script shouldn't be able to execute. If we go too far down this road I'll start talking about hosting and serving user data, but that's a conversation for another time.

What Does a Working CSP Look Like?

When I'm running small tests to see how different scenarios are handled by certain headers / protections, I like to throw together a quick Go(lang) web server. In this instance, I'll use one to show the differences between a super basic CSP and no CSP defending against the simplest XSS test ever; an alert box.

Here's the Go program I'm using:

package main

import (
	"io"
	"net/http"
)

func main() {
	http.HandleFunc("/withCSP", withCSP)
	http.HandleFunc("/withoutCSP", withoutCSP)

	_ = http.ListenAndServe(":3333", nil)
}

func withCSP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'")

	io.WriteString(w, "<h1>With CSP</h1>")
	io.WriteString(w, "<script>alert()</script>")
}

func withoutCSP(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "<h1>Without CSP</h1>")
	io.WriteString(w, "<script>alert()</script>")
}

Start the program using:

go run main.go

Then navigate to the "/withoutCSP" page. Here's what's seen:

The arbitrary JavaScript executes EZPZ, no issues. Now navigate to the "/withCSP" page and open the browser's dev tool console.

You'll notice there was no alert box and in the console it showed that the inline script was blocked because it violated the "script-src" directive that was used.

Thanks for giving this a read 🙂. Some more helpful resources can be found below.


Further Reading

Loading...
highlight
Collect this post to permanently own it.
alp1n3.eth logo
Subscribe to alp1n3.eth and never miss a post.
#csp#content-security-policy#headers#strict#defense-in-depth