Web Security Training

Navigating the web security landscape

Navigating the web security landscape

Article

Should you deploy your own CSP reporting endpoint?

Reporting is an extremely powerful feature of CSP, and setting it up is very straightforward. The real challenge lies in promptly acting upon useful reports, and effectively filtering useless reports. Can you setup such a system using the freely available report-uri.io service, or should you roll out your own CSP reporting endpoint? Read on to find an answer to this question.

Why is CSP reporting so important? Because it reports on a violation of your policy that occurs in your user’s browser, in their unique environment. There are numerous reasons that such a violation may occur, such as:

  • an actual attack being blocked by your policy
  • a misconfiguration of your policy
  • an interference with a browser extension, add-on or similar software
  • an intermediate party modifying network traffic

Without CSP’s reporting feature, you would never learn about an actual attack taking place, or about a configuration error that actually breaks your application.

This post is the third in the CSP series (Read the first and second post here. In an upcoming posts, we will talk about security issues with CSP (yes, you read that right!), so stay tuned (and subscribe here if you haven’t done already)!

What’s in a CSP report?

When CSP reporting is enabled, the browser will automatically submit reports of violations that occur in your application. You can enable CSP’s reporting feature by specifying the URL of your reporting endpoint with a report-uri directive in your policy. As an example, I have included the report-uri directive of this site below:

report-uri https://websec.report-uri.io/r/default/csp/enforce

Whenever a violation of any of the CSP directives occurs, the browser will automatically submit a report. Below is an illustration of a violation, and the corresponding network request being made by the browser.

The browser automatically submits a report when a violation is detected.

As you can see, reports are being submitted in a JSON format, and include plenty of information about the actual violation. The full JSON of the screenshot above has been formatted in the text box below, which improves readability quite a lot.

{"csp-report": {
	"document-uri": "https://www.websec.be/blog/digest-02/",
	"referrer": "https://www.websec.be/blog/",
	"violated-directive": "script-src 'self' https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js  https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com",
	"effective-directive": "script-src",
	"original-policy": "default-src 'self'; script-src 'self' https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js  https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/ https://platform.twitter.com https://*.twimg.com https://*.disquscdn.com; img-src 'self' data: https://www.google-analytics.com/ https://syndication.twitter.com https://*.twimg.com https://platform.twitter.com https://referrer.disqus.com https://*.disquscdn.com; frame-src https://platform.twitter.com https://syndication.twitter.com https://disqus.com/ https://player.vimeo.com https://www.youtube.com; font-src 'self' https://fonts.gstatic.com; connect-src https://links.services.disqus.com; report-uri https://websec.report-uri.io/r/default/csp/enforce",
	"blocked-uri": "eval",
	"line-number": 1,
	"column-number": 1609,
	"source-file": "https://a.disquscdn.com",
	"status-code": 0
}}

From the full report, it becomes quite clear that you get a lot of useful information to pinpoint the actual violation. I just want to highlight a few interesting things, without going over each of these properties in detail.

  • You get a violated directive, and and effective-directive. These might differ if the policy falls back to the default-src directive for a certain type of resource, as explained here.
  • You get the full CSP policy in the original-policy. This is useful to compare against the policy that is configured on your servers, to detect client-side tampering with your CSP policy. This might be caused by browser extensions, or by network-based traffic manipulation, for example by an ISP.
  • You get the URI of the resource that has been blocked. In this case, it simply shows eval, because the violation is caused by an attempted eval() function call from within JavaScript.
  • You get the actual source file that triggered the violation. As you can see in this case, the violation is caused by the Disqus component, which tries to perform an eval(). As explained by the Disqus team, this violation can safely be ignored, because the use of eval() is not critical.

All in all, a CSP report contains plenty of useful information, without revealing too much sensitive information. This is a deliberate design decision by the team behind CSP, to avoid malicious probing behavior.

Why is CSP reporting so tricky?

At first sight, collect and processing CSP reports does not seem that hard. However, once you actually start monitoring reports, you’ll quickly start noticing a lot of weirdness, which is exactly one of the reasons that dealing with CSP reports is tricky.

Example 1

As a first example, take a look at both reports below.

{
    "csp-report": {
        "document-uri": "https://www.websec.be/blog/digest-02/",
        "violated-directive": "script-src 'self' https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js  https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com",
        "effective-directive": "script-src",
        "original-policy": "default-src 'self'; script-src 'self' https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js  https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/ https://platform.twitter.com https://*.twimg.com https://*.disquscdn.com; img-src 'self' data: https://www.google-analytics.com/ https://syndication.twitter.com https://*.twimg.com https://platform.twitter.com https://referrer.disqus.com https://*.disquscdn.com; frame-src https://platform.twitter.com https://syndication.twitter.com https://disqus.com/ https://player.vimeo.com https://www.youtube.com; font-src 'self' https://fonts.gstatic.com; connect-src https://links.services.disqus.com; report-uri https://websec.report-uri.io/r/default/csp/enforce",
        "blocked-uri": "eval",
        "line-number": 1,
        "column-number": 1609,
        "source-file": "https://a.disquscdn.com",
        "status-code": 0
    }
}
{
    "csp-report": {
    	"document-uri": "https://www.websec.be/blog/digest-02/",
        "violated-directive": "script-src https://www.websec.be https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com",
        "original-policy": "default-src https://www.websec.be; script-src https://www.websec.be https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js https://www.google-analytics.com/ https://platform.twitter.com/ https://cdn.syndication.twimg.com https://syndication.twitter.com https://websec-be.disqus.com https://*.disquscdn.com; style-src https://www.websec.be 'unsafe-inline' https://fonts.googleapis.com/ https://platform.twitter.com https://*.twimg.com https://*.disquscdn.com; img-src https://www.websec.be data: https://www.google-analytics.com/ https://syndication.twitter.com https://*.twimg.com https://platform.twitter.com https://referrer.disqus.com https://*.disquscdn.com; frame-src https://platform.twitter.com https://syndication.twitter.com https://disqus.com/ https://player.vimeo.com https://www.youtube.com; font-src https://www.websec.be https://fonts.gstatic.com; connect-src https://links.services.disqus.com; report-uri https://websec.report-uri.io/r/default/csp/enforce",
        "blocked-uri": "self",
        "line-number": 1,
        "source-file": "https://a.disquscdn.com/next/embed/alfie.f51946af45e0b561c60f768335c9eb79.js",
        "script-sample": "call to eval() or related function blocked by CSP"
    }
}

Both reports are actually for the same eval() violation of the Disqus component we talked about earlier. The first report comes from a Chrome browser, while the second comes from a Firefox browser. Reliably tying both reports to the same issue is definitely tricky.

Example 2

The second example is illustrated in the screenshot below. Can you spot the problem?

A violation triggered by an image that is being loaded from a CDN that I never used before.

This report describes a violation where an image with a weird name is being loaded from a Cloudfront CDN. If you would scan the entire codebase of this website, you will not find a reference to this image, nor to the Cloudfront CDN. Good luck finding the cause of that issue …

The real culprit for this violation is the Buffer browser extension, which allows you to capture pages to share on social media. If you click the Buffer button in the browser, it tries to inject an overlay into the page, allowing you to write your social media post within the page your are sharing. However, CSP blocks this behavior, causing a violation and actually breaking Buffer’s functionality.

Fun fact: when I reported this to the Buffer team, their response was to add www.websec.be to a bypass list, so that the extension navigates the full page, instead of injecting an overlay. Obviously, this is not the cleanest solution. It would be better for the Buffer extension to detect the CSP policy and automatically navigate to the Buffer application instead of trying to inject the overlay.

And there is more …

I’ve illustrated two examples of potential weirdness in CSP reports, but there’s a lot more. Dropbox has enabled CSP reporting on their web application a while ago, and they have written up their experience with filtering through the reports. The least I can say is that the article is worth reading, because their findings are truly astonishing.

Finally, it is important to realise that there is no authentication involved in the reporting process. This essentially means that anyone can start sending crafted reports to your endpoint. So if you start seeing truly strange reports, think of this possibility before starting to panic!

So, Should you build your own reporting endpoint?

Now that we have the full context of what CSP reporting is really about, we come back to the original question posed in the title of this article. Should you build your own CSP reporting endpoint, or should you use the existing report-uri-io service? As always, the answer is not a straightforward yes or no, but a more nuanced “it depends”.

@reporturi enables you to collect #CSP reports right now, and it's free!


report-uri.io allows you to easily collect CSP reports, and allows you to get a quick overview using graphs and filters, as shown in the screenshots below, and well explained by its creator here.

report-uri-io offers you overview graphs, as well as raw access to the reports.

Using these features, you can easily spot configuration problems, and take the necessary action. I use report-uri.io myself for my websites, and I can easily say that it suffices for me.

However, if you run applications on a larger scale, you will quickly outgrow report-uri.io . If you’re dealing with a large userbase, you will get an enormous amount of reports reports. That means that you will need the ability to install a custom filtering mechanism, like Dropbox did. You also want to be able to ignore reports of known issues (like the Disqus example), or to group reports from different pages about the same issue. So yes, if you’re operating at this scale, deploying your own reporting endpoint makes sense.

A final piece of advice if you’re actually thinking of running your own reporting service: since a single violation may create a storm of reports in peak hours, it may be wise to separate the endpoint from your main application, just to avoid DDoSing yourself.

Conclusion

To give you a definitive answer to the question that triggered this post: report-uri-io probably meets all your CSP reporting needs. It’s only worth building your own CSP reporting endpoint if you’re running a large-scale application, and you’re planning on taking CSP reporting seriously.

How are you using CSP reporting? Have you seen some funky things happening? Share your experiences in the comments below!

Want to stay informed?

Subscribe to our mailing list and never miss an update or an event!

BLOGPOSTS

Comments & Discussion