I wrote a little while back about the AppSec Serenity Prayer and talked about things you can and can’t control in terms of your application security. The recent BrowseAloud incident is a perfect reason to talk about this again and provide some real-world examples of how CSP and SRI could have prevented/mitigated this attack.
What is BrowseAloud, What Happened?
Also, the script can be accessed (plaintext in transit) from http://www.browsealoud.com/plus/scripts/ba.js, which also means it could be tampered (Man-in-the-Middle attack) on the load from browsealoud.com.
What Could go Wrong?
But, I know … your job is already hard enough and your boss says your site needs to be 508 compliant, amongst other things. He found a drop-in solution for that … and you would like to still see your family this month. So, you will be hosting the file … won’t you!?!?!?
What You Can Do
First, Acknowledge Your Situation
Like in the serenity prayer, previously mentioned it’s important to know the difference between the things you can change and those you can’t. It’s important to be upfront and acknowledge that any data served by your application (where this script is included) can be accessed by this script. This includes username and password if you include this script on a login page. It can also include any information on a subsequent page. This can all be exfil’d if the script is changed to do so. Maybe you only serve publicly available static pages. In that case, it’s not as big of a deal. If you have already thought this through (or tried to in the discussion with your boss) … Congratulations, you just did some threat modeling!
Second, Use the Worst-named Flag/Attribute on the Web
Ensure your app/framework sets HttpOnly to true on its cookies, especially on those used for authentication/sessions. Most modern frameworks do this for you, but trust AND verify is the name of the game.
Next, Defend with SRI and CSP
So, now let’s see what SRI and CSP can do for us!Verify the Integrity of the script.
Use Sub Resource Integrity (SRI). This will ensure that the script you expect to load is the script you get (pending browser support of course). First, review the script to ensure nothing is hinky. And yes, ‘hinky’ is the technical term! Next, you need to hash the script. You can do this with a tool such as the SRI Hash Generator. You can also do it manually, like this.
You could also do it for sha256 and sha512 if you want. The result is your page would include something like this (from the SRI Hash Generator tool referenced above):
<script src="https://www.browsealoud.com/plus/scripts/ba.js" integrity="sha256-25QlqDofGxAfNkyL+wwkFrdZTNswCyRUTQYWrZDqnzY= sha384-cnR4/ppFBApTv56lLca18rTXF5kZ7+b6EHiH2ShWfQ0YuRaF5PpL5jr8jBA8IZ98 sha512-8nrsaaJZJxuG9m7IFvne8xnRRT4ZTak3hh6qHx/FcMDDKOej3En8SobZc1/Is0jfrdYN7f9jAk1rjef6Bbhm9A==" crossorigin="anonymous"></script>
Reviewing, hashing and implementing this integrity check ensure that the script you expect is the script that runs in your page. If the hash does not match, the script does not run. Also, this protects you whether you are loading over http or https since we are checking the digest of the file contents.
You can go one step further and use require-sri (more on that below) to force all scripts loaded to use SRI.Control Inbound Sources and Outbound Connections
Since we are doing what we can, one other thing you *can* (and should) do is implement a Content-Security-Policy. This is a response header that provides directives to the browser, including where scripts can be loaded from and where connections can be made to externally. So, let’s implement script-src, connect-src and some reporting. We’ll assume the domain you are running is https://important-data.com.
- script-src: your CSP would start with something like:
Content-Security-Policy: script-src https://important-data.com/scripts/*.js;
- connect-src: Now we expand the CSP some … it might look like
Content-Security-Policy: script-src https://important-data.com/scripts/*.js; connect-src https://important-data.com/api/*
report-uri: This tells the browser where to report attempted violations (since they occur in the browser and not on requests to your server). There are services you can use for this, but we’ll pretend you run your own at https://important-data.com/csp-reports. So, your CSP now looks like:
Content-Security-Policy: script-src https://important-data.com/scripts/*.js; connect-src https://important-data.com/api/*; report-uri https://important-data.com/csp-reports
And if you want to enforce SRI as we discussed above …
Content-Security-Policy: script-src https://important-data.com/scripts/*.js; connect-src https://important-data.com/api/*; report-uri https://important-data.com/csp-reports; require-sri-for script
NOTE: require-sri can also be implemented for style (e.g. require-sri-for script style)
Finally, Don’t Let Yourself Be Framed
If your page is embedded in a frame of some sort, these defenses can be undone because your response header may be overridden/ignored. Yes, users *should* notice that the URL is not actually your site, but don’t assume (You know what happens when you assume, right?) that will happen. So, let’s extend that CSP just a little more …
And for good measure, add another header, x-frame-options. That should look like this:
… or if you have to allow your site/page to be embedded/framed, then be as restrictive as you can with it (and go back to the first point of acknowledging your situation, since you may be open to UI Redressing, aka clickjacking).
Having Done All That …
You are likely much faster than the other person the lion is chasing (as the saying goes). You have just implemented solid defenses potentially self-inflicted XSS (or any XSS really). Having done all that, you can go home, see your family and sleep better … Knowing you have done what you can.