So you want to secure your PHP websites. Follow these 100 actionable PHP security tips. What happens if you don’t secure PHP? : US Govt Data Shows Russia Used Outdated Ukrainian PHP Malware.
A system is only as good as the weakest link in a chain Click To Tweet
This list will help enormously during any web development using PHP. Security is always an issue in any sector specially web development. Practice and safe guard your websites against hackers. For any job interview this php security tutorial will help a lot. These are PHP best practices. Some suggestions are for coding and some are for server configuration.
1. Always check user data for php security
From a developer’s point view, always think a user is stupid. A user has all the freedom in the world to type anything. It is the developer’s sole duty to check and validate all user inputs. In HTML, there is range checker for input type.
1 2 3 | <input max="100" min="10" name="input" step="10" type="text" value="10" /> |
Common thinking will be not to check the minimum or maximum value in php code. Why? Because user is not allowed to type without range. But that’s a restriction on client side or mainly in browser. So it will be better to check minimum and maximum value in PHP code.
This user data consists of checking:
- Data type.
- Correct length of string.
- Valid email, url, dates.
- Valid user input without inconsistent characters.
There are few PHP functions already available to validate user input.
To find out if an email is valid or not, use the below function:
1 2 3 | filter_var($email, FILTER_VALIDATE_EMAIL); |
To find out if a website URL is valid or not:
1 2 3 | filter_var($email, FILTER_VALIDATE_URL); |
More filters for validation is available on PHP manual: Filter for validation
2. Disable and avoid short tag:
It’s better not to use short tag. If you already don’t know what short tag is: instead of writing
1 2 3 4 5 | <?php $language = "php"; ?> |
, you can write :
1 2 3 4 5 | <? $language = "php"; ?> |
Short tag is good for fast coding. But fast is not always good. There may be server with short tag disabled. If you upload code in that server, whole code will be shown. Even wordpress plugins and themes come up with full tag. It’s better to avoid short tag. Also, if possible disable short tag in php.ini file.
In digitalocean , php.ini file is located here normally.
1 2 3 | /etc/php5/apache2/php.ini |
1 2 3 | short_open_tag = Off |
If you don’t have access to php.ini file in a shared hosting like: bluehost, use .htaccess and add this:
1 2 3 4 5 | <IfModule mod_php5.c> php_flag short_open_tag off </IfModule> |
or
1 2 3 4 5 | <IfModule mod_php5.c> php_value short_open_tag 0 </IfModule> |
3. Don’t display PHP errors
Any error stack trace is good for debugging. But don’t display them in production site. Again, don’t display php errors! Displaying php errors in live website can tell users info that they should not see. It can expose file name, directory structure. This can be done two ways.
Either disable php errors fully in php.ini :
1 2 3 | display_errors = Off |
or keep error reporting enabled but control displaying based on development or production mode:
1 2 3 4 5 | define('DEBUG', true); error_reporting(E_ALL); ini_set('display_errors', DEBUG ? 1 : 0); |
4. Disable access remote file
This allow_url_fopen should be disabled. Otherwise php files that can access remote files are vulnerable to arbitrary code injection. Most of the websites don’t need to execute code remotely. So it’s better to turn off allow_url_fopen in php.ini file. Please do restart your apache service
1 2 3 | allow_url_fopen = Off |
5. Restrict File Upload
If a website does not want a user to upload file at all, then disable file upload option in php.ini.
1 2 3 | file_uploads = Off |
or in apache’s httpd.conf file:
1 2 3 | php_flag file_uploads off |
Why do you want to restrict file upload? A user can upload anything instead of expected image. User has capability to upload exe files which can be harmful. If image is expected, it is better to check image size with getimagesize() function.
6. Restrict file system access
There is a way to limit PHP to specific directory tree. It means with fopen() function PHP can only access files under that directory. When the file is outside the specified directory-tree, PHP will refuse to access it. This option can be set with open_basedir in php.ini:
1 2 3 | open_basedir = /var/www/public |
open_basedir can be turned off in httpd.conf also:
1 2 3 | php_admin_value open_basedir none |
7. Lower maximum execution time
If you want poorly written scripts from tying the server, it is better to lower maximum execution time. Default is 30 seconds. Do some experiment based on your expectation. Don’t increase it.
1 2 3 | max_execution_time = 10 |
8.Controlling post size
Post is used to transfer data. User needs to upload images, audio or video. Based on the web service demand, post size can be controlled few ways. In own php code:
1 2 3 | @ini_set( 'post_max_size', '1M'); |
or in .htaccess file:
1 2 3 | php_value post_max_size 1M |
or in php.ini file:
1 2 3 | post_max_size = 1M |
9. Filter output data
You should filter out what is displayed in your website. There are few functions to accomplish this task. Filtering out will restrict malicious javascript to execute during rending. Like:
htmlentities(): Convert all applicable characters to HTML entities
htmlspecialchars(): Convert special characters to HTML entities
strip_tags(): Strip HTML and PHP tags from a string
1 2 3 4 5 | $text = '<a href="php">php</a>'; echo htmlspecialchars($text); // <a href="php">php</a> echo strip_tags($text); // php |
10.Important action with Post method
For any important action under active session, it is better to use POST method. Why? A user may need to delete info. If delete is available with GET method, a user can be tricked. Check the following example:
1 2 3 | <a href="http://example.com/action.php?delete=true&id=100"><img src="dog.png" /></a> |
What’s happening above? User is presented with an image with link to delete info. If GET is requested is allowed to perform that job, user can be tricked easily. Secured approach would be to allow delete action using POST request.
11. Disable magic_quotes_gpc and magic_quotes_runtime
As a programmer, it is a hassle to use addslashes() for almost every input. Hence magic_quotes_gpc() comes handy. There are three problems. If magic_quotes_gpc and addslashes are used together, there will be multiple slashes which can cause error. Ok next, you may not use addslashes thinking magic_quotes_gpc is there for you. All data will end up unchecked. Another one is magic_quotes_gpc does not escape all database related characters. It’s better to turn off magic_quotes_gpc and do proper validating mentioned in step 1. This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0. To make sure it is turned off, this can be followed in .htaccess:
1 2 3 4 | php_flag magic_quotes_gpc 0 php_flag magic_quotes_runtime 0 |
or in php.ini:
1 2 3 4 5 | magic_quotes_gpc = Off magic_quotes_sybase = Off magic_quotes_runtime = Off |
12. Update PHP
You should always update PHP in your operation server. Almost every day new flaws are discovered and reported in PHP. Hackers want to exploit that flaw and take control of website. It is recommended to update PHP to the latest version to be in safe side.
13. SetHandler directive
User proper SetHandler to execute PHP code. A file name comes with readme.php.txt file which may not be executed as php file. It’s a huge remote execution vulnerability if readme.php.txt file is handled as php code. Check the proper ‘AddHandler’ directive and get rid of future problem. Check how to set AddHandler in PHP
14. Proper way of processing $_FILES array
No security threat but good to do:
- Don’t expose php is installed on the server. Turn expose_php off in php.ini
1 2 3 | expose_php = Off |
15. Check MIME type correctly
It is tempting to check image type from file array in this way:
1 2 3 4 5 | if ($_FILES['file_name']['type'] == 'image/png') { //not a good way to check image type } |
The client can trick the above example. So it’s not good way to validate file type. Another way to do this is to use finfo class:
1 2 3 4 5 | $finfo = new finfo(FILEINFO_MIME_TYPE); $fileContents = file_get_contents($_FILES['file_name']['tmp_name']); $mimeType = $finfo->buffer($fileContents); |
16. Stop using $_REQUEST
$_REQUEST contains get, post and cookies all in one array. It is hard to trace the source of the data. If you want to process only $_POST data from form, then use only $_POST. With the use of $_REQUEST, any one can send data through $_GET which should be used by only $_POST.
17. Do you concatenate data in SQL? Don’t
Concatenating data in sql opens vulnerability. Check this:
1 2 3 | $sql = "SELECT * FROM user WHERE username = '" . $username . "';"; |
$username may contain another ‘ character in it by choice or without knowledge. That will break this query and also opens up door for intruder to do bad staff. It may end up like this:
1 2 3 4 5 6 | //$username = "evil';drop table users;'" $sql = "SELECT * FROM users WHERE username = '" . $username . "';"; //final sql will be $sql = "SELECT * FROM users WHERE username = 'evil';drop table users;'';"; |
18. Do you use interpolation in SQL? Don’t
Interpolation may open up SQL vulnerability same as concatenating data. Check the previous one and consider this use case:
1 2 3 | $sql = "SELECT * FROM user WHERE username = '$username';"; |
19. Escaping string is bad
To escape string, mysqli_real_escape_string can be used. Let’s say you have 20+ strings to escape. It is very easy to forget one escaping. Then the non-escaped one will be concatenated in sql query. Pretty good situation for a hacker and worse situation for application developer.
20. User Prepared Statement or ORM
For a better sql safe query, use prepared statement or any ORM. This can also leverage to use query with different RDMS (Relational Database Management System). Prepared statement has less load on server. Statement template is parsed once, but executed with values for multiple times. You don’t need to think about escaping or sql injection that much with prepared statement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php $server = "localhost"; $username = "admin"; $password = "12345678"; $database = "wp_security"; //connection query $conn = new mysqli($server, $username, $password, $database); // Check connection and throw error on failure if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // prepare and bind query $stmt = $conn->prepare("INSERT INTO users (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // set parameters and execute $firstname = "sajjad"; $lastname = "robin"; $email = "info@sajjadrobin.com"; $stmt->execute(); $firstname = "robin"; $lastname = "TBG"; $email = "info@webtutts.com"; $stmt->execute(); //Close statement and connection $stmt->close(); $conn->close(); ?> |
The first parameter in bind_param function is type of arguments. Details is here: bind_param
21. What encoding do you use? Utf-8 unless necessary
There are lot of attacks depends on bypassing encoding. It is good to use UTF-8 for application and also for database if there is no other requirement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php $server = "localhost"; $username = "admin"; $password = "12345678"; $database = "wp_security"; //connection query $conn = new mysqli($server, $username, $password, $database); // Check connection and throw error on failure if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $conn->->set_charset('utf8'); |
22. Eval is evil
Whatever is passed in eval gets executed in PHP. But don’t use eval(). It’s evil. It’s also not recommended in Javascript world to use eval().
23. Delete cookies properly
It is essential to delete cookies properly. To delete a cookie completely, follow this:
1 2 3 4 5 | setcookie ($cookieName, "", 1); setcookie ($cookieName, false); unset($_COOKIE[$cookieName]); |
Want to know more about setcookie, check this: setcookie()
So, in the above script $cookieName has an expiry time. If there is any problem with Internet Explorer (IE), just set the value to 0. In the second step, $cookieName is reset to false meaning there is no value in it anymore. Lastly, $cookieName is removed the script.
24.Disable Register globals
register_globals should be disabled. It is removed in PHP 5.4.0. Register globals can lead to security problem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php if (isUserLoggedIn()) { $logged = true; } if ($logged) { include '/personal/user/information.php'; } ?> |
It is easy to add a query parameter ?logged=true in URL for the above example. If register_globals is enabled, $logged will be valid even for not logged in user. It’s not directly related to register_globals. But disabling register_globals will surely remove this security issue.
25. Manage Session safely
Weak management of session can open huge vulnerability in website. Session needs to be handled delicately and as tightly as possible.
- Store session in database – Sessions are by default stored in files. In shared hosting, this file can be readable by other users. Saving session in database remove this issue. It will be only visible to application using it.
- Regenerate Session ID: It is good idea to regenerate Session ID after an interval. It will invalidate earlier ID in case it was hacked. More info: session_regenerate_id .Session should be changed at least when a user gets logged in, logged out or change in user roles happens. Session can be changed over a periodic interval like every 5 or 10 minutes.123session_regenerate_id ()
- Restrict session to user agent: It will be good idea to destroy session if a user changes browser. It can be done easily by checking user agent. If there is no issue for tor browser, you can add user’s IP address in addition to user agent.1234567891011121314151617181920212223242526272829303132333435<?phpfunction getUserAgentIP() {$user_agent_ip = @md5( $_SERVER['HTTP_ACCEPT_CHARSET'] .$_SERVER['HTTP_ACCEPT_ENCODING'] .$_SERVER['HTTP_ACCEPT_LANGUAGE'] .$_SERVER['HTTP_USER_AGENT'] .$_SERVER['REMOTE_ADDR']);return $user_agent_ip;}function login($username, $password) {/* login check here *///login successful$_SESSION['loggedUserAgentIP'] = getUserAgentIP();}//now check login status with intervalfunction checkLoginStatus(){if($_SESSION['loggedUserAgentIP'] == getUserAgentIP()){return true;}return false;}if(!checkLoginStatus()){logout();}
- Use single session if possible: For e-commerce website, single session will be useful. It is better to logout previous session if a user logs in from a new place or browser. It can be easily done if session is saved in database. Simply, replace the old login session with the new one for that user.
Read more:
How to setup virtual host in XAMPP
How to set AddHandler in PHP
Follow me: @sajjadrobin
or the Beard Guy: Robin The Beard Guy (RobinTBG)
Grow 100 twitter Followers in 24 hours