meteor: browser policy
A few weeks ago we started receiving some unusual tickets from users who were unable to sign in. After seeing their console errors, it was clear that something was attempting to load a script from best-deals-products.com and inject it into our app. A little searching revealed the script was related to the Lenovo's infamous superfish scandal. I'm still unclear exactly why the login failed, however we were thrilled to have prevented the tracking code from executing in the first place. Apparently our site doubles as a malware detector. Nice.
We accomplished this simply by installing the browser-policy package and adding a few custom tweaks. For some reason,browser-policy
doesn't seem to get much attention in meteor security blog posts, however it's one of the easiest ways prevent a wide array of malicious attacks against your app.
Under the covers, browser-policy
implements a combination of X-Frame-Options headers and content security policy directives which allow you to do things like ward off XSS attacks and prevent your app from being framed.
Just by adding the package to your project, you'll get some reasonable default settings. I prefer to take it a step further and explicitly shut down as many things as I can while keeping the site functional. Below is a sample policy.js
that you can put anywhere in your /server
directory.
Example
BrowserPolicy.framing.disallow();
BrowserPolicy.content.disallowInlineScripts();
BrowserPolicy.content.disallowEval();
BrowserPolicy.content.allowInlineStyles();
BrowserPolicy.content.allowFontDataUrl();
var trusted = [
'*.google-analytics.com',
'*.mxpnl.com',
'*.zendesk.com'
];
_.each(trusted, function(origin) {
origin = "https://" + origin;
BrowserPolicy.content.allowOriginForAll(origin);
});
These settings do the following:
- prevent the site from being framed (recommended)
- prevent inline scripts (recommended)
- prevent
eval
(strongly recommended) - allow inline styles (we found this necessary for Google Fonts to work)
- allow fonts to be loaded via data URLs (we load our icon font this way)
- trust external scripts only from Google Analytics, Mixpanel, and Zendesk
- external scripts must be loaded over HTTPS
Now it's easy to see why the superfish injection failed - best-deals-products.com was not on the list of trusted sites and the browser refused to run the script.
Suggestions
Note that when you first add the package, your external scripts will be blocked. Forunately, your web console should provide some useful errors to determine which domains you need to whitelist. I'd also recommend trying to be as specific as possible when adding domains. For example if you have a CloudFront distribution, you should add an entry like 'SNkmhiE2b2fQiCxB3.cloudfront.net'
, rather than '*.cloudfront.net'
.
Nginx
In order for the above to work with Nginx, we had to increase the default proxy buffer sizes. The following settings worked for us, but YMMV:
# Increase the proxy buffers for meteor browser-policy.
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
These changes should go in the http
secion of your configuration file - typically located in /etc/nginx/nginx.conf
.
Issues
We've had browser-policy
running on Edthena since it's introduction in meteor 0.6. In that time, we encountered only two problems:
- One of the 3rd-party scripts we required was calling
eval
. We had to temporarily relax that restriction until the company modified its code. - Our typeface broke once when Google Fonts changed its domain.
Conclusion
The bottom line is that with only a few minutes of work you can remove a ton of potential attack vectors. I strongly recommend giving it a try. After which, you can tweet about how you hardened the security of your app while sipping your morning latte. Take that script kiddies!