Section 14.1.


Once your code is working, you may be tempted to think that you're done with it. In reality, you may have some security issues that don't affect normal usage but still provide an opening for an attack. The unfortunate reality of web-accessible applications is that they're only as secure as their weakest link. Therefore, you must be conscious of security on every level, from the database to the web server and the PHP processing itself.

Although you can't make every system truly unbreakable, you can perform the equivalent of dead-bolting doors and locking windows. If you make your system difficult enough to compromise, then it's generally not worth a hacker's effort, though keep in mind that some may still try. We've had our own server locked up from hackers trying to get in, or boatloads of spam that cause the server to belch and stop working temporarily.

We're going to reiterate some of the security concepts that we discussed while learning the basics of PHP and MySQL. That reduces the risk that you'll build a site without reading about security and so will end up with an easily compromised site. We'll also expand on those topics to give you some more options for making hackers' lives difficult and your life easier.

14.1.1. Limit Access to Administrative Pages

When installing software packages that include a control panel or setup script, you should always either change the script's directory or, in the case of setup scripts, remove them after you're done installing. These scripts can provide a way for a random web surfer to mess up your configuration for the package you installed. While that isn't so bad, in a worst case scenario, it could lead to hackers uploading PHP code of their choice and doing quite unpleasant things with your system. Most web-based packages recommend doing this in their installation instructions. Follow their advice; they wrote the installation manual for a reason: for you to read it! As most technical writers say, "Always published, never read." How many people do you know personally who actually read their alarm clock setup, DVD player setup, or manuals for any number of electronic devices?

An alternate means of securing directories containing administrative scripts is to create an .htaccess file in the same web directory as the scripts. This file tells Apache to require a user to authenticate it before it returns any of the information in that directory.

To require authentication for a specific directory, place the code in Example 14-1 into a file called .htaccess in the directory you created for the code.

Example 14-1. Using Apache authentication to restrict access to scripts

 AuthType Basic AuthName "Administrators Only" AuthUserFile /usr/local/apache/passwd/passwords Require valid-user 

Requesting a directory or subdirectory where this file was saved causes the prompt in Figure 14-1 to display in Firefox; Internet Explorer also displays a similar prompt.

Figure 14-1. The authentication prompt your browser displays because of the Apache authentication request

Failure to supply a correct username and password causes the warning in Figure 14-2 to display.

Figure 14-2. The browser won't return any information for a protected directory without a valid login

For best results, this file shouldn't be readable by usersonly by the web server process. On a Unix system, this can be set with the command:

 chmod 644 /usr/local/apache/passwd/passwords 

Apache has a special command, the htpasswd file, which contains valid usernames and encrypted passwords for your web site. The path to the htpasswd file needs to be specified in the htaccess file as the AuthUserFile.

As you probably know, usernames and passwords are completely arbitrary; unfortunately, there's no correspondence between the usernames and passwords used in your htaccess file. For example, if your login name is mdavis, your username for the htaccess file could also be mdavis, or it could be Michele.

Keep in mind that you need to set up usernames that are understandable for your site, and then you need to create passwords for those usernames.

It's important to know that the htpasswd command is used to create the username and password pairs. The full path for the command on a Unix/Linux server is /usr/local/bin/htpasswd. Remember, the htpasswd command reflexively encrypts every single password before writing it to the htpasswd file. In other words, the htpasswd command takes the name of the password file and the username to set its parameters. Look at Example 14-2 for the correct format.

Example 14-2. Creating an Apache password for .htaccess

 htpasswd -c /usr/local/apache/passwd/passwords mdavis 

The -c option is required only for adding the first entry to a password file. You'll be prompted to enter the password twice to ensure that you don't have a typo. If the passwords match, you'll see the following:

    Adding password for user mdavis 

As stated above, keep in mind that if the password is valid, it's automatically encrypted. When you do this, only users who respond correctly to the authentication prompt are able to access pages in the directory in which .htaccess resides in any subdirectories.

