PHP security: 'Nonce' or 'unique form key' problem - php

I use this class (taken from a blog tutorial) to generate unique keys to validate a form:
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct() {
//We need the previous key so we store it
if(isset($_SESSION['form_key'])) {
$this->old_formKey = $_SESSION['form_key'];
}
}
//Function to generate the form key
private function generateKey() {
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey() {
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
// echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
return $this->formKey;
}
//Function that validated the form key POST data
public function validate() {
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey) {
//The key is valid, return true.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Everything in my website goes through index.php first, so I put this in index.php: $formKey = new formKey();
Then, in every form I put this: <?php $formKey->outputKey(); ?>
That generates this: <input type="hidden" name="form_key" id="form_key" value="7bd8496ea1518e1850c24cf2de8ded23" />
Then I can simply check for if(!isset($_POST['form_key']) || !$formKey->validate())
I have two problems. First: I cant use more than one form per page becouse only the last key generated will validate.
Second: Because everything goes through index.php first, if I use ajax to validate the form, the first time will validate but the second time not, because index.php generates a new key but the pages containing the form does't refresh so the form key is not updated..
I have tried several things but I cant get it to work.. Maybe YOU can update/modify the code/class to get it to work?? Thanks!!!

You could put this into a class, but this is needless complexity. Simple security systems are best because they are easier to audit.
//Put this in a header file
session_start();
if(!$_SESSION['xsrf_token']){
//Not the best but this should be enough entropy
$_SESSION['xsrf_token']=uniqid(mt_rand(),true);
}
//$_REQUEST is used because you might need it for a GET or POST request.
function validate_xsrf(){
return $_SESSION['xsrf_token']==$_REQUEST['xsrf_token'] && $_SESSION['xsrf_token'];
}
//End of header file.
The extra && $_SESSION['xsrf_token'] makes sure this variable is populated. Its there to make sure the implementation fails securely. (Like if you forgot the header file doah! ;)
This following html/php goes in any file you want to protect from XSRF, make sure you have the code above in a header file.
if(validate_xsrf()){
//do somthing with $_POST
}
This is all you need to print out the form, again make sure you call session_start(); before you do anything, it doesn't matter if you call it multiple times.
<input type="hidden" name="xsrf_token" id="form_key" value="<?=$_SESSION['xsrf_token']?>" />

Not tested, but it should work.
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct() {
//We need the previous key so we store it
if(isset($_SESSION['form_key'])) {
$this->old_formKey = $_SESSION['form_key'];
$this->formKey = $this->generateKey();
$_SESSION['form_key'] = $this->formKey;
}
}
//Function to generate the form key
private function generateKey() {
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey() {
return $this->formKey;
}
//Function that validated the form key POST data
public function validate() {
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey) {
//The key is valid, return true.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Edit: changed back to single key. Just call outputkey() to when needed. Don't create more than one instance of this class.

Related

Form doesn't validate after submitting in moodle

I am trying to create a form whose structure depends on the parameter in the url. If no parameter is specified in the url, an error message should be displayed. Depending on the id, a database query is performed and the form is filled with data.
example url: http://127.0.0.1/local/group/signin.php?groupid=14
Unfortunately, my form doesn't validate after I submit the form via pressing the action button. It jumps to http://127.0.0.1/local/group/signin.php and because there is no parameter present in the url the error message 'No group found' is displayed.
What have I done wrong here?
signinform.php:
class signinform extends moodleform {
public function definition() {
global $DB;
global $USER;
$mform = $this->_form;
$urlid = $this->_customdata['id']; // get the passed group id
$message = 'No group found';
if(is_null($urlid)){
$mform->addElement('html', '<h3>'.\core\notification::error($message).'</h3>');
}
else{
// build the form, sql query etc.
$this->add_action_buttons(true, 'Submit');
}
}
function validation($data, $files) {
return array();
}
}
signin.php:
$PAGE->set_url(new moodle_url('/local/schedule/signin.php?'));
$PAGE->set_context(\context_system::instance());
$PAGE->set_pagelayout('base');
$PAGE->set_title("Sign up");
$PAGE->set_heading("Sign up for a group");
global $DB;
global $USER;
$urlid = $_GET["id"];
$to_form = array('id' => $urlid); // pass group id to form
$mform = new signinform(null, $to_form);
$homeurl = new moodle_url('/');
if ($mform->is_cancelled()) {
redirect($homeurl, 'Cancelled.'); // Just for testing, never enters here
} else if ($fromform = $mform->get_data()) {
redirect($homeurl, 'Validation in process'); // Just for testing, never enters here
}
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
You need to add a hidden field to the form that contains the 'id' that has to be passed to the page, otherwise, when the form is submitted, that id will no longer be present in the params for that page.
e.g. (in definition())
$mform->addElement('hidden', 'id', $urlid);
$mform->setType('id', PARAM_INT);
Also, in Moodle, you should never access $_GET directly - use the wrapper functions required_param() or optional_param(), as these:
Clean the parameter to the declared type
Automatically take parameters from either $_GET or $_POST (which will be important in this case, as the 'id' param will be part of the POST data, when you submit the form)
Handle missing parameters by either applying a default (optional_param) or stopping with an error message (required_param)
So your access to $_GET['id'], should be replaced with:
$urlid = optional_param('id', null, PARAM_INT);

Issue with redirect() when using conditional to evaluate multiple form buttons

So I've built a small conditional to evaluate which button is pressed in my form (as there are 2). This works fine and fires off the correct method and writes the appropriate data to the DB, however my redirect is not working. It saves() to the DB and then simply stays on the page designated as the POST route.
I suspect the problem has something to do with my conditional and the use of $this.
Here is my check_submit method:
public function check_submit()
{
if(!is_null(Input::get('add_to_invoice'))){
$this->invoice_add_item();
} elseif(!is_null(Input::get('complete_invoice'))) {
$this->invoice_complete();
}
}
Here is one of the 2 methods which I am currently testing:
public function invoice_add_item()
{
$input = Request::all();
$invoice_items = new Expense;
$invoice_items->item_id = $input['item_id'];
$invoice_items->category_id = $input['category'];
$invoice_items->price = $input['price'];
$invoice_items->store_id = $input['store'];
if(Input::has('business_expense'))
{
$invoice_items->business_expense = 1;
}
else{
$invoice_items->business_expense = 0;
}
$invoice_items->save();
return redirect('/');
}
Perhaps there is a better way of handling this in my routes(web) file, but I'm not sure how to go about this.
You should add the return to the check_submit() method. Something like
public function check_submit()
{
if(!is_null(Input::get('add_to_invoice'))){
return $this->invoice_add_item();
} elseif(!is_null(Input::get('complete_invoice'))) {
return $this->invoice_complete();
}
}
Better yet, you should probably return a boolean on invoice_add_item() and based on that, redirect the user to the correct place (or with some session flash variable with an error message)

Safari is causing session variables to be changed

I'm verifying a form input and making sure the user doesn't submit the form twice. I'm doing this with the following class:
<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
//Here we store the generated form key
private $formKey;
//Here we store the old form key (more info at step 4)
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct()
{
//We need the previous key so we store it
if(isset($_SESSION['form_key']))
{
$this->old_formKey = $_SESSION['form_key'];
}
}
//Function to generate the form key
private function generateKey()
{
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
//See http://www.php.net/mt_rand for a precise description of the function and more examples.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey()
{
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
}
//Function that validated the form key POST data
public function validate()
{
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey)
{
//The key is valid, return true.
unset($_SESSION['form_key']);
return true;
}
else
{
//The key is invalid, return false.
return false;
}
}
}
?>
This is output to the form like this then:
include STYLESHEETPATH . '/formkey.class.php';
$formKey = new formKey();
$formKey->outputKey();
If I echo out the old_formKey and the post form_key they are the exact same and it works in every browser other than Safari. If I check these in Safari, the old_formKey is always different. Why would this be?

How can I edit a widget value while posting?

Lets see the action (form is based on model)
$this->form->bind ($request->getParameter('task'));
if ($this->form->isValid())
{
// cakk
}
This all works good, its not valid when its really not valid etc.
But I want to edit some fields, for example a date must be always set to now. Or a password must be encoded. How can I do this?
You can override the doSave() method in the form .. something like this :
public function doSave($con = null)
{
$this->values['form field'] = 'newvalue';
parent::doSave($con);
}
$this->values is an array containing the values on the form.
Update
You could use a post validator .. like this (again in the form class) :
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this, 'methodName')))
);
public function methodName($validator, $values)
{
// check / change what you need to
$values['fieldname'] = 'new value';
// return values
return $values;
}

