TL;DR
The file_exists() function in PHP can be bypassed using various techniques, including symlinks, race conditions, and alternative path representations. This guide explains common bypass methods and how to mitigate them.
Understanding the Problem
Many applications use file_exists() to check if a file exists before performing operations on it. However, this function isn’t foolproof. Attackers can sometimes trick the application into thinking a file exists when it doesn’t, or vice versa, leading to vulnerabilities like arbitrary file inclusion or deletion.
Bypass Methods
- Symlinks (Symbolic Links)
- A symlink is a special type of file that points to another file.
file_exists()will returntrueif the target of the symlink exists, even if the original file doesn’t exist in the expected location. - Example: If your application checks for
/var/www/data/userfile.txtand an attacker can create a symlink at/var/www/data/userfile.txtpointing to/etc/passwd, the check will pass even though they’re accessing a sensitive system file. - Mitigation: Use
realpath()to resolve the actual path of the file before checking its existence. This follows symlinks and returns the canonical absolute pathname. - Race Conditions
- A race condition occurs when multiple processes access and modify the same resource concurrently. An attacker can exploit this by creating a file after
file_exists()checks but before the application actually uses it. - Example: The application checks if
/tmp/tempfile.txtexists, then creates it. An attacker could create their own malicious file with the same name in between these two steps. - Mitigation: Use atomic operations to ensure that the file creation and existence check happen as a single, indivisible unit. This is difficult to achieve reliably in PHP without extensions like
inotifyor using more robust locking mechanisms. Consider avoiding checking for existence altogether if possible (see below). - Alternative Path Representations
- Different operating systems and filesystems may represent the same file path in different ways (e.g., with varying case sensitivity, forward slashes vs. backslashes).
file_exists()might treat these as distinct paths. - Example: On a case-insensitive filesystem,
/var/www/data/UserFile.txtand/var/www/data/userfile.txtmight be considered different files. - Mitigation: Normalize the path using
str_tolower()or similar functions before checking its existence to ensure consistent case handling. Also, use absolute paths whenever possible. - Null Byte Injection
- In older PHP versions, null bytes (
%00) could be used to truncate the path afterfile_exists(). This is less common now due to improved security measures. - Example: If an attacker provides a filename like
/var/www/data/userfile.txt%00,file_exists()might only check for/var/www/data/userfile.txt. - Mitigation: Sanitize user input to remove null bytes and other potentially harmful characters. Use modern PHP versions with built-in protections against this type of injection.
Best Practices for Prevention
- Avoid Checking for Existence When Possible: If you’re creating a new file, simply attempt to create it. Handle the exception if it fails (e.g., due to permissions or disk space).
- Use Whitelisting: Only allow access to specific files or directories that are explicitly permitted. Don’t rely on blacklisting.
- Sanitize User Input: Thoroughly validate and sanitize all user-provided input before using it in file paths.
- Least Privilege: Run your application with the minimum necessary privileges to reduce the impact of potential vulnerabilities.
- Regular Security Audits: Regularly review your code for security flaws, including those related to file handling.

