CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, or have issues with using the registration form, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: Securing forms - local formkeys or reCAPTCHA  (Read 1879 times)

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Securing forms - local formkeys or reCAPTCHA
« on: 29 Jan 2020, 03:39:45 pm »
Below is a sample form:
Code: [Select]
function contact_ShowForm() {

$content = "
<form method=\"post\" action=\"?module=Contact\">

<h1>Contact Us</h1>

<p>So you want to drop us a line - then please just fill out the form below and we will get back to you...</p>

<input type=\"hidden\" name=\"MODE\" value=\"01\">

<p><input type=\"text\" name=\"name\" value=\"{NAME}\" placeholder=\"Your Name\"></p>

<p><input type=\"text\" name=\"email\" value=\"{EMAIL}\" placeholder=\"Your Email\"></p>

<p><textarea name=\"message\" rows=\"4\" cols=\"50\" placeholder=\"Your Message\">{MESSAGE}</textarea> </p>

<p><input type=\"checkbox\" name=\"privacy\" value=\"1\">I agree for my personal information to be process so that they can carry out the request I made.</p>

<p><button>Send Message</button></p>

</form>\n";

// FORM Defaults OR User input...:
$content = preg_replace("/{NAME}/", ( ! empty($_POST['name'] ) ? $_POST['name'] : "" ), $content );
$content = preg_replace("/{EMAIL}/", ( ! empty($_POST['email'] ) ? $_POST['email'] : "" ), $content );
$content = preg_replace("/{MESSAGE}/", ( ! empty($_POST['message'] ) ? $_POST['message'] : "" ), $content );

}

How would you go about securing the form, by Just using a "local" form key? or reCaptcha? or both? or another method?

Oh I know I need to sort the HTML "'s out
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #1 on: 29 Jan 2020, 05:08:12 pm »
Should I also make them confirm the email address - double type it? for both registration and the contact form?
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1054
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #2 on: 29 Jan 2020, 05:21:29 pm »
Generally a random hash matched to the session is sufficient for MOST forms. Captcha I'd add if it were a forum or open posting system, but if it's for contact mails and the like

Now that said... that's EXACTLY the type of code I'm often talking about where it's the pinnacle of bad practices, often out of some noodle-doodle "template" nonsense that forgets PHP is a template system.

1) Just echo the bloody thing instead of wasting memory on a variable for nothing.

2) If you used single quotes for the string, you wouldn't need to escape all your doubles.

3) if you echo'd you'd be able to comma delimit which would be faster than string additions.

4) It would also mean axing all those painfully slow and memory/cpu wasting regex.

5) It's highly unlikely you're at H1 depth from a logical document structure standpoint.

6) PLACEHOLDER IS NOT A LABEL!!! I don't care how many artsy-fartsy types cream their shorts over it, USE BLOODY LABELS!!! That is NOT what placeholder is for!!!

7) Likewise where's your fieldset? You know, the marker of which fields are user-interactable as a group?

8) What the blazes makes a single INPUT tag a grammatical paragraph?

9) you didn't htmlspecialchars your $_POST, opening the door to hackers doing script injections.

If I were writing that form, the markup would go something more like this:

Code: [Select]
function contact_postValue($index) {
return empty($_POST[$index]) ? '' : '
value="' . htmlspecialchars($_POST[$index]) . '"';
}

function contact_ShowForm() {

echo '
<form method="post" action="?module=Contact" id="contact">
<h2>Contact Us</h2>
<p>
So you want to drop us a line - then please just fill out the form below and we will get back to you...
</p>
<fieldset>
<label for="contact_name">Your Name</label><br>
<input
type="text"
name="name"
id="contact_name"',
contact_postValue('name'), '
><br>
<label for="contact_mail">Your E-Mail</label><br>
<input
type="email"
name="email"
id="contact_mail"',
contact_postValue('email'), '
><br>
<label for="contact_message">Your Message</label><br>
<textarea
name="message"
id="contact_message"
rows="4" cols="50"
>', empty($_POST['message']) ? '' : htmlspecialchars($_POST['message']), '</textarea><br>
<input
type="checkbox"
name="privacy"
id="contact_privacy"
value="1"
>
<label for="contact_privacy">
I agree for my personal information to be process so that they can carry out the request I made.
</label>
</fieldset>
<div class="submitsAndHiddens">
<button>Send Message</button>
<input type="hidden" name="contactHash" value="', session_hash('contact'), '">
<!-- .submitsAndHiddens --></div>
</form>';

} // contact_ShowForm

