For many sites it is quite straight forward to switch from HTTP to HTTPS: Install a certificate, fix some URLs and set up some redirects. Others, like Stack Overflow have found it to be much more involved. At my job we had a good mix of users on HTTP and HTTPS as our clients have had the freedom to choose. I want to remove the option for weak security entirely. The first problem is that "fix some URLs" is about fixing a million URLs and second that almost all of those URLs are controlled by our clients. The consequence of having these URLs referencing content on HTTP would be that browsers would choke on mixed content when everything is loaded over HTTPS. The page loads over HTTPS but requests content over HTTP. The result could be lacking security indicators in the browser or blocked scripts and style sheets which quickly leads to a really bad user experience.
To tackle the problem I built scanners for content referenced on HTTP, which is something I will write about later. The scanners would cover the major use cases for our clients. But there were a few remaining item types where it just wasn't worth it building and maintaining a scanner. Another solution had to be found.
Content-Security-Policy-Report-Only
With the header Content-Security-Policy (CSP) header it is possible to restrict where the browser allows a site to load its content. The different directives can control where scripts, styles, images, media, etc. can be loaded from. If there is a violation it can send it to a specified report location. Another header called Content-Security-Policy-Report-Only (CSPRO) makes the browser send a report in the cases when CSP would block content, a good way of testing without breaking anything. The report functionality of CSP is mainly used for knowing when the CSP breaks something on your site. It can also be used to track XSS attempts and prevent embedding of other undesired content such as Coinhive for crypto mining on your site. I also realized that it could be leveraged for detecting what would become mixed content even though the site is still on HTTP.I configured CSPRO to allow scripts, images and media from "https:" as this would make it report everything on HTTP. I realized that I should also include 'self' (as it supports HTTPS) and also allow some bad practice in scripting as I'm not interested in those reports right now. That gave me this configuration:
img-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: media-src 'self' https:; font-src 'self' https:;
Report-uri.com
But where should I send the report? I had already gotten an account on report-uri.com so I decided to add configuration for reporting by adding this to CSPROreport-uri https://<my subdomain>.report-uri.com/r/d/csp/reportOnly
I had the header value configured in my web server and loaded a page. Two reports ended up in report-uri.com. I could also see the same reports going out in Fiddler, but in production I it would be the users' browsers sending the reports. The two reports I got were about an image in a stylesheet loaded as "background-image: url(data:image/png;base64, ...) and jQuery loaded from ajax.googleapis.com. Google's CDN does of course support HTTPS, the problem was that the reference URL is protocol relative and I tested the page on HTTP.
So I added "data:" and "http://ajax.googleapis.com" to the CSPRO and got
img-src 'self' data: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: https: http://ajax.googleapis.com; media-src 'self' https:; font-src 'self' https:; report-uri https://3cc60a1016cf59feef45b86272689d21.report-uri.com/r/d/csp/reportOnly
Now I'm ready to take it to production. I don't need hundreds of reports on each violation. As the reports come in I can update the header to exclude reported URLs I have already seen. I can also configure just one of the servers to send the header. With some user activity we can collect all the instances of HTTP references. I would still need to map it to the clients and filter out the cases of protocol relative URLs. But still it would require less effort than creating a scanner.
Some of you may now object that I'm missing HTTP requests to 'self'. I see a lot of cases where an absolute URL with http:// to the same server is used in cases where a relative URL would do. Yes, I do miss those but I may replace them with "http://<server name>". If you have other ideas, please let me know. Going forward we will need to work with clients to fix all the references before we redirect everyone to HTTPS. At that time we can set the CSP header with the Upgrade-Insecure-Requests directive and the browser (excluding IE and Edge which also has a less strict handling of mixed content) will change all HTTP requests to HTTPS. If a resource is not available on HTTPS when this is enabled, it will break.
But the whole point is that we don't want anything to break! We want to use reporting ahead of time to know what will break and fix it before it does.
No comments:
Post a Comment