On Windows, the procedure is quite similar, but instead of using htpasswd, use htpasswd.exe. It's usually located in C:\Program File\Apache Group\Apache2\bin\. You can also place the .htpasswd file in the C:\Program Files\Apache Group\Apache2 directory.

14.1.2. Including Files

Because no one ever wants to recreate the wheel, there are ways to reuse code. It probably sounds like plagiarism, but in the world of open source, it's a bonus to reuse code using include files.

Obviously, the ability to reuse code by including makes your life easier by not having the same blocks of code repeated over and over in your programs. It also improves the maintainability of your pages, because code used on multiple pages need only be modified once in the PHP source file.

The downside to look out for is using filenames for your included code that allows the web server to return the contents of the file without being processed by PHP. This has two major security risks. First, it allows a user to see your PHP source code, which could allow someone to look for weaknesses in the code and then know how to easily exploit them. Second, you could expose passwords that may be stored in an include file. In order to thwart these problems, make sure that you always name your included file with the .php extension and not something such as .inc that won't be processed if viewed directly. But, there is a caveat when using include.

For PHP versions before 4.0.2, require always attempts to read the target file, even if the line it's on never executes. The good news is that the conditional statement won't affect the require statement. Then again, if the line on which require occurs doesn't execute, neither will any of the code in the target file. Additionally, looping structures don't affect the operation of the require statement. Remember that the code in the target file is still subject to the loop, even though the require statement occurs only once.

Figure 14-3 shows what can happen by simply requesting your db_login.php script if it ends in an .inc extensionfor example,

Figure 14-3. Nothing that we want the world to see!

You should avoid ending the login script with a .inc extension; there's a better way. The files should be renamed with a .php extension. You can put your include files that have sensitive information in a directory that's not under the published web root. Another good way is to place it in a directory that's protected by an .htaccess file, at the very least.

14.1.3. Storing Passwords in the Database

In general, it's never a good idea to store passwords for users in the database without encoding them. The principal reason for this is that if someone is able to gain access to your database, even just read-only access, he can get all of your users' passwords. This allows that person to log in as other users, and he could attempt to use the same password on other web sites, since many users use common passwords across numerous sites.

We see password violation on a weekly basis. Our teenager uses instant messaging, and his friends know what he likes and he knows what they like, so all the teens can extrapolate someone's password just based on their knowledge of their friends. Then they can log in as someone else and raise havoc by pretending to be soccergrrl, as opposed to their own login, randomkid.

There are only a few downsides to encrypting passwords, including slightly increased complexity and the need to change a password for a user instead of being able to relay the forgotten password. One way to work around this problem is to store a password hint in the database. This is something that a user can enter when registering that'll help her remember what password she used. For example, if your password is your dog's name, you might use "dog" as a reminder.

So far, we've only discussed a single way to encrypt a password using the md5 one-way encrypt function. There's actually another function that can be used that is more secure. It's called sha1, which stands for secure hash algorithm. Instead of returning a 128-bit string such as md5, sha1 returns a 160-bit string. The added length helps make it harder to guess the original password value. Additionally, the algorithm that's used in sha1 is more advanced that md5, making it more difficult to break the code.

For example, try Example 14-3 and see what you get when you run the code.

Example 14-3. Comparing the output of md5 to that of sha1

 <?php echo "Encrypting <b>testing</b> using md5: ".md5("testing"); echo "<br />"; echo "Encrypting <b>testing</b> using sha1: ".sha1("testing"); ?> 

This displays the result shown in Figure 14-4.

Figure 14-4. The output from sha1 is slightly longer than md5's

14.1.4. The Problem with Automatic Global Variables

Sometimes making life easy for developers can cause problems. Early versions of PHP (before version 4.2.0) by default allowed you to access variables for a GET or POST operation automatically as global variables with the name of variable coming from whatever came from the GET or POST operation. While this was very convenient, it also created a big security hole.

The actual setting that changed its default value is called register_globals. It could be set to OFF in the php.ini configuration file before. Most people didn't change the default value though.

