y4ss3r
cyber-sec engineer
← cd ../writeups
Bug Bounty/Web · SSRF/Medium/Oct 2026/6 min

Path traversal to a constrained SSRF: reading an internal Drupal host

TL;DR — A public content-listing endpoint accepted ../ sequences in a path parameter and relayed them to an internal Drupal backend, returning internal JSON the Internet can't reach. A redacted SSRF writeup.

WebBug BountySSRFDrupal
[ machine card / cover ]

A redacted writeup of an SSRF I found on a European energy provider's blackbox program. Host names are redacted. Full-disclosure note: this one was triaged as a duplicate — someone had reported the same issue first — but it's a clean, independently-found example of turning a path parameter into a constrained SSRF, so it earns a place here.

TL;DR

A public endpoint, /api/content-listing, built a server-side request to an internal Drupal backend using a user-supplied entityType parameter as part of the request path, with no validation and no URL-encoding. Feeding it ../ sequences let me escape the intended path and make the frontend fetch arbitrary paths from the internal host — and because the app reflects any JSON body straight back, I could read internal resources the Internet can't reach (such as Drupal's composer.json).

Class: SSRF (CWE-918) — more precisely arbitrary internal request-path traversal (constrained SSRF), since the destination host is fixed. Severity: Medium (CVSS 5.8).

Why it's "constrained" SSRF

The attacker controls the request path and query string only; the destination host stays pinned to the internal Drupal server. That's not full SSRF (I can't repoint it at 169.254.169.254), but it still crosses the trust boundary from the public frontend into an internal backend that isn't directly reachable — and whenever the internal resource returns JSON, the app hands back the full response body.

Exploitation

# The intended call fetches content from the internal Drupal CMS.
GET /api/content-listing?paragraphId=99999&page=0&langcode=en&entityType=..%2F..%2Fcore%2Fcomposer.json HTTP/1.1
Host: www.<redacted>
User-Agent: Mozilla/5.0 -BugBounty-ywh-hunter
  1. Hit the public endpoint /api/content-listing — no auth needed.
  2. Replace the expected entityType value with a traversal payload: ../../core/composer.json.
  3. URL-encode it: ..%2F..%2Fcore%2Fcomposer.json.
  4. The frontend builds the backend URL by string concatenation, producing http://production-<redacted>-drupal/core/composer.json.
  5. The backend returns the JSON and the app reflects the complete body:
{
  "name": "drupal/core",
  "description": "Drupal is an open source content management platform ...",
  "type": "drupal-core"
}

Proving it's really the internal backend (not a public file)

  • https://www.<redacted>/core/composer.json returns 404 — the file isn't served publicly.
  • production-<redacted>-drupal is not publicly resolvable.
  • Reproducible from multiple clients (curl and Python).
  • The app even leaks the internal hostname in its error responses.

Risk

An unauthenticated attacker can:

  • issue arbitrary-path requests against the internal Drupal backend;
  • read JSON from internal resources;
  • enumerate internal paths by response differences (XSPA); and
  • fingerprint internal infrastructure — hostnames, backend technology, reachable internal GraphQL.

The demonstrated impact is disclosure of an internal, non-public JSON resource — but the real point is the broken trust boundary between the public frontend and an internal service.

Remediation

  • Validate entityType against a strict allowlist (e.g. only node and media).
  • URL-encode path segments, and build backend URLs with a safe URL API instead of string concatenation.
  • Restrict the frontend to the required backend path prefix only.
  • Stop leaking internal URLs/hostnames in client-facing error messages.
  • Review access controls on internal endpoints reachable through the backend — GraphQL and other JSON endpoints included.

Lesson

Any user input that ends up inside a server-side URL path is an SSRF candidate — even when the host is hard-coded. ../ plus URL-encoding is still one of the first things to try, and "the host is fixed" is not a mitigation when the fixed host is an internal one you were never meant to reach.

YL
Yasser LaroussiCPTS · eJPTv2
Next writeup
... /Related ...
Bug Bounty/Android · Mobile/Hard/8 min

Hijacking a trusted app's WebView via an exported Android Activity

An energy app shipped a device-linking SDK whose Activity was exported with no permission, parsed an unsigned base64 token, and loaded attacker URLs into a JS-enabled WebView wired to native Bluetooth + OAuth bridges. A redacted writeup of an accepted finding.

MobileAndroidBug BountyWebView
Read moreOct 2026
Bug Bounty/API · Web/Intermediate/7 min

Unauthenticated IDOR in a contract-confirmation flow (PII-hash disclosure)

An endpoint that needed no auth, ignored the user ID, and keyed on a sequential contract ID — leaking an internal GUID and md5(email) for any contract. A redacted writeup of an accepted finding.

Bug BountyIDORAPIAccess Control
Read moreOct 2026