PHP coding question 2

Discuss how to use and promote Web standards with the Mozilla Gecko engine.
Locked
Bethrezen
Posts: 445
Joined: September 13th, 2003, 11:56 am

PHP coding question 2

Post by Bethrezen »

Hi all

Ok so I've run in to something of an oddity that I don’t understand and I was wondering if any might be able to explain what is going on.

Why would

Code: Select all

if($_SESSION['total'] === (float)$_POST['response'] 
or $_SESSION['total'] === (integer)$_POST['response'])
{
echo "match";
}
else
{
echo "no match";
}
give the correct response when the values are the same

while

Code: Select all

if($_SESSION['total'] !== (float)$_POST['response'] 
or $_SESSION['total'] !== (integer)$_POST['response'])
{
echo "no match";
}
else
{
echo "match";
}
gives the incorrect response when the values are the same ?

Ok so a little background in my previous post I set up a simple form and using a little php this form would generate a random maths question and then ask the user for a response.

I would then get the users response from the form and compare the generated answer value to the users response to see if they match and then give either a correct or incorrect response depending on whether the answer is right or not.

now one if the issues I ran into during this little experiment is that when retrieving data from a web form it is sent as a string but the answer value generated by the script was either an integer (whole number) or a float (number with a decimal point) which means that when using the strict comparison you need to force the value from the form to be evaluated as either a integer or a float now after experimenting I discovered that I could do that like this

Code: Select all

if($_SESSION['total'] === (float)$_POST['response'] or $_SESSION['total'] === (integer)$_POST['response'])
{
echo "match";
}
else
{
echo "no match";
}
now while this works fine if I try to switch the check around the other way so that I'm checking to see if the values don't match like this

Code: Select all

if($_SESSION['total'] !== (float)$_POST['response'] or $_SESSION['total'] !== (integer)$_POST['response'])
{
echo "no match";
}
else
{
echo "match";
}
when the check is performed it says the values don't match even when the values where the same which is strange because to the best of my knowledge if the values that it is comparing are the same then regardless of which way round I do the check I should get the same response which is that the values match.

here is the full code

Code: Select all

<?php

//Start Session
session_start();

$error = false;
$sent = false;

//Check is Submit Has been pressed
if(isset($_POST['submit']))
{
  //Check If The Stored Answer Is Identical To The Users Response.
  //(float)$_POST['response'] and (integer)$_POST['response'] Forces $_POST['response']
  //To Be Evaluated As A Whole Number Or As A Decimal Number Instead Of As A String.
  if($_SESSION['total'] === (integer)$_POST['response']
  or $_SESSION['total'] === (float)$_POST['response'])
  {
    $error = false;
    $sent = true;
    $message = "Correct.";
  }
  
  else
  {
    $error = true;
    $sent = false;
    $message = "You answered the anti-spam question incorrectly.";
  }
}

if(!isset($_POST['submit']) or $error === true or $sent === true)
{
  //Generate Random Numbers
  $value_1 = rand(1,1000);
  $value_2 = rand(1,1000);
  $value_3 = rand(1,4);

  switch ($value_3)
  {
    case 1:
      $question = "What is" ." ". $value_1 . " / " . $value_2 . " ? ";
      $answer = round($value_1 / $value_2, 2);
      break;
 
    case 2:
      $question = "What is" ." ". $value_1 . " * " . $value_2 . " ? ";
      $answer = $value_1 * $value_2;
      break;
 
    case 3:
      $question = "What is" ." ". $value_1 . " + " . $value_2 . " ? ";
      $answer = $value_1 + $value_2;
      break;

    case 4:
      $question = "What is" ." ". $value_1 . " - " . $value_2 . " ? ";
      $answer = $value_1 - $value_2;
      break;
  }

  //Store/Update The Answer To The Question.
  $_SESSION['total'] = $answer;
}

?>

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>test</title>
</head>

<body> 

<form action="random_math_question.php" method="post">
<p><?php echo $question; ?>
<br><input type="text" name="response"> <?php echo $message; ?></p>
<p><input type="submit" name="submit" value="Submit"></p>
</form>

</body>
</html>
isaacschemm
Posts: 270
Joined: January 20th, 2015, 12:29 pm

Re: PHP coding question 2

Post by isaacschemm »

Shouldn't

Code: Select all