Fixing the broken semantics and nonsensical structure, ditching that garbage "let's add everything to a string so we can regex it" rubbish, and showing where I'd apply the random hash.

session_hash is a function I have that just generates a random hash, stores it in $_SESSION via the name passed as an argument to the function, and returns said hash. I have a second routine session_verify that you pass the same name and it checks if $_POST[$name] == $_SESSION[$name], that it's not blank. If true delete the hash from $_SESSION and return true.




We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1054
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #3 on: 29 Jan 2020, 05:22:38 pm »
Should I also make them confirm the email address - double type it? for both registration and the contact form?

if you use type="email" I'd skip it on the contact form, but on a registration form it's not a horrible idea.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #4 on: 29 Jan 2020, 05:40:20 pm »
Should I also make them confirm the email address - double type it? for both registration and the contact form?

if you use type="email" I'd skip it on the contact form, but on a registration form it's not a horrible idea.
Thanks! :)

Now, correct me if I am wrong, which I probably am, I think I read that you shouldn't just rely on the browser for the input checks - which is why I am only using "text" and not "email"... :)

Thanks again for the valuable feedback you keep providing to me - I will read your other post(s) in the morning as I am sure I have made some changes which you recommended as that was a just a quick and dirty form to get the logic working - as I again I get the thing working and then make it pretty... :)

I really do appreciate the time you take to reply Jason - I hope you can see that! :)
« Last Edit: 29 Jan 2020, 05:42:37 pm by GrumpyYoungMan »
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1054
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #5 on: 29 Jan 2020, 05:43:02 pm »
Now, correct me if I am wrong, which I probably am, I think I read that you shouldn't just rely on the browser for the input checks - which is why I am only using "text" and not "email"... :)
You shouldn't RELY on the client side to provide correct information, so yes check it again server-side. HOWEVER do not fail to leverage it as an aid to the user so they are more prone to get it correct without the submit working.

It's an enhancement... don't skip past good enhancements just because you're checking it on the server when they're built into the language. When you're told not to RELY on them, that doesn't mean don't USE them to make the user experience more pleasant.

Layers, the more of them you have, the better. See the "progressive enhancement" technique -- it's layers. Semantics first, layout second, adjusting what it looks like for size (responsive) third, making it pretty with colors and presenational images fourth, enhancing with scripting fifth. Make the page better and better with distinct layers, so that if any of those layers fail the one below catches it.

Form input should be thought of in the same way. Make it work without scripting or client-side checks FIRST, then enhance it to be more convenient to the user.
« Last Edit: 29 Jan 2020, 05:46:28 pm by Jason Knight »
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #6 on: 30 Jan 2020, 01:16:48 am »
Thanks again for the feedback, I wanted to get the PHP system checks completed before I used the date and email, etc options, so based on your feedback looks like I’m going down the right lines...
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #7 on: 30 Jan 2020, 03:43:43 am »
session_hash is a function I have that just generates a random hash, stores it in $_SESSION via the name passed as an argument to the function, and returns said hash. I have a second routine session_verify that you pass the same name and it checks if $_POST[$name] == $_SESSION[$name], that it's not blank. If true delete the hash from $_SESSION and return true.

I will run through your check list later, but:

Code: [Select]
function formkey_Remove() {

global $DB;

$removeKey = $DB->prepare("
DELETE FROM
{$prefix}formkeys
WHERE
f_key=:key
");

$removeKey->execute([ ':key' => $_POST['formkey'] ] );

}

function formkey_Validate() {

global $DB;

$validateKey = $DB->prepare("
SELECT
f_key
FROM
{$prefix}formkeys
WHERE
f_key=:key
");

$validateKey->execute([ ':key' => $_POST['formkey'] ] );

return empty($validateKey->fetch() ) ? 0 : 1;

}

function formkey_Generate() {

global $DB;

$key = md5(time());

$DB->query("INSERT INTO {$prefix}formkeys ( f_key, f_added ) VALUES ( '$key', NOW() )" );

return "<input type=\"hidden\" name=\"formkey\" value=\"$key\">";

}

Is that good enough?
« Last Edit: 30 Jan 2020, 06:23:15 am by GrumpyYoungMan »
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1054
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #8 on: 30 Jan 2020, 01:47:02 pm »
1) MD5 is insecure trash, and I've no clue why you'd encode a time that way.

2) do NOT insert variables into values, even if you just made it.

3) A random hash should be RANDOM, not based on the current time.

4) bin2hex(random_bytes($howManyBytes)) is a cryptographically secure random hash, use that instead.

5) why are you returning markup? Again, wasteful nonsense, don't do that.

6) It should be stored in the session, not the database. Huge waste of time coding.

Code: [Select]
function sessionHash_make($name) {
$_SESSION[$name] = bin2hex(random_bytes(24)); // 48 characters hex is overkill
} // sessionHash_make

function sessionHash_postCheck($name, $delete = true) {
if (
empty($_SESSION[$name]) ||
empty($_POST[$name])
) return false;
$value = $_SESSION[$name];
if ($delete) unset($_SESSION[$name]);
return $value === $_POST[$name];
} // sessionHash_postCheck

In your form output -- which should be the tempate not in the logic:

Code: [Select]
echo '
<input
type="hidden"
name="contactHash"
value="', sessionHash_make('contactHash'), '"
>';

And when it's time to check it when you get the submit from the form:

Code: [Select]
if (sessionHash_postCheck('contactHash') {

Even if you use a database for your sessions -- you should still code it to use $_SESSION to expedite things. Particularly in the case of things like guests on things like a contact form.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #9 on: 30 Jan 2020, 02:10:18 pm »
As it was only to stop spam and duplicate entries I didn’t think it would cause much harm just using md5 and the time.

Also I was just bathing my middle child and thought that it would be better served and more secure being tied to the session id -  As you posted earlier!

Am I making you pull your hair out Jason? (If you have any?!)

Will hopefully get to sit down and make the changes and show you what I have done.

Thanks again - and as always I really do appreciate the feedback.
« Last Edit: 31 Jan 2020, 01:18:56 am by GrumpyYoungMan »
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #10 on: 11 Dec 2020, 03:43:50 am »
Sorry for resurrecting such an old topic!

I have tweaked the originally posted code sample by Jason to this:
Code: [Select]
function token_Create($name) {
    $_SESSION[$name] = bin2hex(random_bytes(24)); // 48 characters hex is overkill
    echo '<input type="hidden" name="hash_',$name, '" value="', $_SESSION[$name], '">';
}

function token_Check($name, $delete = true) {
    if (
        empty($_SESSION[$name]) ||
        empty($_POST['hash_' . $name])
    ) return false;
    $value = $_SESSION[$name];
    if ($delete) unset($_SESSION[$name]);
    return $value === $_POST['hash_' . $name];
}

As in the version which was originally posted the hash return was forgotten so it wasn't being added to the form - I also made the input element be outputted by the function rather than just return the hash.

So to get a form token you just need to:
Code: [Select]
token_Create('login')
and to validate:
Code: [Select]
if( ! token_Check('login') ) { exit("<h2>HACK</h2>"); }
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1054
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #11 on: 11 Dec 2020, 06:51:37 am »
For your purposes that does indeed look good. Not sure I'd hardcode markup into the function as that's a violation of that pesky "separation of concerns", but should work for you.

Am I making you pull your hair out Jason? (If you have any?!)
Amazingly unlike most of the guys half my age here in Keene, I still have a full head of hair. I'm not from Keene though I've got family in the area (Chesterfield).

I swear with both Keene and Winchester, the gene pool doesn't run very deep iffn'ye catch my drift.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

GrumpyYoungMan

  • Hero Member
  • *****
  • Posts: 792
  • Karma: +8/-0
    • Grumpy Young Man
Re: Securing forms - local formkeys or reCAPTCHA
« Reply #12 on: 11 Dec 2020, 09:07:05 am »
For your purposes that does indeed look good. Not sure I'd hardcode markup into the function as that's a violation of that pesky "separation of concerns", but should work for you.

Am I making you pull your hair out Jason? (If you have any?!)
Amazingly unlike most of the guys half my age here in Keene, I still have a full head of hair. I'm not from Keene though I've got family in the area (Chesterfield).

I swear with both Keene and Winchester, the gene pool doesn't run very deep iffn'ye catch my drift.
Thanks, Ill revert it back to the way it was posted, but Ill just add the return... :) I took that as a test... :)
Trying to learn a new trick to prove old dogs can learn new ones...

Total Novice have-a go Amateur Programmer - not sure that is the right thing to say... but trying to learn...

 

SMF spam blocked by CleanTalk

Advertisement