Forum Moderators: coopster
How do I check that a submission in the $_POST array came from my own server? That is to say, someone from another server (or their own hard disk) isn't making a copy of my submission form and submitting it from there?
I know about $HTTP_REFERER but that can be spoofed.. Anyone know a decent way?
Alex ...
// mix your own ingredients
$myserver=$_SERVER['HOST_NAME'];
$mysecretword="gorgonzola";
$useragent=$_SERVER['USER_AGENT'];
$yearmonth=date("Y-m",mktime());
$string=$yearmonth.$mysecretword.$useragent.$myserver;
$hash= MD5($string);
echo "<input type='hidden' name='ch' value='".$hash."'";
Then when the POST arrives, pick the hash it apart to make sure it's valid.
$myserver=$_SERVER['HOST_NAME'];
$mysecretword="gorgonzola";
$useragent=$_SERVER['USER_AGENT'];
$yearmonth=date("Y-m",mktime());
$string=$yearmonth.$mysecretword.$useragent.$myserver;
if ($_POST['ch']==MD5($string)){
// form is valid
}else{
// form is invalid
}
This doesn't make your form secure... but it will prevent the most obvious hacking attempts. Choose the ingredients for your checksum so that it will be difficult for someone else to produce valid checksums, but can be easily verified by the receiving script.
You could set a session variable on the form page. Then on the page the form data is posted to, check for that session variable before your process the form. They would need to have a basic level of cookie permissions, but maybe that is acceptable to you. But there is no way they could set session variables on your server.
Also, try and secure your forms enough that you don't need to protect against this. Maybe there is another solution to your problem, via redoing your form.
If you are mainly concerned about forms being re-submitted or forged and submitted from elsewhere, a databasing strategy might do it nicely:
1) create a database with an autoincrementing ID number and space to store the user's IP address and a binary bit. The structure is this:
ID [autoincrementing integer, primary key]
IP [varchar,15]
used [tinyint,1] [default:0]
2) at the page where your form is displayed, store the user's IP in the database. Retrieve the incrementing ID and create an MD5 hash out of those two pieces of data concatenated together, plus a secret key word.
$hash=MD5($IPaddress.$uniqueID.$secretword);
Then what you will have is a good encrypted hash which represents one documented instance of the form, which has a non-repeating integer mixed in. Thus every checksum you create will be different - completely non-forgeable!
Then when the POST is received, grab the user's IP. We assume here that the user won't change IP addresses in mid-session.
Look in the database for any records that match the IP. If you don't find any, then the POST is fake.
Recreate the hash. If it doesn't match, then the POST is fake. (** if the SELECT returned more than one record for that IP, you check each one in turn and see if any of them match the hash POSTed in the form.)
Check if the database has "seen" this form before - look at the "used" field, and if it's 0 (false), then all is well and go ahead and accept the POST. It is valid, it is unique, and it is fresh.
Since that instance of the form is valid, UPDATE your database and set the "used" field to "1" (true). That way, any subsequent forms using the same hash will fail. (which would be the case if someone saved the HTML and tried to reuse a previously-generated checksum)
Only a valid hash will be able to match both the IP address and the unique ID number created for it, and because you added a secret word, it is impossible for a hacker to guess your method and reverse-engineer the checksum. The "used" field prevents spamming or automated submission. The checksum changes every time the form is displayed, thus no one can forge it or reuse it.
And of course on your pages you must show the following text:
"I got advice from httpwebwitch. What a swell guy!"
Or else none of this will work.
Good luck!
Secure my forms? How d'you mean?
I mean if you have a form that can be hacked by altering the page, then you should try and redo your form. Say, for example, you are doing something silly like putting a member name in a hidden form field and the form submits a new post to a forum. So if someone could change that hidden form field he could post with whatever name he pleases. I'm suggesting that you change your methods so that wouldn't be possible. You could store the member name in a session variable instead. Do you follow?
httpwebwitch's method seems to be pretty sound, just involves database queries. Mind you, all the best things do, sooo .. heh. ;)
I'm probably going to try and implement that method at some point.
At first I was worried about the database table getting too big, but since it'd keep assigning new id numbers, there shouldn't be any problem in clearing the entries every week or so, with a cron job or whatever.
Cheers,
Alex ...
I don't know how true this is, but I've been told that AOL users change IP with every new page request. Wouldn't that make this script always fail?
They'd have one IP on the form page, and another on the processing page? In my case the form page also processes the $_POST data but it's still a new page request in effect ..
What happens there?
Alex ...
Re: another concern stated above, YES, the hash *is* visible in the source code. If you look at the <form> you'll see the hidden <input> in there containing the hash. But the point is that each hash can only be used once (the database keeps track of that), so it's pointless for someone to cut and paste a form to reuse it somewhere else.
I'm trying to find a good metaphor for this so it's easier to understand...
The hash is like a "key" that unlocks a door. You can make a million keys that fit the lock, but each key can only be used once, then the lock alters itself so it will never accept that key again. So, you give that key to someone, and they use it. After that, who cares if the key is thrown away or given to someone else? They can't use it, so your lock is as secure as ever. And - no one but you can make the keys. See?
[edited by: httpwebwitch at 3:20 pm (utc) on June 30, 2004]
I suppose if you've got decent data validation it doesn't matter where you get stuff sent from. But it seems wrong to let people do that..
This is an very interesting thread and although i don't have alot of hands on knowledge - would using a Time Stamp in the hash code and storing this in the database help.
It would then be possible to check the Time the hash was created when the IP addresses don't match - if it was only a very short time ago then you could choose to risk letting the form be submitted, and then mark the "key" used as normal.
Just a thought :)
page 1
------
session_start()
$hash=yaddayadda
$_SESSION['ch']=$hash
// print your form
// but don't put the $hash in it
page 2
------
session_start()
$hash=$_SESSION['ch']
if (hash_is_valid($hash)){
// accept the POST
}
Do I need to mention that register_globals should be off?
This is also a case where obfuscating your HTML could be effective. There are some great tools out there for creating completely unreadable javascripts that output solid HTML. Here's an example [ioncube.com]
I'm just a bit wary about $_SESSION. I've had a bit of trouble with it in the past, that and I'm trying to write a class to handle sessions (writing them to a db), but not having much luck. In fact, I was so pissed off I've given up, 'least for the time being. All my Session class does now is provide an interface to access $_SESSION so when I -do- a proper db session class I can change it easily .. [wandering off topic here..] ..
Anyway. In theory, since $_SESSION is invisible to end users, you don't really need a complicated hash which no-one'll guess 'cause they won't be able to access it anyway.