if($_SESSION['total'] !== (float)$_POST['response'] or $_SESSION['total'] !== (integer)$_POST['response'])
use an "and" instead of an "or"?

The opposite of (A or B) is ((not A) and (not B)).
Bethrezen
Posts: 445
Joined: September 13th, 2003, 11:56 am

Re: PHP coding question 2

Post by Bethrezen »

use an "and" instead of an "or"?

The opposite of (A or B) is ((not A) and (not B)).
Ahh of course don’t know why I didn’t see that DO'H!!! but then again this is why I put together simple little exercises, It also makes grate reference material if I need to do something similar at a later point as it gives me a working example to look back at.

Anyway with that little glitch sorted I next need to add some validation and sanitization as user input should never be trusted and should always be assumed to be bad

Now since form security is really important and since I'll be using this as reference material later on I though it would be a good idea to take the time to learn how to set up form security correctly to prevent or at the very least make it as hard as possible for my forms to be attacked

Now after doing a bit of searching I found this on w3schools which I then modified to make it a bit clearer.

Code: Select all

function input_sanitizer($user_input)
{
      //strip whitespace (or other characters) from the beginning and end of a string.
      $user_input = trim($user_input);
      
      //returns a string with backslashes stripped off
      $user_input = stripslashes($user_input);
      
      //strip HTML and PHP tags from a string
      $user_input = strip_tags($user_input);
      
      //convert special characters to HTML entities
      $user_input = htmlspecialchars($user_input);
  
      return $user_input;
}
And this is how I currently have my validation and sanitization set up

Code: Select all

 //Check is Submit Has been pressed
if(isset($_POST['submit']))
{
    //pass each input through the sanitizer function
    $_POST['response'] = input_sanitizer($_POST['response']);

    //Check If The Response Field Is Empty.
    if(empty($_POST['response']))
    {
        $error = true;
        $sent = false;
        $message = "Response field is required";
    }
  
    //Check If The Response Field Only Contains Positive Negative Or Decimal Numbers.
    else if(!preg_match("/-?\d\.*/",$_POST['response']))
    {
        $error = true;
        $sent = false;
        $message = "The response field can only contains positive negative or decimal numbers.";
    }

    //Check If The Stored Answer Is Identical To The Users Response.
    //(float)$_POST['response'] and (integer)$_POST['response'] Forces $_POST['response']
    //To Be Evaluated As A Whole Number Or As A Decimal Number Instead Of As A String.
    else if($_SESSION['total'] !== (integer)$_POST['response']
    and $_SESSION['total'] !== (float)$_POST['response'])
    {
        $error = true;
        $sent = false;
        $message = "You answered the question incorrectly.";
    }
  
    else
    {
        $error = false;
        $sent = true;
        $message = "Correct.";
    }
}
Now while this is a reasonable start, my question is how could you strengthen this further?
User avatar
Frenzie
Posts: 2135
Joined: May 5th, 2004, 10:40 am
Location: Belgium
Contact:

Re: PHP coding question 2

Post by Frenzie »

That really depends on what you want to do. In your current example I doubt it makes sense to do anything other than to check if it can be parsed as a float or integer after trimming.

For database insertion use something like http://php.net/manual/en/mysqli.real-escape-string.php

For making links that work there's http://php.net/manual/en/function.urlencode.php
Intelligent alien life does exist, otherwise they would have contacted us.
Bethrezen
Posts: 445
Joined: September 13th, 2003, 11:56 am

Re: PHP coding question 2

Post by Bethrezen »

That really depends on what you want to do. In your current example I doubt it makes sense to do anything other than to check if it can be parsed as a float or integer after trimming.
I know for my previous exercise it doesn't make a lot of sense I was really thinking in terms of things like contact forms login form etc.

for example here is another little practice exercise I've been working on, now as you will see I've taken what I learn from my previous exercise and incorporated it into this one.

Code: Select all

