OWASP for AEM (XSS, CSRF, injection)
Who this page is for
| Audience | Why it matters to you |
|---|---|
| All engineers | The application-layer defence |
XSS — the big one in a CMS
Authors (and worse, migrated/imported content) supply data your templates render. Defences in order:
| Defence | Mechanism |
|---|---|
| Context-aware output encoding | HTL contexts (reference) — the default does the right thing; every @ context='unsafe' is a finding unless security-reviewed |
| Rich text sanitisation | @ context='html' (AntiSamy-based filtering); RTE configured to a minimal tag/attr set |
| Don't build HTML in Java | Models return data; HTL renders. String-concatenated markup in a model bypasses everything |
| URL contexts | href/src through context='uri' — kills javascript: payloads from link fields |
| The XSS protection API | XSSAPI for the rare programmatic case |
Test with hostile content, not lorem: titles like <script>alert(1)</script> and javascript: URLs belong in your fixture content on dev (Lab 4 checkpoint does exactly this).
CSRF
State-changing POSTs need the CSRF token filter's token (granite.csrf clientlib adds it to XHRs automatically; custom fetch code must include the token header). Server side: keep state changes off GET (servlets) — a GET that mutates is un-defendable by design.
Injection
| Variant | AEM form | Defence |
|---|---|---|
| Query injection | User input concatenated into JCR-SQL2/XPath/QueryBuilder | Parameterise: QueryBuilder map values, JCR-SQL2 bind variables — never string-build queries from input |
| Path injection | Input used in resolver.getResource(userInput) | Validate/normalise; resolve relative to fixed roots; reject traversal |
| OS/LDAP etc. | Rare in AEM code | Same rules as anywhere |
The rest of the top ten, mapped
Broken access control → ACLs + service users; security misconfiguration → dispatcher + default credentials; vulnerable components → dependency scanning; SSRF → outbound calls only to registered endpoints (integration register), never to user-supplied URLs.