Case Study: Responsible Disclosure of a Critical Path Traversal Vulnerability
Disclaimer: The vulnerability described below was discovered during independent security research. It has been responsibly disclosed to the institution’s IT department and was patched immediately. All sensitive details (target name, URLs) have been redacted to maintain confidentiality.
Introduction
With a background in e-commerce engineering and system architecture, I have a habit of checking the “health” of the systems I interact with. During my first week of university, while browsing the Student Portal, my “security radar” went off.
What started as a simple observation of HTTP headers led to the discovery of a critical Local File Disclosure (LFD) / Path Traversal vulnerability that could allow an attacker to read arbitrary files from the server.
Here is the technical breakdown of the discovery, the Proof of Concept (PoC), and the remediation process.
1. Reconnaissance: The “Red Flags”
The first thing that caught my attention wasn’t a bug, but a configuration oversight. The server response headers revealed the technology stack:
X-Powered-By: PHP/7.2.34
PHP 7.2 reached its End of Life (EOL) in November 2020. Seeing a version this old in production is a significant warning sign. A quick check confirmed that this version is susceptible to several known vulnerabilities, including CVE-2017-8923 and CVE-2022-37454.
While I decided not to probe these specific CVEs (as that could be invasive), it motivated me to look closer at the application logic itself.
2. The Discovery: Insecure Direct Object Reference
While navigating the “Directives” section of the portal, which allows students to download PDF documents, I examined the network traffic using Chrome DevTools.
I noticed an AJAX request responsible for fetching the files:
GET /ajax.php?action=download-tempfile&filename=1759877382_n46nrwzb5qodng18jspn.pdf&type=pdf
The parameters immediately raised questions:
action=download-tempfile: Implies interaction with the file system.filename=...: Takes a direct string input.
This is a classic setup for a Path Traversal attack if the application fails to sanitize the input. If the code simply concatenates a base directory with the user input (e.g., $file = 'temp/' . $_GET['filename'];), we can use ../ to escape the directory.
3. Proof of Concept (PoC)
I needed to verify the vulnerability without accessing any PII (Personally Identifiable Information) or sensitive credentials (like config.php).
Step 1: Testing Input Validation
First, I tried a non-existent file to see how the server handles errors:
...&filename=a
Result: Empty Response. This suggested the script likely uses file_get_contents() or similar and fails silently if the file isn’t found.
Step 2: Escaping the Directory
I attempted to access the .htaccess file, which is typically located in the web root.
Payload: ...&filename=../.htaccess
Result: Success. The server returned the content of the .htaccess file:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{HTTP_HOST} !^DOMAIN.pl [nocase]
RewriteRule ^(.*)$ [https://DOMAIN.pl/$1](https://DOMAIN.pl/$1) [L,R=301]
</IfModule>
# ... (rest of the file)
This confirmed that I could escape the intended “temp” directory.
Step 3: Verifying PHP Execution Context
The .htaccess file contained references to Composer (<Files "composer.json">). This gave me a safe target to verify if I could read .php source code.
I targeted the vendor/autoload.php file. This file is generated by Composer, exists in almost every modern PHP project, and contains no sensitive data (no passwords, no keys, no user data).
Payload: ...&filename=../vendor/autoload.php
Result: The server returned the PHP source code of that file:
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) { ... }
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit...::getLoader();

Step 4: System Level Access
To confirm the severity, I checked if I could access files outside the web root entirely (e.g., system files). I chose /etc/hostname as it is harmless but proves root-level read access.
Payload: ...&filename=../../../../etc/hostname
Result: It worked. The server returned the hostname of the machine.
4. The Impact
This vulnerability is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory.
It also falls under the Broken Access Control category, which is currently ranked #1 in the OWASP Top 10.
In a real-world attack scenario, a malicious actor could use this to:
- Read Configuration Files: Access
config.phpor.envfiles to steal database credentials and API keys. - Dump Source Code: Download the entire application logic to find further vulnerabilities.
- Access System Files: Read
/etc/passwdor ssh keys (if permissions allow).
5. Responsible Disclosure & Remediation
After confirming the vulnerability late at night (around 2-3 AM), I paused all testing. Instead of sending a rushed notification, I waited for business hours to prepare a professional report and ensure I could be available for communication.
The Timeline (October 8th, 2025):
- 02:00-03:00 AM: Vulnerability discovered and verified via PoC. I documented the findings and paused activity to draft a proper report.
- 12:27 PM: Initial Contact. I sent a preliminary email to the general IT contact, stating I found a critical vulnerability and requested forwarding the message to the person responsible for IT security or indicating a secure contact channel to share the technical details.
- 12:34 PM: IT Response. The IT department responded promptly, authorizing me to send the details via the current channel.
- 12:46 PM: Full Report Sent. I provided the detailed technical write-up, including the PoC steps.
- 01:35 PM: Patch Confirmed. I received a confirmation that the vulnerability was verified and patched.
The fix was deployed in less than an hour from the full report, which is an impressive response time.
How to fix this in PHP?
(General advice for developers)
The best way to prevent this is to never trust user input for file paths.
1. Use basename():
This function strips directory paths (e.g., turns ../config.php into config.php).
$filename = basename($_GET['filename']);
$path = 'temp/' . $filename;
2. Use an Allowlist: If possible, check the requested filename against a database of allowed files.
3. Disable Fopen Wrappers:
Ensure allow_url_fopen is restricted if not needed, though this is more regarding remote file inclusion.
Conclusion
This case study highlights why “Security by Obscurity” (hiding the temp folder) fails. Even a simple legacy script can expose an entire server if input sanitization is ignored.
I’m glad I could help secure the university’s infrastructure before someone with malicious intent found this flaw.