As I promised I will keep posting about website speed and security, I decided to share with you a vulnerability I’ve often seen to download any file from a server. Directory traversal attacks can work in various PHP projects as well as WordPress themes/plugins. Note that this does not apply to PHP only, it can be used with other scripting languages, so consider checking and updating your code to make it secure.
What is a directory traversal attack?
According to Wikipedia: “A directory traversal (or path traversal) consists in exploiting insufficient security validation / sanitization of user-supplied input file names, so that characters representing “traverse to parent directory” are passed through to the file APIs.”. This HTTP exploit is also known as as the ../ (dot dot slash) attack, directory climbing and backtracking.
The most recent attack on I’ve noticed was when I checked my 404 logs and noticed attempts on downloading wp-config.php by accessing a file that doesn’t exist and likely has a vulnerability. The path was something like /wp-content/themes/[theme_name_here]/scripts/download.php?file=../../../../wp-config.php. I didn’t have that theme installed (as a result a 404 error was issued and logged). It was mostly a scanner that checks WordPress websites for vulnerability and the moment it manages to access wp-config.php it marks that website as vulnerable in their system.
Now, once the vulnerability is found, more files can be downloaded from your server and some of them should not end up being downloaded by everyone, especially by hackers that can mess up your website and affect even your clients.
What can an attacker do with wp-config.php (or any similar configuration file) for instance?
While some attackers would not manage to do much due to their lack of experience, others can have a lot of help by accessing the sensible data from this file. Here are some of the things that you should know about:
1) Keys and Salts – Once one knows them, they can take advantage of that to get into the Dashboard. Click here to read more about this!
2) SQL Injection – Tables prefix are revealed which is useful for someone to attempt an attack
3) MySQL Database Charset – Some attacks rely on this information
4) Login Credentials – While they might not access your database (unless you allow external connections to it, a thing that should be done wisely and if only it’s necessary), they can try variations of your logins on the FTP, email and other popular websites where it’s likely you haven an account (such as Facebook). Often, people make this mistake of using the same username & password combination on multiple websites and emails, thus making things easier for an attacker.
Now, let’s take a look how a vulnerable download.php file (located in the WP theme’s root folder) would look like:
<?php $files_folder = 'files'; $file_name = isset($_GET['file']) ? $_GET['file'] : ''; $path_to_file = dirname(__FILE__).'/'.$files_folder.'/'.$file_name; if(file_exists($path_to_file)) { header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past header("Content-Description: File Transfer"); header("Content-Type: application/save"); header("Content-Length: ".filesize($path_to_file)); header("Content-Disposition: attachment; filename=".basename($file_name)); header("Content-Transfer-Encoding: binary"); readfile($path_to_file); } else { exit('The requested file does not exist.'); } exit; ?>
Why is this code vulnerable?
As you can see in the code, the file download.php is trying to get any file requested that is located within “files” folder. The code dirname(__FILE__)
returns the local server path to the theme.
Only one check is made to determine if the file exists before it is being downloaded. However, this can be easily hacked by using the ../ way to traverse through folders.
In the following example, the file wp-config.php is being downloaded from the server:
http://www.your-domain.com/wp-content/themes/your-theme-name/download.php?file=../../../../wp-config.php
Basically the full path to the file that is being checked if exists is something like: /home/site/public_html/wp-content/themes/your-theme-name/files/../../../../wp-config.php. As ../ goes to the parent folder, you can see that the function returns true and the right headers are being sent to the browser to download this file. Perhaps you have seen in many PHP applications code like the following: include '../config.php'
. It includes the file located in the parent folder. Note that I’m not against this inclusion practice as long as it’s used properly in projects.
How can this code be improved?
1. We should strip ../ from being included in the file name.
2. Check if the file really exists in the directory where we want the files to be downloaded from. We’ll use scandir to get the files and then check if our requested file is in the list.
Here’s a new improved version of the code:
<?php $files_folder = 'files'; $file_name = isset($_GET['file']) ? $_GET['file'] : ''; // No ../ are allowed and something has to be requested if( ($file_name == '') || (strpos($file_name, '../') !== false) ) { exit('Invalid Request'); } // Now let's see if the file really exists in $files_folder $path_to_folder = dirname(__FILE__).'/'.$files_folder.'/'; $files_in_folder = @scandir($path_to_folder); if( ! in_array($file_name, $files_in_folder) ) { exit('Files does not exist.'); } $path_to_file = $path_to_folder . $file_name; header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past header("Content-Description: File Transfer"); header("Content-Type: application/save"); header("Content-Length: ".filesize($path_to_file)); header("Content-Disposition: attachment; filename=".$file_name); header("Content-Transfer-Encoding: binary"); readfile($path_to_file); exit; ?>
Note that this works if you wish to allow people to download any file from that directory. If you only wish that only specific files should be downloaded, you can include extension checking to avoid for instance .PHP file downloads OR make a list of all possible files that can be downloaded and an extra check to see if the file is there, like in the following example:
$allow_list = array('data.zip', 'extra.zip', 'other-file.csv'); if( ! in_array($file_name, $allow_list) ) { exit('Invalid Request! You are not allowed to download the requested file.'); }
/download.php?hash=98b0eec3a439c21f21&key=10a10414
. Then, the keys and hashes can be checked in a database and in the backend retrieve the actual file and download it. That’s how most of the secure download scripts really work. The code above is a simple one that works fine if you really don’t need to add any extra security or bother using a MySQL database or extra code.I hope this post helped you with ideas to prevent directory traversal attack on any websites you’re working on. Check out some links about this kind of vulnerability:
- http://en.wikipedia.org/wiki/Directory_traversal_attack
- http://finalphoenix.me/blog/2015/02/05/7-popular-wordpress-plugins-that-will-get-your-site-hacked/
- http://www.acunetix.com/websitesecurity/directory-traversal/
- http://www.darkreading.com/vulnerabilities—threats/dont-underestimate-directory-traversal-attacks/d/d-id/1140306?