Why does a misconfigured CSP provide false security?

csp-configuration-issues global

The issue

A Content Security Policy can be present and still restrict nothing. Header checks pass, the gap stops showing up in audits, and the browser behaves as if there were no policy at all, which is worse than an honest absence because nobody goes looking.

The usual causes are four: a policy with no fetch directives, several policies that combine in ways nobody intended, a deprecated header name browsers ignore, and http: sources in a policy for an HTTPS site.

Scope

These come from configuration: the server, the framework, the CDN or a proxy. Several layers adding their own headers is the usual source of duplicate policies, so the response the browser receives is the one to check.

How to check you have it

  • In probeo: probeo reads every policy in the Content-Security-Policy header and reports where they still allow broad access, for example csp:script-src-wildcard when scripts may come from *, https: or http:, or csp:missing-object-src when neither object-src nor default-src is set.
  • By hand: run curl -sI https://your-site.example/ | grep -i content-security-policy and count the policies. Look for a lone report-uri, for * or bare https: in script-src, and for any http:// source. Separately, look for X-WebKit-CSP or X-Content-Security-Policy headers.
  • With a tool: Google’s CSP Evaluator reads a policy and lists the directives that are missing or too broad.

The fix

Send one Content-Security-Policy header, with fetch directives that name specific HTTPS sources.

Pick the layer that owns the policy and remove it from the others. Replace any vendor-prefixed header with the standard name. Give default-src and script-src real source lists, not *, and change every http:// source to https://, or drop it if the resource isn’t served over HTTPS.

Done when: each response carries exactly one standard policy whose fetch directives name specific HTTPS sources, and the check above comes back clean.

Do not

  • Do not take a present header as proof that the policy does anything.
  • Do not let the CDN, the proxy and the application each add their own policy.
  • Do not keep X-WebKit-CSP or X-Content-Security-Policy.
  • Do not list http:// sources in a policy for an HTTPS site.

Policies that restrict nothing

A policy made only of report-uri, report-to or sandbox sets no fetch directives, so resource loading falls back to the browser’s defaults. A policy such as default-src * 'unsafe-inline' 'unsafe-eval' data: blob: is valid and allows nearly everything.

Several policies at once

When a response carries more than one policy, the browser enforces each of them, and a resource must pass all of them to load. If one policy allows scripts from cdn.example.com and another allows only api.example.com, scripts from either are blocked. Each policy looks right on its own, which is what makes this hard to trace.

Deprecated header names

X-WebKit-CSP and X-Content-Security-Policy were early, vendor-prefixed forms. Current browsers ignore them, so a site sending only one of these has no policy in effect.

HTTP sources on an HTTPS site

A source such as http://cdn.example.com in script-src permits a script that travels unencrypted, so anyone on the network path could alter it. Browsers block active mixed content on HTTPS pages regardless, so the result is usually a broken script rather than a working attack, but the policy still says the wrong thing.

FAQ

How do I detect if my CSP is meaningless?

Look for fetch directives: default-src, script-src, style-src, img-src, connect-src and the rest. If none are present, nothing is restricted. If they are present but hold * with 'unsafe-inline' and 'unsafe-eval', the restriction is nominal.

Why does having two CSP headers block more than either one alone?

Every policy is enforced, so a resource has to satisfy all of them. A source allowed by only one is blocked by the other.

Are deprecated CSP headers ever useful for backward compatibility?

In practice, no. The browsers that needed them have supported the standard header for many years. Remove them.

Can I use protocol-relative URLs in CSP directives instead of specifying HTTPS?

Not as //host, but a source without a scheme, such as cdn.example.com, already matches only HTTPS on an HTTPS page. Writing https://cdn.example.com says the same thing more plainly. A resource that only exists over HTTP can’t be loaded safely on an HTTPS page whatever the policy says.

How do I find which layer is adding extra CSP headers?

Compare the headers the application sends with what the browser receives, layer by layer: application, reverse proxy, CDN, load balancer. CDN security features, web application firewalls and framework middleware each commonly add their own.

Does a meta tag CSP interact with header CSP through intersection?

Yes. A <meta http-equiv="Content-Security-Policy"> policy is enforced alongside the header, so a resource must pass both. A meta policy also can’t set frame-ancestors, report-uri or sandbox, which is another reason to keep the policy in the header.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.