August 14, 2026
From a Single WAF Bypass to 58,000+ Exposed Media Objects
A bug bounty case study in chaining overlooked misconfigurations into a full attack surface

By Tarek ElDemerdash
7 min read
During an authorized bug bounty engagement against the public-facing marketing infrastructure of a multinational fintech platform, I discovered a chain of vulnerabilities that began with a seemingly simple WAF path-matching issue and ultimately exposed more than 58,000 media objects across 12 regional locales, multiple cloud-storage backends, and a broad cross-origin attack surface.
The engagement reinforced an important lesson in application security: the initial vulnerability is not always the most important part of the finding. The real impact often appears when you continue mapping what that vulnerability makes reachable.
No identifying URLs, hostnames, organization names, or confidential program information are included in this write-up.
— -
## The Target
The target was a WordPress Multisite deployment behind Cloudflare WAF. The platform served localized marketing content through multiple regional paths.
The WAF had rules intended to prevent direct access to the WordPress REST API. A request to the canonical path:
GET /wp-json/wp/v2/
→ 403 Forbidden
GET /wp-json/wp/v2/
→ 403 Forbidden
At first glance, the REST API appeared to be adequately protected at the edge.
However, the application architecture provided an important clue: the WordPress installation used locale-specific paths. That made the URL structure itself worth testing.
— -
## 1. The WAF Bypass
Instead of requesting the canonical REST API path, I tested the equivalent route under a locale prefix:
GET /{locale}/wp-json/wp/v2/
→ 200 OK — REST API reachable
GET /{locale}/wp-json/wp/v2/
→ 200 OK — REST API reachable
Side by side:
/wp-json/wp/v2/ → 403 (WAF block)
/{locale}/wp-json/wp/v2/ → 200 (REST API reachable)
/wp-json/wp/v2/ → 403 (WAF block)
/{locale}/wp-json/wp/v2/ → 200 (REST API reachable)
The locale prefix did not represent a different API. It represented another path through which the same application's REST functionality was exposed. The bypass worked across the regional locales I tested.
This is consistent with a broader testing principle: edge controls and application routing should be evaluated together rather than assuming that blocking one canonical path protects every route that ultimately reaches the same backend functionality. OWASP similarly recommends mapping application architecture and testing authorization/control bypasses rather than relying solely on perimeter behavior.
Lesson: When testing a path-based WAF, don't stop after confirming that the obvious path is blocked. Test the application's routing model:
- Locale prefixes /{locale}/wp-json/…
- Tenant prefixes /{tenant}/api/…
- API versions /v2/wp-json/…
- Trailing slashes /wp-json/wp/v2//
- Case variations /WP-JSON/WP/V2/
- Double encoding /%77p-json/wp/v2/
- Locale prefixes /{locale}/wp-json/…
- Tenant prefixes /{tenant}/api/…
- API versions /v2/wp-json/…
- Trailing slashes /wp-json/wp/v2//
- Case variations /WP-JSON/WP/V2/
- Double encoding /%77p-json/wp/v2/
The objective is to determine whether multiple application routes converge on the same protected functionality while the security control only recognizes one representation.
— -
## 2. Mapping the Exposed Surface
Once the REST API was reachable through the locale-prefixed route, the initial finding was no longer simply "the WAF can be bypassed."
The more important question became:
What functionality becomes reachable because the WAF can be bypassed?
Most of the endpoints I tested behaved consistently and required authentication. That consistency made one particular endpoint stand out.
— -
## 3. The Media Authorization Gap
The media endpoint behaved differently from the other tested REST endpoints. While endpoints for posts, pages, users, comments, and search required authentication, the media endpoint returned results without credentials:
GET /{locale}/wp-json/wp/v2/posts → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/pages → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/users → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/comments → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/search → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/media → 200 OK + full data
GET /{locale}/wp-json/wp/v2/posts → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/pages → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/users → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/comments → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/search → 401 rest_auth_required
GET /{locale}/wp-json/wp/v2/media → 200 OK + full data
This was important because it demonstrated an authorization enforcement inconsistency rather than simply establishing that some media was publicly reachable.
A statement such as "media files are public" can easily be interpreted as intended behavior. The stronger evidence was:
Authentication is enforced on the surrounding REST resources, while this specific resource returns data without authentication.
That makes the authorization inconsistency explicit and gives the triager a reproducible basis for evaluation. OWASP's authorization testing guidance specifically recommends checking whether resources can be accessed without the authentication or authorization level that should be required.
The media metadata also contained author identifiers. Across the affected locales, I identified 13+ internal CMS author accounts represented through the media metadata. I treated the account information as supporting evidence rather than automatically claiming privilege escalation.
— -
## 4. Quantifying the Exposure
The media endpoint supported standard REST API pagination. Rather than downloading the underlying files, I used the API's pagination metadata to determine the size of the exposed dataset:
HTTP/1.1 200 OK
X-WP-Total: 4682
X-WP-TotalPages: 47
HTTP/1.1 200 OK
X-WP-Total: 4682
X-WP-TotalPages: 47
Using those values across the affected locales, the final documented count was:
58,961 media objects across 12 locales
Each response included object metadata — titles, upload dates, author identifiers, file paths, and storage-related URLs — without requiring the file itself to be downloaded.
I deliberately avoided bulk-downloading the dataset. Proving scale does not require collecting everything.
— -
## 5. Multiple Cloud-Storage Backends
The media metadata revealed that the application was not relying on a single storage backend. The exposed objects referenced 5 distinct cloud-storage backends.
One of those backends appeared to correspond to a production object-storage environment that was still serving content without authentication. The architecture appeared consistent with a legacy storage system that had remained accessible after a CDN or infrastructure migration, although I treated that explanation as an inference rather than a confirmed root cause:
"The storage backend appeared to be a legacy system that remained accessible after the migration; the exact reason for its continued exposure could not be confirmed externally."
The exposed content included corporate documents, draft legal material, and organizational charts.
The significance was that the initial WAF bypass and authorization inconsistency had exposed a much larger content-discovery surface than the original REST API request suggested.
— -
## 6. The CORS Attack Surface
The exposed REST API led to another significant observation. The server dynamically reflected arbitrary Origin values:
Request:
Origin: [https://attacker.example.com](https://attacker.example.com)
Response:
Access-Control-Allow-Origin: [https://attacker.example.com](https://attacker.example.com)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE…
Request:
Origin: [https://attacker.example.com](https://attacker.example.com)
Response:
Access-Control-Allow-Origin: [https://attacker.example.com](https://attacker.example.com)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE…
I did not want to present the issue merely as "CORS is misconfigured." The relevant question was: what can an attacker actually do with the cross-origin access?
I created a minimal local HTML proof of concept:
<script>
fetch('https://target/{locale}/wp-json/wp/v2/media', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
// Attacker can read authenticated API responses
console.log(data);
});
</script>
<script>
fetch('https://target/{locale}/wp-json/wp/v2/media', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
// Attacker can read authenticated API responses
console.log(data);
});
</script>
The PoC demonstrated that:
-
A page hosted on an attacker-controlled origin could initiate the request
-
The browser included the victim's applicable credentials
-
The target accepted the request and reflected the attacker's origin
-
The browser permitted the attacker's page to access the response
This turned the finding from a header-level configuration issue into a demonstrated cross-origin authenticated API access path. I described it as a potential session-riding / cross-origin access path to authenticated API functionality, rather than claiming arbitrary administrative compromise — because the practical impact depends on the privileges of the victim's authenticated session.
The findings reinforced one another. Together they create a chained attack path:
Attacker-controlled origin
↓
Permissive CORS (any origin + credentials)
↓
Authenticated browser request
↓
Locale-prefixed REST API (WAF bypassed)
↓
Application functionality
Attacker-controlled origin
↓
Permissive CORS (any origin + credentials)
↓
Authenticated browser request
↓
Locale-prefixed REST API (WAF bypassed)
↓
Application functionality
— -
## 7. Secondary Findings
Once the REST API became accessible, additional information became visible: username enumeration through oEmbed-related functionality, internal and UAT hostnames exposed through plugin configuration, outdated software components reachable through the exposed surface, regional support email addresses embedded in page content, and third-party integration information visible in rendered HTML.
Several of these were individually low impact. I did not attempt to inflate their severity. Instead, I used them to demonstrate the broader consequence of the initial security-control failure:
A single WAF path-matching mistake exposed an application surface substantially larger than the endpoint originally being protected.
— -
## 8. Reflections
The report was confirmed and rewarded. The experience reinforced that technical complexity and bounty severity are shaped by different factors — asset classification, required attacker prerequisites, and program-specific risk models all influence the final outcome independently of the vulnerability chain's technical depth.
— -
## 9. Methodology
The entire investigation was performed with a deliberately minimal tooling footprint:
Tools used:
- curl → all HTTP requests and API enumeration
- Local HTML file → CORS proof of concept
- Manual analysis → response comparison and validation
Not used:
- Automated scanners
- Brute-force tools
- Bulk downloading
Tools used:
- curl → all HTTP requests and API enumeration
- Local HTML file → CORS proof of concept
- Manual analysis → response comparison and validation
Not used:
- Automated scanners
- Brute-force tools
- Bulk downloading
The investigation relied on request/response comparison, application route mapping, authentication-state comparison, pagination metadata, HTTP response headers, manual validation, and reproducible proof-of-concept requests.
This approach kept the investigation within the program's tooling restrictions while producing evidence that a triager could independently reproduce.
— -
## What I Learned
Don't assume the canonical path is the only path. Understand how the application constructs its routes. Locale prefixes, tenant identifiers, versioning, and other path components can create alternate representations of the same backend functionality.
Treat CORS as an attack surface, not just a header problem. A reflected Origin header is only the beginning. The important questions are: are credentials allowed? Can an attacker-controlled origin read the response? Which authenticated endpoints are accessible? Can state-changing functionality be reached?
Compare authorization behavior across equivalent endpoints. If five endpoints return 401 and the sixth returns 200 with sensitive data, document the difference. Comparative evidence is much stronger than a single request.
Quantify exposure without unnecessary collection. API pagination metadata was sufficient to establish 58,961 exposed media objects. That provided a defensible measurement while minimizing unnecessary interaction with the target.
Keep observed facts separate from hypotheses. Instead of "the legacy bucket was forgotten after the CDN migration" — write "the storage backend appeared to be a legacy system that remained accessible; the exact reason could not be confirmed externally." The second statement is more defensible.
Don't stop at the first valid finding. The WAF bypass itself was already reportable. But continuing to map the newly exposed surface revealed the authorization inconsistency, the 58,961 media objects, the 5 cloud-storage backends, the internal CMS metadata, and the cross-origin authenticated attack path. The most valuable part of the investigation was not discovering one isolated bug — it was understanding what that bug made possible.
— -
## Final Takeaway
A vulnerability should be investigated as a change in attack surface, not merely as an isolated technical defect.
A WAF bypass may initially look like a path-matching mistake. But if that bypass reaches an application layer containing inconsistent authorization, exposed data, permissive CORS, legacy storage, and internal metadata, the real security impact can be considerably broader.
The strongest reports are not necessarily the ones containing the largest number of vulnerabilities. They are the ones that clearly establish:
What was supposed to be protected
↓
How that protection was bypassed
↓
What became reachable
↓
What was actually demonstrated
↓
What impact was proven
↓
What conditions an attacker needs
What was supposed to be protected
↓
How that protection was bypassed
↓
What became reachable
↓
What was actually demonstrated
↓
What impact was proven
↓
What conditions an attacker needs
That is the methodology I will continue applying to application security research.
— -
## References
-
OWASP Web Security Testing Guide — methodology for web application security testing
-
OWASP — Testing Cross-Origin Resource Sharing — CORS testing and impact considerations
-
OWASP — Testing for Bypassing Authorization Schema — authorization testing methodology
-
Cloudflare WAF Documentation — WAF rules, request filtering, and path-based controls
— -
I'm Tarek Eldemerdash, an application security researcher focused on authentication, access control, and server-side vulnerabilities. I hunt on HackerOne, YesWeHack, and Intigriti.