A key Role in the Initial Growth of the World Wide Web

Apache Web Server Journal

Subscribe to Apache Web Server Journal: eMailAlertsEmail Alerts newslettersWeekly Newsletters
Get Apache Web Server Journal: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Apache Web Server Authors: Liz McMillan, Elizabeth White, Pat Romanski, Janakiram MSV, Gil Allouche

Related Topics: Apache Web Server Journal, MySQL Journal, PHP Developer's Journal

Apache Web Server: Article

More stupid PHP tricks

How to lock-down PHP-Nuke, make PHP a bit safer, & stop bad guys from touching MySQL.

(LinuxWorld) -- If you follow the latest security bulletins, you noticed someone discovered a huge security hole in the PHP-based weblog software PHP-Nuke. I mention this because I've been learning PHP the hard way by modifying the PHP-Nuke source code, since my two non-profit sites VarLinux.org and Petreley.org are based on PHP-Nuke. I used the 4.4.1a version of the source code, but it looks to me like the security hole may still exist in the latest version.

The problem exists in the admin.php file. This part of the program authenticates you as an administrator before it lets you run most privileged functions. There's a snippet of code outside of the security check that lets you upload a file. If you're clever enough to form the proper URL, you can trick PHP-Nuke into uploading its own PHP-Nuke configuration file as a text file. Then you can view the text file to find the password for the PHP-Nuke database.

The example used by the person who discovered this hole converts the configuration file into a file called hacked.txt. This wouldn't have worked on my system, since my Apache configuration would redirect a request for a .txt file to the home page. That's a minor detail -- you could rewrite the crack to create a file called hacked.gif instead, which is a file my installation of Apache would allow you to view.

There are a number of ways to fix this hole. The simplest is to move the code inside of the portion of the file that checks to see if you're an administrator before it allows you to do anything. But I ripped out the code, along with the other file manager functions within PHP-Nuke. I never use them, anyway.

Security tricks

Which brings me to my next stupid PHP tricks. One of PHP's biggest weaknesses is its use of global variables. Or, I should say, one of the ways you can invite security problems is if you enable and use global variables in PHP. You can turn off the capability altogether before you even start a project, thus protecting yourself from your own folly. The problem is most existing PHP code, including PHP-Nuke, relies on global variables. I would have to invest a massive amount of effort rewriting the PHP-Nuke code to get to a point where I could turn off the use of global variables.

For example, combine global variables with HTTP forms or cookies, and you've got a recipe for disaster. For example, PHP detects and addresses cookies automatically. PHP-Nuke sets an administrator cookie using the name admin. Once the cookie exists, PHP can access the contents of the cookie automatically as the variable or array called $admin.

Assuming only a true administrator can log in as an administrator, the only time this cookie should exist is if the user has been validated. This means you should be able to write this following simple bit of code to check to see if the user is an administrator (obviously, the "Do administrator stuff" is not real PHP code):

if (isset($admin)) {
  (Do administrator stuff)
}

If the cookie exists, then the variable $admin will be set. If the cookie doesn't exist, it won't be set.

Unfortunately, all a cracker has to do to get past this check is to append the string admin=1 to any URL on the site. PHP sees this in the URL string and sets a global variable called $admin to the value 1. That's all it takes to get past the so-called security check above.

I've implemented a solution that, while not infallible, still leans toward paranoia. It is an extension of the way PHP-Nuke works normally. (The PHP-Nuke code may have had that outrageous hole above, but it didn't do the simple $admin check in my example above.) PHP-Nuke stores the administrator ID number and password as part of the $admin cookie, so my code checks the password in the cookie against the one in the database every time you try to access an administrator task. In addition, it also checks your IP address against a list of acceptable IP addresses. Here's a simplified version of the code:

$admintest = 0;
if (isset($admin)) {
   $admincheck = base64_decode($admin);
   $admincookie = explode(":", $admincheck);
   $aid = "$admincookie[0]";
   $pwd = "$admincookie[1]";
   if ($aid=="" || $pwd=="") {
      setcookie("admin", "", time() - 3600);
      unset($admin);
   } else {
      $result=mysql_query("select pwd from admin where aid='$aid'");
      if(!$result) {
         setcookie("admin", "", time() - 3600);
         unset($admin);
      } else {
         list($pass)=mysql_fetch_row($result);
         mysql_free_result($result);
         if($pass != $pwd) {
            setcookie("admin", "", time() - 3600);
            unset($admin);
         } else {
            $ip = getenv("REMOTE_ADDR");
             $ahost_result = mysql_query("select ipaddress from adminhosts where aid='$aid'");
             if ($ahost_result) {
                while (list($ipaddress) = mysql_fetch_row($ahost_result)) {
                    if ($ip == $ipaddress) {
                       $admintest = 1;
                       break;
                    }
              }
              mysql_free_result($ahost_result);
              if (! $admintest ) {
                 setcookie("admin", "", time() - 3600);
                 unset($admin);
               }
            } else {
               setcookie("admin", "", time() - 3600);
               unset($admin);
          }
       }
   }
}

See what I mean about paranoia? It even cancels the cookie if the validation fails. The key here is I set the variable $admintest to 0 at the beginning. That way no one can override the check by adding the string admintest=1 to a URL.

The .php exploit

Another way to crack a PHP-based site, especially an open-source one, is to examine all the PHP files to see if the author left open any means of dynamically creating or changing a file, or even dynamically generating a database query. Then all you have to do is figure out how to form a URL to access that PHP file with your own variables. For example, suppose you create a PHP file called crackme.php containing the following:

<?php
mysql_pconnect($dbhost, $dbuname, $dbpass);
mysql_select_db("$dbname");
mysql_query($query);
?>

On the surface, it looks like it serves no useful purpose because we haven't set any of the variables the file needs. People create files like this because they function fine as generic subroutines. All you have to do is include this kind of file in another PHP file that set the proper variables, first.

Unfortunately, this practice opens a huge security hole for crackers. All a cracker has to do is create a URL such as the following: http://somesite/crackme.php?dbhost=databasehost&dbuname=username... etc. That way the cracker executes the file directly with whatever variable settings he or she wants.

One way to avoid this kind of problem is to seal up all your PHP code within PHP functions and make sure your code calls those functions properly. But it always helps to put something like the following at the beginning of every PHP file that you create to include within another PHP file, but don't want anyone to execute on its own. In the case of the crackme.php file, you would put this line at the top:

if (eregi("crackme.php", $PHP_SELF)) { die }

This checks the name of the PHP file currently executing. If it is crackme.php, then the program dies before it gets to the database code, because you didn't intend for anyone to execute this file on its own. If it is included in some other PHP file, then the $PHP_SELF variable will be something other than crackme.php, and the program will continue to execute.

This is the way PHP-Nuke handles all of the separate administrator files. One master file, admin.php, includes several other PHP files that manage stories, links, polls, and so on, such as stories.php, links.php, polls.php. Each of the separate PHP files assigned to these tasks checks first to make sure they're included within admin.php, otherwise they refuse to work.

MySQL, not yours

Finally, assuming you've nailed down your PHP code such that nobody can execute their own database queries on your system, you can also make sure nobody can access your MySQL database even if they do get the proper username and password.

If you use MySQL network clients on your local network but don't need to access MySQL from the Internet, close the port MySQL uses, port 3306, at your firewall. If you use MySQL for your Web site alone, and you have both MySQL and Apache running on the same machine, then you don't need the network client capability. Turn off all network access in MySQL itself. This is the default configuration on Debian systems, but you can do this on any system by including the string skip-networking under the [mysqld] section in your my.cnf MySQL configuration file. Or, you can use the --skip-networking command-line switch when you start MySQL.

By the way, the VarLinux.org code is GPL and you can download all of the code from the Downloads section of VarLinux.org. I'll warn you, though, that although you may learn something by studying it (even if what you learn is I'm often a lousy programmer), I cannot support the code, and I do not recommend you try to use my code in its current state as an alternative to PHP-Nuke to set up your own site. I made a number of changes to the database structure, but not in a way to install it for generic use. The only way to use the code now is to reverse engineer it to figure out how to create and populate the proper database tables, and that's not easy.

If you want to start a similar site, I recommend instead you start with PHP-Nuke 5.2 yourself, or one of the other offshoots of the PHP-Nuke code such as Postnuke. If you want to sample the many alternatives, visit PHP.net or Freshmeat and search for "weblog", or "blog."

More Stories By Nicholas Petreley

Nicholas Petreley is a computer consultant and author in Asheville, NC.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Al McNicoll 03/20/05 04:41:54 AM EST

If you're worried about PHP's global variables, just check for $admin using $_COOKIES["admin"] which cannot be set by the URL ?admin=1.