<?php

  //Start Session
  session_start();
    
  //define and set variables
  $sent = false;
  $conformation_message = "";
  
  $error = false;
  $name_error_message = "";
  $email_error_message = "";
  $subject_error_message = "";
  $comments_error_message = "";
  $captcha_error_message = "";
  
  //add the variables needed to make the mail() function work.
  
  function input_sanitizer($user_input)
  {
    //strip whitespace (or other characters) from the beginning and end of a string.
    $user_input = trim($user_input);
      
    //returns a string with backslashes stripped off
    $user_input = stripslashes($user_input);
      
    //strip HTML and PHP tags from a string
    $user_input = strip_tags($user_input);
      
    //htmlentities() converts special characters to HTML entities
    //"UTF-8" explicitly specifies the use of UTF-8 encoding needed if running older versions of PHP
    //ENT_QUOTES, tells htmlentities() to encodes both double and single quotes 
    $user_input = htmlspecialchars($user_input, ENT_QUOTES, "UTF-8");
  
    return $user_input;
  }

  //check if the submit button has been pressed.
  if(isset($_POST['submit']))
  { 
    //pass each input through the sanitizer funtion
    $_POST['name'] = input_sanitizer($_POST['name']);
    $_POST['email'] = input_sanitizer($_POST['email']);
    $_POST['subject'] = input_sanitizer($_POST['subject']);
    $_POST['comments'] = input_sanitizer($_POST['comments']);
    $_POST['captcha'] = input_sanitizer($_POST['captcha']);
  
    //check if the name field is empty.
    if(empty($_POST['name']))
    {
      $error = true;
      $name_error_message = '<span class="error">Name is required.</span>';
    }

    //check if the name field only contains letters and spaces.
    else if (!preg_match("/^[a-z\040]*$/i",$_POST['name']))
    {
      $error = true;
      $name_error_message = '<span class="error">Only letters and spaces are allowed.</span>';
    }
    
    //check if the email field is empty.
    if(empty($_POST['email']))
    {
      $error = true;
      $email_error_message = '<span class="error">Email is required.</span>';
    }
	
    //check if the e-mail address is well-formed.
    else if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL))
    {
      $error = true;
      $email_error_message = '<span class="error">Please enter a valid email address.</span>';
    }
	
    //check if the subject field is empty.
    if(empty($_POST['subject']))
    {
      $error = true;
      $subject_error_message = '<span class="error">Subject is required.</span>';
    }
    
    // check if the subject field only contains letters and spaces.
    else if (!preg_match("/^[a-z\040]*$/i",$_POST['subject']))
    {
      $error = true;
      $subject_error_message = '<span class="error">Only letters and spaces are allowed</span>';
    }
    
    //check if the comments field is empty.
    if(empty($_POST['comments']))
    {
      $error = true;
      $comments_error_message = '<span class="error">Comments is required.</span>';
    }
    
    //insert another check to limit what characters users can input into the comments field.


    //check if the captcha field is empty.
    if (empty($_POST['captcha']))
    {
      $error = true;
      $captcha_error_message = '<span class="error">Captcha is required.</span>';
    }
    
    //check if the captcha field only contains positive negative or decimal numbers.
    else if(!preg_match("/-?\d\.*/",$_POST['captcha']))
    {
      $error = true;
      $captcha_error_message = '<span class="error">The captcha field can only contains positive, negative or decimal numbers.</span>';
    }

    //check if the stored answer is identical to the users response.
    //(float)$_POST['captcha'] and (integer)$_POST['captcha'] forces $_POST['captcha']
    //to be evaluated as a whole number or as a decimal number instead of as a string.
 
    else if ($_SESSION['total'] !== (integer)$_POST['captcha']
    and $_SESSION['total'] !== (float)$_POST['captcha'])
    {
      $error = true;
      $captcha_error_message = '<span class="error">You answered the question incorrectly.</span>';
    }

    else if($error === false and $sent === false)
    {
      //add the mail() function to actually send the mail.
      
      $sent = true;
      $conformation_message = '<p class="sent">Message Sent.</p>';
    }

  }
  
  if(!isset($_POST['submit']) or $error === true or $sent === true)
  {
    //Generate Random Numbers
    $value_1 = rand(1,100);
    $value_2 = rand(1,100);
    $value_3 = rand(1,4);

    switch ($value_3)
    {
      case 1:
        $question = "What is" ." ". $value_1 . " / " . $value_2 . " Rounded to 2 decimal places ? ";
        $answer = round($value_1 / $value_2, 2);
        break;
 
      case 2:
        $question = "What is" ." ". $value_1 . " * " . $value_2 . " ? ";
        $answer = $value_1 * $value_2;
        break;
 
      case 3:
        $question = "What is" ." ". $value_1 . " + " . $value_2 . " ? ";
        $answer = $value_1 + $value_2;
        break;

      case 4:
        $question = "What is" ." ". $value_1 . " - " . $value_2 . " ? ";
        $answer = $value_1 - $value_2;
        break;
    }

    //Store/Update The Answer To The Question.
    $_SESSION['total'] = $answer;
  }
