Forum Moderators: coopster

Message Too Old, No Replies

Checking a $_POST array came from your domain?

         

Warboss Alex

2:24 pm on Jun 28, 2004 (gmt 0)

10+ Year Member



Hey all,

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

httpwebwitch

4:11 pm on Jun 28, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



If you are controlling the form doing the POSTing, then you can certainly identify whether the POST is a fake or not. Along with your other POST stuff, send an encrypted hash or checksum. Mix in something that will change from user to user over time, like the USER_AGENT, IP address, or the current date.

// 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.

Warboss Alex

5:29 pm on Jun 28, 2004 (gmt 0)

10+ Year Member



I thought of something like that, actually man, having a secret key or something, but surely someone'd be able to just copy and paste that from the form on my site to the form on their site, and it'd still be valid, right?

Or is there a way around this I'm not seeing?

chadmg

6:28 pm on Jun 28, 2004 (gmt 0)

10+ Year Member



They could indeed just copy that hidden variable info down. And if someone is determined enough to spoof their referrer, thay would probably know enough to get around that.

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.

Warboss Alex

6:40 pm on Jun 28, 2004 (gmt 0)

10+ Year Member



Secure my forms? How d'you mean?

This is just conversational, I hardly think anyone's gonna try and break into my website.. but it's interesting to see how people get round this! :)

httpwebwitch

6:46 pm on Jun 28, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



The key to success of a good checksum is making an algorithm that can't be forged, changes constantly, and hasn't been pre-used.

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!

chadmg

7:15 pm on Jun 28, 2004 (gmt 0)

10+ Year Member



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?

mykel79

8:23 am on Jun 29, 2004 (gmt 0)

10+ Year Member



Another method that's pretty popular is a code a user has to type in. The code is written on a picture (and changes for each form), making it impossible (very hard) for an automatic form submitting program to read and enter. The same code is stored in the session. If the code typed in by the user matches the code in the session, then the form is accepted.

WhosAWhata

7:00 pm on Jun 29, 2004 (gmt 0)

10+ Year Member



don't PHP JavaScript and .htaccess files all contain methods that allow a script to be executed iff it is called by a specific page?

how can one get around that?

mykel79

7:53 am on Jun 30, 2004 (gmt 0)

10+ Year Member



You can get around that fairly easily. The referral data (the page from which the form was called) is passed by the browser. With some software or even a php script pretending to be a browser, you can fake that data and send any referral you want.

jamie

8:35 am on Jun 30, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



httpwebwitch

just out of curiosity:

your method still wouldn't stop someone accessing the page, viewing source and copying the form, including the hash, onto his own server and submitting the form from there?

or did i not understand that correctly :)

interesting dicsussion!

Warboss Alex

11:36 am on Jun 30, 2004 (gmt 0)

10+ Year Member



The hash wouldn't be available in the page (html) source Jamie.

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

Warboss Alex

11:49 am on Jun 30, 2004 (gmt 0)

10+ Year Member



Here's something else though.

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

httpwebwitch

3:16 pm on Jun 30, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



You're right - my method assumes that the user will have the same IP address from one page to the next. If AOL users are affilicted with a transitory IP, they would never be allowed to submit the form successfully. The strategy I described could easily be done without adding the IP address as an ingredient.

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]

httpwebwitch

3:19 pm on Jun 30, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



FYI-
I should mention that the script I described prevents someone from stealing your form and reusing the hash. It does not prevent automated or repeated submission, since someone can just press "refresh" on their browser and be given a new form with a new hash.

Warboss Alex

3:20 pm on Jun 30, 2004 (gmt 0)

10+ Year Member



I thought you were doing the hash as a session variable.. Hrm. Why'd I get that idea.

Warboss Alex

3:23 pm on Jun 30, 2004 (gmt 0)

10+ Year Member



It's amazing the number of sites you can log into from a form on your own computer. Any phpBB forum for instance. All you need is the session variable (hashed as a hidden <form> field..) ..

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

Mr_Brutal

3:23 pm on Jun 30, 2004 (gmt 0)

10+ Year Member



Hi all,

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

httpwebwitch

3:49 pm on Jun 30, 2004 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Good point. You can combine the use of sessions and checksums; if you store the checksum as a session variable, it would prove that the form was generated on your server, because only your server would have a value stored for that session. It's unlikely that someone could forge a fake (yet valid) session ID.

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]

Warboss Alex

4:11 pm on Jun 30, 2004 (gmt 0)

10+ Year Member



Yeah, I thought we were using $_SESSION all along! Since putting the hash in the form defeats the object, heh.

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.