Page 1 of 3

Protecting your site from form tampering....

Posted: Tue Jul 26, 2005 9:47 am
by nielsene
Here's a problem, with a few possible solutions. Which one(s) do you like and use?

Problem: Lets say you have a series of Submit buttons on a web page, maybe "Delete This!" for each of several things. The id of the thing to delete is carried in a hidden form element. How do you protect from malicious users "forging" a submission back to you with an non-orginally listed id?

Solution 1: Sign the ID's. Change the ID to "$id"."+".HMAC($id). Explode the submitted value, recompute the HMAC and compare, throwing away forgeries.
Pros: Cryptographically more secure. If the business logic is hard to validate on the processing page, but easy on the list choices page, this works. Doesn't require sticking anything in server side storage. Visible security to casual attackers.
Cons: Adds processing overhead, possibly display of several cleartext, hash pair could allow easier brute forcing of server's signing key, therefore need to rotate keys often and deal with hash expiration, etc. Visible security might scare/confuse normal, but paranoid users.

Solution 2: Submit-side verification. For "things" that should come from an expected possible list, stick said list into a session variable or recompute legal list on the submit script. Test input against list.
Pros: Fast, possibly O(1) verification. Still safe. Transparent to the user. No added hash key manangement complexity -- therefore less likely to be buggy, etc
Cons: Doesn't "advertise" security to casual inspection, requires saving/recomputing the list of options -- if this list is large (memory) that might become inconvienent, if the list takes a long timeto generate, ditto.

I've been using "Solution 2" for a long time, but I've been thinking I should be using "Solution 1" or both. However I'm now less sure that "Solution 1" would truly add any security -- assuming the "Solution 2" checks are tight enough. There are a few places where I know I need to use the hash, but for a while I was thinking it should be used all the time.... now I'm not so sure.

Posted: Tue Jul 26, 2005 10:15 am
by josh
I don't see why the "problem" you listed would ever occur,
say for instance users can only edit posts they made themselves and you have an edit button that passes the id to edit.php, on edit.php simply make sure the current user is also the original author.

I just don't get why you even need to protect against something like this in the first place, but if you really want to solve your problem you could probably take sha1(uniqid()), take that value and store it in the database with your item, send that hash instead of the id, then when the button is pressed, look up that hash again. If you do something like that a user is less likely to guess an arbitrary hash.

Edit: I guess your "solution 2" is basically what I said, with validating the correct user.. That should always be done no matter what, I would never rely Solly on keeping an id# confidential.

Posted: Tue Jul 26, 2005 10:22 am
by timvw
Imho there are 2 issues that need to be tackled:

(1) try to avoid CSRF (enforce use of your own forms)

Code: Select all

<?php
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token; 
$_SESSION['token_timestamp'] = time(); 
?> 
<form action="update_item.php" method="post"> 
<input type="hidden" name="token" value="<? echo $token; ?>" /> 
<p>Subject: <input type="text" name="post_subject" /></p> 
<p>Message: <textarea name="post_message"></textarea></p> 
<p><input type="submit" value="Update Item" /></p> 
</form>
(2) try not to expose the real primary key / column names

For example http://timvw.madoka.be/demo/person_list.php.

First i retrieve the rows that are available (store them in the session as $_SESSION['rows']).

Then i make a 1 - 1 mapping to my HTML form. For example each row is identified as

Code: Select all

&lt;input class=&quote;checkbox&quote; type=&quote;checkbox&quote; name=&quote;select&#1111;]&quote; value=&quote;1&quote;&gt;
. So no matter what the primary key is, i only use numbers. Same goes for the column ordering..

Now when the person posts his selection, i will look at $_POST['select'] and retrieve all mapped rows from $_SESSION['rows']. Once i have those rows, i filter out the primary key, store that in the session, and forward to the edit/delete/update script...

So if someone tries to use an invalid number.. There is no mapping.. And nothing happens ;)

Posted: Tue Jul 26, 2005 10:26 am
by John Cartwright
Well if users can only modify their own submittions why not do the verification in the query?

Code: Select all

