CSP Header Hacking for Apache2
Hello,
I write here to report my latest dirty hack. Story—
While enforcing our HTTP server configuration at work, we are slowly implementing CSP policies and one of them allow Web browsers to report violations to a given endpoint.
At this moment, we are only at the reporting step of the implementation because the first time we attempted to roll this header out, it ended up breaking our staging env.
So, it's recommended to set the Content-Security-Policy-Report-Only
header.
This header takes the report-uri
directive which allows the browser to push the violation findings to a given endpoint.
For this example:
Content-Security-Policy-Report-Only: default-src https: 'unsafe-inline'; report-uri /_/csp-report
However, the CSP header also has this directive but seems to be deprecated and soon to be replaced by the report-to
directive.
This new directive implies setting a new header named Report-To
which contains JSON data:
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://domain.test/_/csp-report"}]}
Then your CSP header looks like this:
Content-Security-Policy: default-src https: 'unsafe-inline'; report-to csp-endpoint
You finally end up with 3 headers:
Content-Security-Policy: default-src https: 'unsafe-inline'; report-to csp-endpoint
Content-Security-Policy-Report-Only: default-src https: 'unsafe-inline'; report-uri /_/csp-report
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://domain.test/_/csp-report"}]}
So far so good, except that the Report-To
header payload does not accept relative URL and this is an issue for our stack because we have a single Apache config for thousands of sub-domains and we don't want to build an extra app just to log the CSP violations.
How to make this URL absolute?
After a few hours of struggle, I found a hacky way to dynamically an absolute URL with Apache. Apache mod_headers does not allow to build strings so at first, I tried using the merge
action:
Header set Report-To "{\"group\":\"csp-endpoint\",\"max_age\":10886400,\"endpoints\":[{\"url\":\""
Header merge Report-To "%{REQUEST_SCHEME}e://%{HTTP_HOST}e"
Header merge Report-To "/_/csp-report\"}]}"
But then I realized that the merge
action is more of an "append" action that add values separated with a comma:
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":", http://domain.test, /_/csp-report"}]}
So, #fail.
Then I tried to find another way and I saw the edit
action which allows to edit a given header. I put this in my config:
Header set Report-To "{\"group\":\"csp-endpoint\",\"max_age\":10886400,\"endpoints\":[{\"url\":\"host/_/csp-report\"}]}"
Header edit Report-To "host" "%{REQUEST_SCHEME}e://%{HTTP_HOST}e"
Here, I replace host
by %{REQUEST_SCHEME}e://%{HTTP_HOST}e
. This returns:
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://domain.test/_/csp-report"}]}
Tada!!
I know this is not pretty at all but it does the job. Now, the CSP Report-To recommendation tells us that only HTTPS endpoints will be considered by the Web browsers so the next step would be to output only these headers when the connection is made over TLS.
But I keep this one for another day, another story.
GitHub issue about lack of relative URL support for Report-To
header: https://github.com/w3c/reporting/issues/147 and here's the doc on the Reporting API: https://developers.google.com/web/updates/2018/09/reportingapi.
Feel free to drop any comment, critic or just a thank you.