I am currently having an issue with using a nonce as a security solution in PHP
i read this post about
How to check if a request if coming from the same server or different server?
about using an hidden input form field to hash a random value and At the same time, store that random value into the session that correspond to the user.
When the form is submitted, check that the hidden field has the same value as the one that's stored in session. (I think am having a problem with this)
Example
<?
$_SESSION['formhash'] = md5('any value to be hashed');
?>
<input type="hidden" name="hashed" id="hashed" value="<?php echo $_SESSION['formhash']; ?>" />
A user having an Mozilla Firebug and inspecting the element will still find out my hidden field and then copy it. And then create his/her own form then post it to my url and the Login will still be Bypassed.
Image showing example
Is there any more secure way to do this ?
Any help will be appreciated thanks !
The bottom line is that you cannot prevent a user from manipulating the form data prior to submitting the form. What your solution does is confirms that the form data is coming from the user you sent it to.
Regardless of whether or not a user is "logged in", you will probably start a session each time a new visitor hits your site. This means you can store the hash value each time you send them a form and you should, in theory, be able to associate the hash value on the returned form data with the hash value in the session (just like your code is doing).
Armed with that knowledge, we can consider the following scenarios:
The typical use case is that a user submits a form without modifying the data. Your approach will allow you to confirm that the form has been posted by that very user. Your validation code should confirm that the POST data is acceptable.
If a user modifies the form data and submits it, your approach will allow you to confirm that the form has been posted by that user, but not that the form has been messed with. This is why you need to validate forms very, very carefully.
If a user grabs a form that was actually sent to someone else and posts it - modified or not - your system will allow you to confirm that the form did not come from the user that it was originally sent to and you should reject it.
Scenario 3 is what is known as a CSRF attack and your solution is the standard defense against this attack.
PS As #cHao says, you should regenerate the hash for every form you generate.
Related
I implemented CSRF protection by including a token with PHP into a hidden input for every form. Each token can only be used once, of course.
However, there are tools, such as any web developer tools, which allow inputs to be changed. For example, I can change on-page input forms: I can make disabled checkboxes enabled, and I can change input boxes to textarea boxes without reloading the page or anything like that. CSRF wouldn't catch such changes.
So, how much of a form do I need to validate to stay safe? Do I need to validate every single input to make sure it wasn't altered, including selects, checkboxes, hidden inputs, etc? Surely it can't be safe to assume that these haven't been altered?
You need to validate (on the server side) everything that needs to be validated. What exactly needs to be validated depends on many factors and personal choices. Some of it may be for safety, but only a bare minimum is needed for that in many cases. For the most part validation is to improve or create user experience.
For example you can check to see whether they have entered a valid email address. If they haven't, you can give them a message. If you don't do that nothing bad will happen to your application, but the user won't be able to receive email from you.
There is also an important distinction between validation and sanitation. Sanitation is done for security (e.g. to prevent injection). Validation is done to make sure that input meets requirements to work correctly with your application although incorrect input may be benign. It's also possible for sanitized malicious input to be valid.
All input must be sanitized. No input needs to be validated, so it's really up to you.
CSRF protection has nothing to do with validation. All it does is prevent a user from making a request using your form from an external source because the only way to generate and see the token is to make a request to your site first.
What we are trying to do using CSRF is to ensure that the request IS coming from a reliable source. For e.g, your case what you need to do is ensure that the value in the hidden field is sane. And it can be sane (provided that your token is strong enough) only if it is the same as the one that was provided while the form was rendered by the server.
Now whether fields in the form changed or not, is just your application logic. It does not have anything to do with csrf. If the token is sane, then it came from the right source. Now, if it was the same person who entered values in the form for e.g. is not within the scope of csrf.
I think you are getting the wrong end of the stick here. The token is not a hash of the form when it was sent.
The way this works is to store your unique token in a hidden field on the form and into the session when you server the original page.
When you get the page POSTed/GETed back from your user you check that the token on the page is the same as the token previously stored in the session.
Changing fields must still be allowed or your user will not be able to enter any data on the form. You are just checking that you got the same form back that you sent, because the token is the same, rather than one from somewhere else, i.e. its not a cross site request forgery.
You still have to validate all the fields and do any data preparing before storing it in a database.
Reading1
Reading2
Is there a way to ensure the $_POST data my code received came from my form and not an outside influence. Basically I don't want someone to be able to spoof a $_POST to a universally available page such as account creation. The account creation page is accessible by any user, but I want to ensure only the data submitted by my account_creation form is what gets processed.
The only thing I could think of was initiating a $_SESSION, and then supplying the session_id to the form using a hidden input. Upon $_POST the value of the hidden input would then be matched against the current session_id.
If there is a better method to achieve this result? If there is I look forward to hearing it.
You cannot ensure that data came from a form. A POST request is just a POST request, it can be generated in any number of ways. An HTML form is just one of those ways that's very user friendly. Your server needs to validate whether the data received via the POST request is valid or not and whether to act on it or not.
Having said that, there are things that can help you to restrict and validate the data that is being submitted. First of all, require that a user is logged in using (session) cookies. That eliminates random requests by anonymous users. Secondly, you can embed a token as a hidden field into the form which you also save into the user's session. The POST request needs to contain that token in order to be valid. The token is simply a pseudo-random string.
You can enhance this by preparing a hash of the form fields that you expect the user to submit. If the form value should be read-only, you can include the value into the hash as well. E.g.:
$rand = md5(mt_rand());
$hash = sha1('lastname:firstname:email:' . $rand);
$_SESSION['rand'] = $rand;
$_SESSION['hash'] = $hash;
// on form submit:
$keys = array_keys($_POST);
$checkHash = sha1(join(':', $keys) . ':' . $_SESSION['rand']);
if ($checkHash != $_SESSION['hash']) {
die('Form submission failed token validation');
}
That's just a quick example, you'll probably want to sort the keys alphabetically to make sure you'll get the same hash etc. It demonstrates the concept of the user needing to have a unique token for each request though which prevents tempering with forms and submitting more or less data than wanted.
This still does not mean that a user actually used your form to submit the data though.
$ref = $_SERVER['HTTP_REFERER'];
if($ref !== 'some site path/index.php')
{
die("Access Denied!");
}
This should prevent most people from posting data to your database from an outside influence.
Slightly better is to add additional validation such as user_agent, user_ip and some other $_SERVER vars - those are the two I use.
So, create the unique ID (or Session ID) as you describe, but add a little extra validation that the agent and ip also match. Not fool proof, but adds another little layer of security.
Edit: I should add that you don't send the user agent back; keep that server side and silently validate against the returned session id.
Also, if a submission fails validation, never reveal that back to the user as to why - that way a cheat doesn't know how your tracking them. You can also add "5 invalids and you're out" tracking, but you need to sort of login for that.
Using the session ID is certainly one way of doing it. But there are other options most (if not all) of which involve adding some data as a hidden field.
Use a CAPCHA. That will always be unique to each page load and therefore make it mandatory to use your form.
Generate random data and store it in the DB (or just the $_SESSION variable) and check it once the form is submitted.
Option one is the one I recommend for a user creation form as it pulls double duty. It stops automated submission of your own form, while ensuring that the $_POST data is coming from your own form.
This is a standard pattern pattern to prevent XSRF. Essentially it is the similar to what you mentioned. Server creates a random token when form is rendered for the user. It is tied to a browser cookie for the user. On form submission it is posted back to the server. Server then compares the token with what was issued and form action is performed only after a successful match.
There's a lot of good mentions of putting a unique value in the form and matching to the stored value in the server side session. Do that, but also think about what happens when a user uses the back button and possibly tries to submit the form twice, or they open a second browser window(same session!), or they use multiple forms on your site.
Don't create crazy bugs by not thinking your system through.
I have been googling a lot but i am still without an answer so i decided to ask this question here:
Is there a way in PHP how to check that the data i am receiving in some script are from the specific form on the page? I am asking because everyone can see the name of the script i am using for saving the data to my database, so if somebody is able to find out the whole URL, he is also able to send some fake data to the script and i need a condition, that the saving process is triggered only when the data comes from the proper form on my page.
I am using jQuery to call AJAX function, so basically if i click on the button "send", the $.POST() method is triggered to call the script for saving the data.
Thanks,
Tomas
Use tokens to check if request is valid
You could always add some kind of security token when submitting data:
Tokens can be easily extended for many different uses and covers wide area when it comes to checking if some request is valid, for example you could let your non critical forms open for public, ask users to get their secret keys from some page (forcing them to open that page) and then use those keys to identify them when submitting data.
Of course all of this can be completely transparent to user as you could give keys from front page via cookies (or session cookies, it does not matter here, no more or less security as server keys should change after use and invalidate within specified time or when user's identity changes).In this example of use, only user that opened front page can submit data to server.
Another case is when cookies is given away at same page which contains form for submitting data to server. Every user that open page will have their keys to submit data straight away, however if someone tries to make request from outside it will fail.
See OWASP Cross Site Request Forgery
and codinghorror.com Blog CSRF and You
Only with AJAX?
Here is my answer to another question, this answer covers different methods for inserting additional data to ajax request: Liftweb: create a form that can be submitted both traditionally and with AJAX (take a closer look at
$.ajax({
...
data: /* here */
...
Currently I am using tokens this way:
Form used to submit
This hidden input can be added to form, it is not requirement as you can use methods described earlier at another answer.
<input type="hidden" name="formid" value="<?php echo generateFormId(); ?>" />
Function generateFormId()
Simply generate random string and save it to session storage
function generateFormId() {
// Insert some random string: base64_encode is not really needed here
$_SESSION['FORM_ID'] = 'FormID'.base64_encode( uniqid() );
// If you want longer random string mixed with some other method just add them:
//$_SESSION['FORM_ID'] = 'FormID'.base64_encode( crypt(uniqid()).uniqid('',true) );
return $_SESSION['FORM_ID'];
}
Processing submitted form data
if (!isset($_SESSION['FORM_ID']) || $_SESSION['FORM_ID'] != $_POST['formid']) {
// You can use these if you want to redirect user back to form, preserving values:
//$_SESSION['RELOAD_POST'] = $_POST;
//$_SESSION['RELOAD_ID'] = uniqid('re');
echo 'Form expired, cannot submit values.';
//echo 'Go back and try again';
exit(1); // <== Stop processing in case of error.
}
If you need to check which form is submitting data
Then you could just add prefix when generating id's and check for that prefix when processing form data.
This is case when one php script deals with many different forms.
Remember that only ultimate answer to prevent evil users is to pull off all wires from your server...
This is an interesting topic, but you are missing an important point: A spam robot / bad user could also bomb your database by using that specific form page (!).
So, the question is not how to check if the request comes from that page or not, the question is how to check if he's a regular user or a robot/spammer/bot.
Do it with a captcha, like http://www.recaptcha.net
In case i slightly misunderstood the question: If you want to be sure that the request comes from a "real" user you will have to work with a login system / user system and check for users id / password (via db or sessions) every time you want to do a db request.
You can verify that the page is being requested via AJAX with checking against this:
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest'
You could also check the HTTP_REFERER.
But it sounds like you're trying to protect a page that processes vulnerable data. This requires more than just the two things above. I'd recommend googling 'CSRF Forgery' to get more information on this.
These are something that you should take a look at.
Capthcas.
Referer check.
Use POST than GET. [Still Curl can automate it.]
I have a main form with a lot of inputs. The forms action is set to a php file.
After submitted the php-page shows up with all the inputs that the user typed into the form. The user has to re-check everything and hit an 'approve' button!
The problem is, I don't know how to do this...
I want to eventually put all data into a MySQL database, but only after hitting approve, not submitting the form!
Is there a way to 'recall' the php script after clicking the button 'approve'?
How is this done? I mean, I don't want to write information to the MySQL database, and then the user regrets and DOESN'T hit 'approve' and then the data is already in the database.
Thanks!
Just let me know if you need more input (I will then update the question)
One way to accomplish this is rewrite every value that was just submitted into the approval form in the form of html hidden inputs. Like this:
<form name="approval" method="post>
<input type="hidden" name="firstname" value="<?php echo htmlspecialchars($_REQUEST['firstname']); ?>" />
<input type="hidden" name="lastname" value="<?php echo htmlspecialchars($_REQUEST['lastname']); ?>" />
...
<input type="submit" value="Approve!" />
</form>
Others have suggested storing the values in the users session. If you choose to go that route, be careful about user's who like to work with multiple browser windows open at the same time. Their different forms will share the same session and depending on what order they choose to submit forms in, you could end up with some crossed wires if your code is too naive. One way around this is to generate a unique key for each form and pass it around from page to page. This of course gets messy which is why I prefer the hidden form field approach.
Another pitfall related to the session approach is the PHP's default session implementation uses the local filesystem to store session data. This breaks down when you have redundant web servers. You can, of course swap the default file based session implementation for something more sophisticated (based on memcached perhaps). But again, this is just more complexity. Why not avoid the complexity and stick with hidden form fields?
You can store the information in one of two ways: using the session or as hidden form inputs.
Using a Session It would make a lot of sense to use the session in this case. So, when the first form is submitted, start a session with the user and save all the values in it. Then, the confirmation page simply shows the data. When the user hits "approve", this triggers your script to store the information that is already in the session. This is a well-known method for persisting information between requests.
Using a Hidden Form As you write out your "approve" page, you could also write hidden form inputs along with your displayed confirmation data. Adding a new field to indicate that the user has approved this data, your script will only write to the database when it sees this confirmation value.
Without knowing much more about your application, I'd prefer using sessions in this case.
You could store all the information in a session first. Then make your calculations, and have the user approve the information. Write them from the session into the database.
What is the best way to stop a form from being reprocessed when a user hits a Back button?
I'm following the Post/Redirect/Get pattern, so I don't have a problem if F5 is pushed, but the back button still provides the opportunity to resubmit the form. If this form is a credit card processing page, this is bad.
This question has been asked somewhat here, but the phrasing and answers were of poor quality and not specific to this problem exactly.
I have form.php which submits to itself. If there were no errors in input data upon submission, the user is redirected to form_thanks.php. Hitting back (and "Send" or "Resubmit") once resubmits form.php (BAD!) and then brings them back to form_thanks.php.
Please also include solutions that do not involve using Sessions, if possible.
I would do it a different way. Put up a hidden input with a random string as the value, and when it's submitted store that random string in a session. Set up a simple check to see if they've already posted it and if they have don't accept the input.
This should be done with a single-use token, or a nonce. The php/server should include this token in a hidden form field.
It should store a list of all the recent tokens and each time a form is submitted, it should check to see if the token is in the list of recent valid tokens.
If it's not in the list, then don't reprocess the form.
If it is in the list, then process the form and remove the token from the list.
The tokens can be handled within sessions or just a simple database table without sessions. The tokens should be user-specific though.
This is also the recommended way to avoid CSRF attacks.
Just thinking out loud here. But what about a variation on post/redirect/get where the final get is not actually the final get ;) But rather, it in turn always automatically forwards to the truly final page, so that should the user hit the back button, they return right back whence they came?
EDIT:
Ok, taking into consideration the OP's comment, here's another idea. The URL for the form submission could be made to require a parameter that is good for only one use. That token would be generated (using MD5 or some such) before the form was submitted and could be stored in a database (in response to somebody else's suggestion you requested a solution without using sessions). After the form is processed, this token would then be flagged in the database as having already been used. So that when the page is returned to with the same token, steps can be taken to prevent the resubmission of the form data to the backend.
Late answer, but one could avoid the processing altogether by using an AJAX-based solution; there wouldn't be an issue with including a nonce with this processing scheme, but by using an asynchronous query which, on success, redirects the user, the requests are not repeated by refreshing, pressing back, or anything other than clicking the button.
It is also easy to implement a mechanism that prevents the button from either being pressed twice or being "locked up" if something happened during the request by embedding into the handler for the request (whether high level with PrototypeJS or jQuery or low level with your handrolled function) the mechanisms to enable and disable the button when the request completes and first fires, respectively.
I find that back will bring the form to the state it was before the page was redirected, if that is the case, have a hidden input/variable or something which starts with value say true, then once the form is submitted, and if the value is true, change it to false and then submit, else return false
Try this :
<SCRIPT type="text/javascript">
window.history.forward();
</SCRIPT>