if (!empty($_GET['id'])) {
   $sql = 'DELETE * FROM `submissions` WHERE `submission_id` = \''.(int)$_GET['id'].'\' AND `user_id` = \''.$_SESSION['user_id'].'\' LIMIT 1';

   //...query

   if (mysql_affected_rows() == 0) {
      echo 'No Id Found';
   }
}
else {
   echo 'No id!';
}
Wouldn't really matter if all they could tamper with was their own submissions.
Sorry if I'm off the mark here :wink:

Posted: Tue Jul 26, 2005 10:26 am
by nielsene
jshpro2 wrote: Edit: I guess your "solution 2" is basically what I said, with validating the correct user.. That should always be done no matter what, I would never rely Solly on keeping an id# confidential.
The hash is not to keep the id confidential, but to protect it from tampering. The value of the hidden (or select or radio, etc) would look like

Code: Select all

<input type=&quote;hidden&quote; name=&quote;test&quote; value&quote;=5+adhfuadyf87sd84w75sdyfv&quote; />
The "5" is the value, the gobblygook is the HMAC of 5 using the server's hashing key.

Posted: Tue Jul 26, 2005 10:28 am
by nielsene
timvw wrote:Imho there are 2 issues that need to be tackled:

(1) try to avoid CSRF (enforce use of your own forms)

Code: Select all

<?php
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token; 
$_SESSION['token_timestamp'] = time(); 
?> 
<form action="update_item.php" method="post"> 
<input type="hidden" name="token" value="<? echo $token; ?>" /> 
<p>Subject: <input type="text" name="post_subject" /></p> 
<p>Message: <textarea name="post_message"></textarea></p> 
<p><input type="submit" value="Update Item" /></p> 
</form>
Nothing there stops a user from editting and submitting the form themselves. They can easily screen scrap the $token off the page then draft their own forgery. Nothing, there, could tell the difference between a user who spent some time filling out the form or the user who spent that same time forging it.

(2) try not to expose the real primary key / column names
Agreed, but its not always possible. Compared to the HMAC version, you're trading off CPU load for RAM load. Depending on the size of the array you need to store for your mapping and the server load/usage pattern this may or may not be a viable option.

Posted: Tue Jul 26, 2005 10:32 am
by timvw
Jcart wrote:Wouldn't really matter if all they could tamper with was their own submissions.
Assume that you are logged in in your website.. And you have another window open to surf my site... And my site has the following html: (Bye Bye submission :p)

Code: Select all

<frameset>
<frame src=&quote;http://mysite.example.com&quote;/>
<frame src=&quote;http://yoursite.example.com/delete.php?id=10&quote;/>
</frameset>

Posted: Tue Jul 26, 2005 10:45 am
by timvw
nielsene wrote: Nothing there stops a user from editting and submitting the form themselves. They can easily screen scrap the $token off the page then draft their own forgery. Nothing, there, could tell the difference between a user who spent some time filling out the form or the user who spent that same time forging it.
It allows you to test the user is coming from this page (because he has a valid token) and is not being tricked by a foreign site trying to CSRF..
(2) try not to expose the real primary key / column names
Agreed, but its not always possible. Compared to the HMAC version, you're trading off CPU load for RAM load. Depending on the size of the array you need to store for your mapping and the server load/usage pattern this may or may not be a viable option.[/quote]

Agreed, it's the same as your solution 2.

If you want less memory usage, you perform the query again, and lookup the actual rows.

If you want less CPU usage, you cache the rows, and look them up form there.

Posted: Tue Jul 26, 2005 10:49 am
by nielsene
jshpro2 wrote: Edit: I guess your "solution 2" is basically what I said, with validating the correct user.. That should always be done no matter what, I would never rely Solly on keeping an id# confidential.
Second response to same line :)

While I agree with timvw that you should avoid the exposure of primary keys/column names; if you have to use said key for some reason, then yes a hash would work to transform it to keep it confidential. However I would be more likely to use symmetric key encryption at that point. In psudeo code

Code: Select all

$tamperProof = $value.&quote;+&quote;.HMAC($server_hash_key,$value);
$exposedValue = symmetric_encrypt($tamperProof,$server_secret_key);
(Hash key and secret key must be different! Both would need to be rotated medium frequently). Then on the receiving end you decrypt, check for a "+', explode recompute HMAC. If no '+' then the message was tampered with, if HMAC doesn't match value then message was tampered with, etc