?>

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Contact form test</title>

<style>
.error{color:red;}
.sent{color:green;}
</style>

</head>

<body>
<main>

<h1>Contact</h1>

<form action="test2.php" method="post" name="contact">

<p>Name: <?php echo $name_error_message; ?>
<!-- Prevent the browser from deleting the contents of the field if the form didn’t send due to an error. -->
<br><input type="text" name="name" <?php if(isset($_POST['name']) and $error === true){echo'value="'.$_POST['name'].'"';}?>></p>

<p>Email Address: <?php echo $email_error_message; ?>
<!-- Prevent the browser from deleting the contents of the field if the form didn’t send due to an error. -->
<br><input type="text" name="email" <?php if(isset($_POST['email']) and $error === true){echo'value="'.$_POST['email'].'"';}?>></p>

<p>Subject: <?php echo $subject_error_message; ?>
<!-- Prevent the browser from deleting the contents of the field if the form didn’t send due to an error. -->
<br><input type="text" name="subject" <?php if(isset($_POST['subject']) and $error === true){echo'value="'.$_POST['subject'].'"';}?>></p>

<p>Comments: <?php echo $comments_error_message; ?>
<!-- Prevent the browser from deleting the contents of the field if the form didn’t send due to an error. -->
<br><textarea name="comments"><?php if(isset($_POST['comments']) and $error === true){echo $_POST['comments'];}?></textarea></p>

<p><?php echo $question; echo $captcha_error_message; ?>
<br><input type="text" name="captcha"></p>

<p><input type="submit" name="submit" value="Submit"></p>

<?php if(isset($_POST['submit']) and $sent === true){echo $conformation_message;} ?>

</form>
</main>
</body>
</html> 
While this is a reasonable start I am aware that there are still a few issues with it, and there are some additional things that can be done to make it harder to attack.

one suggestion I read about was generating and setting a session token and then checking for the presence of that token upon submunition, and if its wrong or not present then you can assume its a hacking attempt and then kill the script and log the attempt.

another suggestion I read about was to use a randomly placed hidden bate field to trip up bots and just like the previous idea you can check to see if that field has been messed with and if so then you can assume its a hacking attempt and kill the script and log the attempt.

Now I know this contact form isn’t complete and won't actually send email just yet because there are still a couple of bits missing this is deliberate because I'm not at that point yet, I wanted to try and figure out the form security first because it's important and if you don’t do it correctly then that can lead to all sorts of nastiness like for example header injection which would potentially allow spammers to hijack the form.

Or if I was doing say a login form then something like cross site request forgery attacks could potentially allow an attacker to gain unauthorized access to stuff that is supposed to be protected like peoples credit card numbers for example.

And of course lets not for get about SQL injection type attacks if the form is attached to a back end database, again if you don’t set up your form security correctly then potentially that could lead to all sorts of issues like people having there details stolen, the database being deleted, maybe even the whole site being deleted to name but a few.

More over allowing these sort of things to occur is an offence that can potentially get you sacked, so I figure it's well worth my time to learn how to do this stuff correctly since this is not something that I covered extensively during my HND, and therefore its not something that I currently understand well.

now obviously if you are only talking about a contact form then there is no database involvement which eliminates 1 set of issues, but there are still various other issues that can occur even with something as common and relatively simple as a contact form, one of which as I mentioned above is header injection which my current code doesn't protect against, another is XSS or Cross Site Scripting attacks which the input_sanitizer function should take care of so long as all inputs are ran through it to remove any unexpected input, and of course doing input validation and making sure that the mail wont send if the form has any errors helps in this with this as well.

So like I said I'm simply looking to learn how to lock down any forms that I crate to make it as hard as possible for other people to attack them.
User avatar
Frenzie
Posts: 2135
Joined: May 5th, 2004, 10:40 am
Location: Belgium
Contact:

Re: PHP coding question 2

Post by Frenzie »

The PHP manual is usually a good place to look, although sometimes obtuse.

http://php.net/manual/en/intro.filter.php
http://phpsec.org/projects/guide/
https://github.com/padraic/phpsecurity
Intelligent alien life does exist, otherwise they would have contacted us.
Locked