Implementing CSP with Nonce for Inline scripts in AEM: A Step-by-Step Guide
Is my AEM site CSP certified?
Prelude
One of Non-Functional Requirements (NFR) is to comply with CSP Version3
There are chrome extensions and tools to evaluate CSP compliance
And articles to understand this requirement https://support.securityscorecard.com/hc/en-us/articles/6223292850587-Content-security-policy-contains-broad-directives
Idea is simple, only 2 changes:
- The Response Header must include
Content-Security-Policy
with a valuescript-src 'nonce-<uniquevalue>
- ALL <script> tags loaded on page must include a
nonce
attribute with same value as header.
Response Header
Page Source
This way browser asserts if attribute value matches to header, and then accepts the clientlib. If any malicious code, attempts to run custom js, the nonce value mismatches and browser blocks.
How to Implement
3 changes:
- Update dispatcher Response header to include the `Content-Security-Policy`
- Create new Transformer Pipeline that ll include the same nonce into <script> tags
- Config to Register the pipeline into transformer factory
Dispatcher Change
From the site.vhost file, under root Directory tag, add below lines
<VirtualHost *:80>
TraceEnable off
<Directory />
# CSP Policy
Header set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; script-src 'nonce-unique' 'strict-dynamic' 'self';"
</Directory>
</VirtualHost>
Transformer Pipeline
Create a new LinkTransformer that checks <script> tag and adds same nonce-unique
into the script tags.
package com.app.corp.core.filter;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.rewriter.*;
import org.osgi.service.component.annotations.Component;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* This filter will add a nonce=unique attribute to ALL <script> tags. At
* dispatcher, response header adds a Content-Security-Policy script-src
* 'nonce-unique'. Now browser will allow only <script> tags bearing the
* correct nonce. This will prevent XSS injections.
*/
@Component(immediate = true, service = TransformerFactory.class, property = { "pipeline.type=nonce-injestor" })
public class NonceTransformer implements TransformerFactory {
@Override
public final Transformer createTransformer() {
return new ClientlibsTransformer();
}
public class ClientlibsTransformer extends DefaultTransformer {
@Override
public void init(ProcessingContext context, ProcessingComponentConfiguration config) {
// do nothing. housekeeping function
}
@Override
public void startElement(final String nsUri, final String localname, final String qName, final Attributes attrs)
throws SAXException {
final AttributesImpl newAttributes = new AttributesImpl(attrs);
if (StringUtils.equalsIgnoreCase(localname, "script")) {
newAttributes.addAttribute("", "nonce", "nonce", "CDATA", "unique");
}
super.startElement(nsUri, localname, qName, newAttributes);
}
}
}
Register the pipeline
Under /apps/myapp/config/rewriter/myapp, include the new transformer
Test the changes
From local, hit ctrl+U to view source and validate if all script tags are adding the nonce=unique
tag. If local dispatcher is running validate the page response headers include the same CSP with nonce tag.
After deploying to lower environment, test from 3rd party tools like cspvalidator.org and make sure the site is CSP compliant.
Congratulations! you made your security audit happy. Cheers!!