It wasn't really that register_globals was a terrible idea, it's just that most people didn't properly check the source of a variable before use. The danger was that because PHP doesn't require variables to be predefined, it's possible for a malicious user to call your PHP script with a GET or POST parameter that you aren't anticipating. If that variable matches the name of a variable that you're using for something important, such as indicating whether a password matches, then the malicious user might be able to change the functionality of your program just by adding a false parameter.

Unfortunately, admitting that this was a mistake and having to change the default value wasn't without some pain. Because many people assumed that they could automatically reference form-submitted values as globals, scripts that used to work now find no value where they expect it. Code had to be rewritten, and worse yet, you may still find some code that hasn't been fixed and therefore doesn't work and won't even tell you the problem.

If you've just downloaded a set of PHP scripts from the Internet and find that they run but essentially ignore form-inputted data, there's a good chance that they were written with the assumption that register_globals was on. You'll need to either expand out the variables or change the references within the scripts to the appropriate $_GET or $_POST superglobals.

Example 14-4 shows how the globals could be misused (assuming the function check_username_and_password is defined already).

Example 14-4. Not initializing a variable was a hole in sample.php

 <?php if (check_username_and_password()) {    //they logged in successfully    $access = TRUE; } if ($access) {    echo "Welcome to the administrative control panel.";    //more privileged code here... } else {   echo "Access denied"; } ?> 

What should have happened in the code for Example 14-4 is to set $access to FALSE before it was used. Had a malicious user called a script such as http://sample.php?access=1, she'd see Figure 14-5.

Figure 14-5. A security breach

The value for $access of TRUE from the GET parameter would cause the check for access to return trUE when register_globals is on. Modifying the code to look like this:

 <?php //predefining the value is good coding practice anyway $access = FALSE; if (check_username_and_password()) {    //they logged in successfully    $access = TRUE; } if ($access) {    echo "Welcome to the administrative control panel.";    //more privileged code here... } else {   echo "Access denied"; } ?> 

causes the correct message to come up, as shown in Figure 14-6.

Figure 14-6. Access is correctly denied regardless of the register_globals setting

The legacy of register_globals doesn't stop with data supplied from forms. It's possible to read session variables when register_globals is on. In Example 14-5, $username could also come from other sources, such as GET, which is part of the URL request.

Example 14-5. Sessions with register_globals on or off in session_test.php

 <?php session_start(); if (isset($username)) {   echo "Hello $username"; } else {   echo "Please login."; } ?> 

Requesting"test" with register_globals on returns what is shown in Figure 14-7.

Figure 14-7. Any security has been effectively circumvented

The correct way is to access the variable from the $_SESSION super global, as in Example 14-6.

Example 14-6. Session using the proper $_SESSION super global

 <?php session_start(); $username=$_SESSION['username']; if (isset($username)) {   echo "Hello $username"; } else {   echo "Please login."; } ?> 

The code in Example 14-6 returns Figure 14-8.

Figure 14-8. Users must log in and cannot bypass the login with a global variable

To continue to access a user-supplied variable without caring about where it came from, you can use the $_REQUEST superglobal, as shown in Example 14-7.

Example 14-7. Detecting simple variable poisoning

 <?php if (isset($_COOKIE['MAGIC_COOKIE'])) {    // MAGIC_COOKIE comes from a cookie.    // Be sure to validate the cookie data! } elseif (isset($_GET['MAGIC_COOKIE']) || isset($_POST['MAGIC_COOKIE'])) {    mail("", "Possible breakin attempt", $_SERVER['REMOTE_ADDR']);    echo "Security violation, admin has been alerted.";    exit; } else {    // MAGIC_COOKIE isn't set through this REQUEST } ?> 

While register_globals is turned off by default to improve security, it doesn't mean that the problem of validating has gone away.

Remember to always initialize variables. This simple step can thwart a malicious attempt to send data through an alternate source. It also helps the readability of your code at almost no cost.

Superglobal arrays such as $_GET, $_POST, and $_SERVER have been available since PHP 4.1.0.

Learning PHP and MySQL
Learning PHP and MySQL
ISBN: 0596101104
EAN: 2147483647
Year: N/A
Pages: 135

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: