π§ Introduction
Sometimes, the most critical vulnerabilities are not caused by complex logic⦠but by small inconsistencies in how input is handled.
In this write-up, I'll walk through a vulnerability in a file access system that uses HMAC-based presigned URLs, and how a subtle mismatch between validation and sanitization leads to arbitrary file access.
βοΈ Application Logic Overview
The application exposes two main functionalities:
action=signβ Generates a signed URL for a fileaction=downloadβ Downloads the file using the signed URL
Security Controls
- Only files under the
public/directory are allowed - Path traversal (
..) is explicitly blocked
At first glance, everything looks secure.
π¨ The Vulnerability
The issue lies in an inconsistent handling of the filename between two stages:
πΉ During Signing
- The application:
- Checks if the filename contains
.. - Then applies sanitization via
sanitizeFilename()
πΉ During Download
- β No sanitization is applied
- The filename is used directly
π£ Exploit Strategy
The idea is simple:
Make the server validate a "safe" input⦠that becomes "dangerous" after sanitization.
π§ͺ Step-by-Step Exploitation
πΉ Step 1 β Bypass the .. Filter
Instead of using:
../
We inject a control character between the dots:
.%7f.
Resulting payload:
public/.%7f./super_secret.txt
β The server does NOT detect ..
β The validation passes
πΉ Step 2 β Sanitization Effect
The function: sanitizeFilename()
removes control characters. So: .%7f. β ..
Final transformed path: public/../super_secret.txt
πΉ Step 3 β Generate a Valid Signature
Request:
?action=sign&filename=public/.%7f./super_secret.txt
The server generates the signature based on:
public/../super_secret.txt
πΉ Step 4 β Retrieve the File
Now we use the normalized path:
?action=download&filename=public/../super_secret.txt&expires=β¦&signature=β¦
Resolved path:
files/public/../super_secret.txt β files/super_secret.txt
β Signature matches β Access control bypassed β Sensitive file retrieved
π Impact
- Unauthorized access to restricted files
- Complete bypass of access control
- High-risk sensitive data exposure
This vulnerability effectively allows an attacker to read any file accessible via path traversal.
𧨠Root Cause
The root issue is:
β Validation is performed before sanitization
This creates a gap where input can change after validation.
π‘οΈ Remediation
βοΈ Correct Approach
- Apply sanitization before validation
- Ensure consistent input handling across all flows
βοΈ Additional Protections
π Reject control characters
if (preg_match('/[\x00-\x1F\x7F]/', $filename)) { reject(); }
π Enforce strict allowlist
^public/[a-zA-Z0β9._-]+$
π Normalize paths securely
- Use
realpath() - Ensure the resolved path stays within the allowed directory
π Use consistent logic
- The same normalized input must be used in:
- Signing
- Verification
π§© Key Takeaways
- β οΈ Order of operations matters in security
- π§ Sanitization β Validation
- π Always normalize input before applying checks
- π‘ Control characters can be used as powerful bypass techniques
π Conclusion
This vulnerability highlights how a small inconsistency in input handling can completely break a security mechanism.
Even when strong cryptographic protections like HMAC are used, logic flaws can render them useless.