Cakephp - Conditional Save

My form contains a model object that contains five child objects that are related using hasMany. When I save the form, I notice that all fields, regardless if they are empty, are saved into the database. Is it possible to set a condition in the beforeSave() callback method to prevent child items that have no values from being saved? I have tried to unset the key in the array containing empty values but the row is still being added into the database.
Here is the code for my 'Mtd' model. The Mtd model contains many Flowratereatments. On my form, I have a checkbox that says 'This is a Flow Rate Based Treatment'. So, if a user clicks on that, then the user can fill it in the fields. However, if the user does not fill it in, I want to prevent a new row from being added with just the foreign key of the Mtd table.
<?php
class Mtd extends AppModel {
public $name = 'Mtd';
public $hasOne = array('Treatmentdesign', 'Volumetreatment');
public $hasMany = 'Flowratetreatment';
function beforeSave() {
if($this->data['Mtd']['is_settling'] != 1){
unset($this->data['Flowratetreatment'][0]);
}
return true;
}
}
?>
Did you tried something like:
class User extends AppModel {
function validates() {
$this->setAction();
#always validate presence of username
$this->validates_presence_of('username');
#validate uniqueness of username when creating a new user
$this->validates_uniqueness_of('username',array('on'=>'create'));
#validate length of username (minimum)
$this->validates_length_of('username',array('min'=>3));
#validate length of username (maximum)
$this->validates_length_of('username',array('max'=>50));
#validate presence of password
$this->validates_presence_of('password');
#validate presence of email
$this->validates_presence_of('email');
#validate uniqueness of email when creating a new user
$this->validates_uniqueness_of('email',array('on'=>'create'));
#validate format of email
$this->validates_format_of('email',VALID_EMAIL);
#if there were errors, return false
$errors = $this->invalidFields();
return (count($errors) == 0);
}
}
?>
in your model
I have used this code:
public function beforeSave() {
if(isset($this->data[$this->alias]['profile_picture'])) {
if($this->data[$this->alias]['profile_picture']['error']==4) {
unset($this->data[$this->alias]['profile_picture']);
}
}
return true;
}
in a previous app, to remove a key from $this->data if the user had not uploaded a file, to prevent the old value being overwritten.
this should work for you (you'll need to adapt it; based on what $this->data contains at this point.
public function beforeSave() {
if(empty($this->data[$this->alias]['the_key'])) {
unset($this->data[$this->alias]['the_key']);
}
//debug($this->data); exit; // this is what will be saved
return true;
}
you mention you tried this already? post your code in your original post.

Categories