An article from PHP Security consorium.
Almost every website has an HTML form for visitors to complete. But how do you know that the person who completed the form did so through your website? That is, how do you make sure that no one has 'spoofed', i.e., 'forged', a form submission?
SPOOFING SUBMISSIONS & FORGING REQUESTS
METHOD 1: Spoofing Submissions
How can you "spoof" a form submission?
Let's assume you have the following HTML form that is located at http://yourdomain.org/form.php
<form action="/submit.php" method="post">
It would seem safe to assume that you will be able to reference $_POST['myvar'] and it will have a value of either "foo" or "bar." Assuming the user selects "foo," the request will look something like this:
POST /submit.php HTTP/1.1
Now let's assume that someone does the following:
1. Saves your form from their browser to their desktop.
2. Opens the "saved" HTML file and makes the following alterations:
1. Modifies the "action" tag so that it now contains the full URL to the form.
2. Removes the "select" tag and replaces it with a "textarea" tag.
This person would then have a form that looks like the following:
<form action="http://yourdomain.org/submit.php" method="post">
With this simple form, this person can now submit anything as the value of $_POST['myvar']. In fact, there was nothing to prevent the person who manipulated your form from including unexpected form variables or anything that can be achieved with an HTML form.
METHOD 2: Forging HTTP Requests
More sophisticated attacks might just use raw HTTP rather than bothering to generate or modify a form for every desired type of attack. You can telnet to port 80 and type your requests. Below is a sample request for logging into the NYPHP forum.
POST /index.php?act=Login&CODE=01&CookieDate=1 HTTP/1.1
To use these raw requests, you would need to change three items.
1. Change "myname" to be your "username."
2. Change "mypass" to be your "password."
3. Change "session_id" to the necessary value.
4. Finally, change the the "Content-Length" to be the new length of the POST data.
A more advanced form of attack leaves out the typing and uses specific software written to automate attacks.
Since, from a strict protocol perspective, the only thing you know is that HTTP requests and responses are going back and forth, there is no definitive way to know that a form submission has not been "spoofed". Therefore, our recommended "best practice" reduces the possibility of a spoofed form submission by relying on the use of a "shared secret" and, reducing the possiblity of unwanted values being submitted by outlining a general architecture for handling data and forms. (See Note 1)
A) SHARED SECRETS:
Also referred to as one-time tokens or hashes, the idea is typically the same. You create a secret that is intended to only be known by the server and the legitimate user. Implementations vary widely but they share the characteristics of being transparent to your users and difficult to exploit.
One implementation would be to store the secret in the user's session:
$secret = md5(time());
$_SESSION['secret'] = $secret;
This would then be used as a hidden form variable in the form:
<input type="hidden" name="secret" value="<? echo $secret; ?>" />
Every time you display the form, you would regenerate this secret, so that the user always has a current, fresh, and correct secret.
The receiving page can check this by comparing the "secret" sent by the form with the "secret" that was stored in the corresponding session variable. You can improve the security of this method by restricting the timeout window rather than relying on the session timeout, which might be too large for your needs.
B) KNOWING WHAT TO EXPECT:
A solid "architecture" would assure that:
1. You know what you are sending out
Keep track of forms you've put on your site and develop a policy for accepting form submittals (for instance time outs, multiple forms per user id, multiple submissions, not accepting forms you don't expect, etc). This can be implemented using tokens.(See Note 2)
2. You know what you should be getting back (see Note 3)
This is crucial. Just because a <select> field contains certain values, don't think you can't get back something totally different such as as PHP code, SQL, etc.
1. know the fields you need to have back to accept the form as valid
2. restrict exactly what values you'd accept as input
3. always minimize taking data from forms (or any external source) and using it directly in database queries, or other inner and intimate parts of your application.
1 - A bad practice is the use of "generic" form handling scripts that rely on hidden form values to determine what form fields may be required, etc. Any user can, using the "Spoofed Form" technique noted earlier, manipulate the submitted values.
2 - Some have suggested the use of a CAPTCHA as a method to eliminate spoofed form submissions. Unfortunately it does not eliminate spoofed form submissions. The original intent of the use of CAPTCHAs was to determine if a human being, not a webbot, has filled out a form. However, CAPTCHAs can be of use in reducing the possibility of a website being flooded with form submissions since it effectively slows down the rate at which those forms can be submitted.
Nonetheless, CAPTCHAs - especially those that rely on letter and numbers - can be circumvented. One technique utilizes a form of OCR using something like GOCR (see: http://jocr.sourceforge.net/
To learn more about CAPTCHAs, see http://www.captcha.net/
. See also, on captcha.net, the section that describes the use of pictures instead of text.
3- Be sure you know which variables contain the values you want and whether you are inadvertently relying on register_globals. Though as of PHP 4.2.0 this particular ini setting is "OFF" by default, nonetheless, it is still common for this setting to be "ON" in shared server environments (some open source applications require register_globals to be on). It's considered "best practice" to write your code with the assumption that register_globals is off. For more information, see http://php.net/manual/security.globals.php
More information found at http://www.phpsec.org