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.

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

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.

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.

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.

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 = '