Form Spoofing

Published in PHP Architect on 22 Jul 2004

Welcome to another edition of Security Corner. This month's topic is form spoofing, a technique mentioned briefly in the column on input filtering. As a PHP developer, you have most likely written code to handle HTML forms. If you have been reading this column, you also know that you should filter all input on the server. This article explains why, by detailing some common methods used to spoof form submissions.

HTML Forms

Form handling is very convenient with PHP, even when register_globals is disabled. Consider the following HTML form:

  1. <form action="/process.php" method="POST">
  2. <p>Day</p>
  3. <select name="day">
  4.    <option value="Mon">Monday</option>
  5.    <option value="Tue">Tuesday</option>
  6.    <option value="Wed">Wednesday</option>
  7.    <option value="Thu">Thursday</option>
  8.    <option value="Fri">Friday</option>
  9.    <option value="Sat">Saturday</option>
  10. </select>
  11. <p>Year</p>
  12. <select name="year">
  13.    <option value="2004">2004</option>
  14.    <option value="2005">2005</option>
  15. </select>
  16. <input type="submit" />
  17. </form>

In the receiving script, process.php, the user's selections for the day and year are available in $_POST['day'] and $_POST['year'], respectively. An easy mistake is to assume that these variables can only contain the values provided in the form (for example, that $_POST['year'] can only be 2004 or 2005). Even if you are not guilty of making this erroneous assumption, you may not realize just how easy it is for a user to spoof a form and send arbitrary data as the values of the form variables.

Simple Form Spoofing

The simplest and most common method used to spoof a form is to simply reproduce the HTML and use a standard browser to send the request. With a simple modification to the action attribute of the <form> tag, you can create an identical form that can reside anywhere and still submit to the same receiving script. For example, assuming the previous example represents a form located at http://example.org/form.html, you can reproduce the form:

  1. <form action="http://example.org/process.php" method="POST">

Instead of the relative URL in the action attribute, you use an absolute URL. This allows the form to reside anywhere. In most cases, no other modification is necessary. I encourage you to try this on a few forms with which you are already familiar. As an example, you can use the following HTML to recreate Google's search form:

  1. <form action="http://google.com/search">
  2. <input type="hidden" name="hl" value="en">
  3. <input type="hidden" name="ie" value="UTF-8">
  4. <input type="text" name="q">
  5. <input type="submit" name="btnG" value="Google Search">
  6. <input type="submit" name="btnG" value="I'm Feeling Lucky">
  7. </form>

Once you are able to successfully submit the form, you can then modify it to remove any client-side restrictions. For example, you can modify the first example as follows:

  1. <form action="http://example.org/process.php" method="POST">
  2. <p>Day</p>
  3. <select name="day">
  4.    <option value="Sec">Security Corner</option>
  5. </select>
  6. <p>Year</p>
  7. <input type="text" name="year" />
  8. <input type="submit" />
  9. </form>

This form provides a convenient mechanism with which to launch various attacks. With nothing more than a typical browser, you can now send an unexpected value for the day, and you can enter anything you want for the year. You are no longer as restricted.

Hopefully it is clear that very little effort is required for this technique. In most cases, it is sufficient to view the source of a page, save it to a local file, and modify the action attribute of the form tag to specify an absolute URL. Once you have tested your fake form to ensure that it works, you're free to make whatever modifications you want, and you can then bypass any attempt at client-side security.

HTTP

The method attribute of the form tag has two possible values, GET and POST. If any other value is specified, or if the method attribute is omitted, the GET method is assumed. This indicates the request method to be used by the browser when it submits the form. A form submission is no different than any other HTTP request except that it includes the form data provided by the user. How this form data is included depends upon the request method being used.

When the GET method is used, the form data is included in the query string of the URL being requested. The following illustrates a sample GET request that includes two form variables, day and year:

  1. GET /process.php?day=Mon&year=2004 HTTP/1.1
  2. Host: example.org

The first line of the request has three pieces of data separated by spaces: the request method, the relative URL being requested, and the version of HTTP being used. In this case, the URL includes the form data in the query string. The query string is separated from the rest of the URL by a question mark, and the name / value pairs (formatted as name=value) are delimited from each other by ampersands.

If the request method is POST, this same form submission becomes the following:

  1. POST /process.php HTTP/1.1
  2. Host: example.org
  3. Content-Type: application/x-www-form-urlencoded
  4. Content-Length: 17
  5.  
  6. day=Mon&year=2004

The form data is now being sent in the content section of the request, and there are two extra HTTP headers that describe this content. The format of the POST data is identical to the format of the GET data. Only its location within the request is different.

Advanced Form Spoofing

With a basic understanding of how HTTP requests are formatted, we can now communicate with a web server manually. This is a bit more advanced than the previous examples of form spoofing, but it provides the ultimate in flexibility.

To practice manually sending an HTTP request, start with a basic HEAD request that includes no form data. HEAD behaves exactly like GET except that the content (HTML) in the response is suppressed, so that your screen doesn't get filled with distracting information. An example HEAD request is as follows:

  1. HEAD / HTTP/1.1
  2. Host: example.org

The telnet utility provides a convenient way to establish a TCP/IP connection to the web server, and the following example illustrates connecting to example.org on port 80 and sending the example HEAD request:

  1. $ telnet example.org 80
  2. Trying 192.0.34.166...
  3. Connected to example.com.
  4. Escape character is '^]'.
  5. HEAD / HTTP/1.1
  6. Host: example.org
  7.  
  8. HTTP/1.1 200 OK
  9. Date: Thu, 15 Jul 2004 12:34:56 GMT
  10. Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
  11. Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
  12. ETag: "3f80f-1b6-3e1cb03b"
  13. Accept-Ranges: bytes
  14. Content-Length: 438
  15. Connection: close
  16. Content-Type: text/html
  17.  
  18. Connection closed by foreign host.
  19. $

With this technique, you can now send your own HTTP requests. This is less convenient than using a browser, of course, but it is far more flexible.

It is also possible to write a PHP script to do this communication for you (instead of using telnet). The following example will send the same HEAD request to example.org:

  1. <?php
  2.  
  3. $http_response = '';
  4. $fp = fsockopen('example.org', 80);
  5.  
  6. fputs($fp, "HEAD / HTTP/1.1\r\n");
  7. fputs($fp, "Host: example.org\r\n\r\n");
  8.  
  9. while (!feof($fp)) {
  10.      $http_response .= fgets($fp, 1024);
  11. }
  12.  
  13. fclose($fp);
  14.  
  15. echo $http_response;
  16.  
  17. ?>

You can use this approach to help automate some of your tests.

Until Next Time...

The most important point to retain from all of this is that the form data sent by a user can truly be anything, regardless of the interface or any client-side technology. This is one reason why input filtering is such an essential part of web application security.

The real danger is that the vast majority of your users will interact with your applications exactly as you expect. This can potentially leave some situations untested, and this is how many security vulnerabilities are born.

If you don't make assumptions about the data being sent in a form, and if you adhere to the practices described in the previous column on input filtering, you will be much less likely to make such mistakes. You should now better understand why these practices are so important.

Until next month, be safe.