About the Author

Chris Shiflett

Hi, I’m Chris: web craftsman, community leader, husband, father, and partner at Fictive Kin.


Form Spoofing

  • Published in PHP Architect on 22 Jul 2004
  • Last Updated 22 Jul 2004
  • 10 comments

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:

<form action="/process.php" method="POST">
<p>Day</p>
<select name="day">
   <option value="Mon">Monday</option>
   <option value="Tue">Tuesday</option>
   <option value="Wed">Wednesday</option>
   <option value="Thu">Thursday</option>
   <option value="Fri">Friday</option>
   <option value="Sat">Saturday</option>
</select>
<p>Year</p>
<select name="year">
   <option value="2004">2004</option>
   <option value="2005">2005</option>
</select>
<input type="submit" />
</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:

<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:

<form action="http://google.com/search">
<input type="hidden" name="hl" value="en">
<input type="hidden" name="ie" value="UTF-8">
<input type="text" name="q">
<input type="submit" name="btnG" value="Google Search">
<input type="submit" name="btnG" value="I'm Feeling Lucky">
</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:

<form action="http://example.org/process.php" method="POST">
<p>Day</p>
<select name="day">
   <option value="Sec">Security Corner</option>
</select>
<p>Year</p>
<input type="text" name="year" />
<input type="submit" /> 
</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:

GET /process.php?day=Mon&year=2004 HTTP/1.1
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:

POST /process.php HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
 
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:

HEAD / HTTP/1.1
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:

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

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:

<?php
 
$http_response = '';
$fp = fsockopen('example.org', 80);
 
fputs($fp, "HEAD / HTTP/1.1\r\n");
fputs($fp, "Host: example.org\r\n\r\n");
 
while (!feof($fp)) {
     $http_response .= fgets($fp, 1024);
}
 
fclose($fp);
 
echo $http_response;
 
?>

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.

About this article

Form Spoofing was last updated on 22 Jul 2004. Follow me on Twitter.

10 comments

1.chr wrote:

Great example, i really understand how someone can spoofing form, actually i only knew form technique but not bye telnet.

Thanks, your site is really helpfull

Mon, 31 Oct 2005 at 15:02:33 GMT Link


2.Alex wrote:

Really great article, it helped me very much securing my own forms. Thanks!

Thu, 15 Dec 2005 at 19:43:16 GMT Link


3.ruger wrote:

I understand how someone could spoof a form now. But what's the other side of the operation? Is there anything in the HTTP POST that would tell the server where the form submission is coming from? like say I have a login page for my php web app and I want to make sure that people are ONLY logging in via my page. Is there information in the HTTP POST submission that could be used to track this?

Mon, 09 Jan 2006 at 21:32:46 GMT Link


4.Mladen Mihajlovic wrote:

There is referrer, but it can also be spoofed.

Wed, 22 Mar 2006 at 05:30:43 GMT Link


5.Mike wrote:

I have read your article and have found it to be very useful in creating a secure form. However, one thing that I believe you left out was how to filter data that comes from something that is unpredictable, such as a comment textarea, for example. I have been struggling to figure out an effecient way in which to filter data where you do not expect a specific response from the user. Any help would be appreciated.

Thu, 08 Jun 2006 at 22:20:03 GMT Link


6.Nate Klaiber wrote:

RE: Mike

I think with comment forms, the best thing would be to restrict to certain tags - and then filter out the others. Some formatting is nice for people to have - but not necessary.

In a way, you are expeciting a specific response. It can be their response, but they cannot add malicious code. Accept limited tags then refuse/strip the rest (unless you want to post code, then there is a little more to it...).

I guess it all boils down to your exact need of your forms and textarea field.

Mon, 12 Jun 2006 at 19:15:24 GMT Link


7.hjb wrote:

Although the last comment on this thread was a long time ago, I think my addition is worth it.

For forms that are placed on your own site and post back to your own site you can use something along the lines of a magic_cookie to ensure submissions are genuine.

When the form page is requested use server side code to create a random string, put it in the database with a timestamp and a page url and then place it in a hidden field in the form you are securing.

<input type="hidden" name="magic_cookie" value="lvig0d52bbw73n2v6">

An example magic_cookie would be 'lvig0d52bbw73n2v6'

When the page receives a form submission you can again use server side code to get the magic_cookie, check for it in the database and ensure it was created less than x minutes ago, it has not already been used and it was created on the same page as http_referer

Not fool proof but pretty good for stopping the casually script kid.

Sun, 12 Nov 2006 at 13:13:39 GMT Link


8.Chris Shiflett wrote:

Good comments are always welcome. :-)

I like your approach, and it's something I cover in another article:

http://shiflett.org/articles/security-corner-dec2004

I don't call it a magic cookie, but it's the same idea. (Calling it a cookie can confuse people, because it's not a cookie at all.)

Mon, 13 Nov 2006 at 05:36:21 GMT Link


9.Stock Market Today wrote:

Requiring users to re-authenticate themselves more often.

192.168.l.l

Sun, 12 Jun 2016 at 07:51:10 GMT Link


10.Alexa wrote:

Awesome blog. I enjoyed reading your articles. This is truly a great read for me. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work!

192.168.1.1

Thu, 23 Jun 2016 at 17:30:40 GMT Link


Hello! What’s your name?

Want to comment? Please connect with Twitter to join the discussion.