Posted: Tue Jul 26, 2005 10:51 am
by nielsene
timvw wrote:
nielsene wrote: Nothing there stops a user from editting and submitting the form themselves. They can easily screen scrap the $token off the page then draft their own forgery. Nothing, there, could tell the difference between a user who spent some time filling out the form or the user who spent that same time forging it.
It allows you to test the user is coming from this page (because he has a valid token) and is not being tricked by a foreign site trying to CSRF..
It stops only the trivial CRSF exploit, but not a forged POST submission which is just as trivial. It does NOT test if they are coming from your page.

I could write a page on my website that POST'd to yours. I go to your site, view source, and add the token from your page to my page. Now your receiving script thinks my form is legitimate.

Posted: Tue Jul 26, 2005 11:04 am
by timvw
nielsene wrote: I could write a page on my website that POST'd to yours. I go to your site, view source, and add the token from your page to my page. Now your receiving script thinks my form is legitimate.
That is only possible if i don't require my users to log in. If they are not logged in, they don't get to see the form, and thus they don't get a token.

The problem with CSRF is when you have users that are logged in, and are forged into posting data... This would be made impossible, because the site that tries to perform CSRF doesn't know what the user's token is..

Posted: Tue Jul 26, 2005 11:09 am
by nielsene
timvw wrote:
nielsene wrote: I could write a page on my website that POST'd to yours. I go to your site, view source, and add the token from your page to my page. Now your receiving script thinks my form is legitimate.
That is only possible if i don't require my users to log in. If they are not logged in, they don't get to see the form, and thus they don't get a token.

The problem with CSRF is when you have users that are logged in, and are forged into posting data... This would be made impossible, because the site that tries to perform CSRF doesn't know what the user's token is..
Note, in my hypothetical counter example, I have logged in to your site. I'm looking at the form you present to the user. I do view source, I see the token. I copy/paste that token to my forged script. I can now submit anything I want and your site thinks it came from yours.

Posted: Tue Jul 26, 2005 11:10 am
by timvw
nielsene wrote: While I agree with timvw that you should avoid the exposure of primary keys/column names; if you have to use said key for some reason, then yes a hash would work to transform it to keep it confidential.
Well, my method is exactly the same.. I build up a dictionary/hash that gives each row a key.

But it also suffers the same problem: How to accomplish the rotation of keys? (You don't want that the row <-> key mapping is constant in time)

Posted: Tue Jul 26, 2005 11:26 am
by timvw
nielsene wrote:
timvw wrote:
nielsene wrote: I could write a page on my website that POST'd to yours. I go to your site, view source, and add the token from your page to my page. Now your receiving script thinks my form is legitimate.
That is only possible if i don't require my users to log in. If they are not logged in, they don't get to see the form, and thus they don't get a token.

The problem with CSRF is when you have users that are logged in, and are forged into posting data... This would be made impossible, because the site that tries to perform CSRF doesn't know what the user's token is..
Note, in my hypothetical counter example, I have logged in to your site. I'm looking at the form you present to the user. I do view source, I see the token. I copy/paste that token to my forged script. I can now submit anything I want and your site thinks it came from yours.
Meaby is the sentence "enforce use of own forms" a bit heavy, but:

This method does prevent CSRF. It requires that the user has visited my form page (to recieve a token)..

I don't care if he uses the data to generate a custom form, or a script that posts it (This is the nature of HTTP). All i care about is that he has a valid token..

External parties don't have this token, so they can't trick the user to post it...

Posted: Tue Jul 26, 2005 11:30 am
by Ambush Commander
Well, they could be tricked.

Code: Select all

<form action=&quote;http://yourwebsite.example.com/delete.php&quote; method=&quote;post&quote;>
<input type=&quote;hidden&quote; name=&quote;id&quote; value=&quote;9823&quote; />
<input type=&quote;submit&quote; value=&quote;Click here to get your free MP3!&quote; />
</form>
It'd be kinda hard to actually get the right person to click the right button, though.