Forum Moderators: coopster

Message Too Old, No Replies

Alternative to PHP contact forms: clever or dumb?

How's this for an alternative to a hack-vulnerable PHP form?

         

markis

12:05 am on Nov 13, 2008 (gmt 0)

10+ Year Member



OK, so my PHP contact form got hacked today. (Sorry to all.) I know now that I shouldn't have relied only on a CAPTCHA. My webhost shut down my email sending privileges (temporarily?), and I deserve it. My bad -- but, moving on ...

I want to make a webform that is 100% un-hackable. I'm not as worried about incoming spam, but I'm very against the idea of spammers doing what they did -- sending junk to thousands using MY server. And since any PHP script that sends an email is vulnerable (at least a tiny little bit, right?), here's what I dreamed up:

1. User fills out a form, as usual, and the data is submitted to a processing script, same as before.

2. The processing script error checks the input and finds any spam it can.

3. Here's the twist: the script doesn't email anything to anyone -- it just writes the results to a text file and closes up shop. No email, 0% chance of an email hack.

4. I check the text file instead of my email. I could have a permanent link in my email account, a link on my desktop, whatever. I could even make a little view / delete page for myself if I wanted to.

There could also be a separate script sitting on the server that runs every little while, checking the text file for new entries and alerting me if it finds one. But it's not really necessary -- it would be pretty easy to just have a link sitting in my webmail.

Clever, dumb, or already common practice? Please be honest. I won't get hurt feelings.

Thanks,

- m.

Anyango

4:50 am on Nov 13, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Try to figure out if they used your "mail server" directly for sending those junk emails or used your contact form ? i doubt it was the form itself but you need to check that out. And if they used the mail server directly then you need to secure that first.

markis

5:10 am on Nov 13, 2008 (gmt 0)

10+ Year Member



Thanks, Anyango --

How do I tell if they've used the mail server directly? How do I stop them?

I'm with 1and1, BTW, and I'm on a shared server with lots of others.

I thought they were just making requests to the contact form with various character included to hijack it and fill out bcc, cc, etc. No?

Anyango

6:29 pm on Nov 13, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Do you perform any checks on the inputs ? Your idea seems good but killing something aint its solution, for me atleast. check your apache logs to see what they sent to your script to find out any pattern and then change your code to block such stufff.

markis

12:15 am on Nov 14, 2008 (gmt 0)

10+ Year Member



Yeah, I've definitely been parsing the input for what I can think of. But, from what I've read, it'll never be failsafe, and I'm guessing the spammers will usually be about 1/2 a step ahead. Especially since security is not my strong suit.

I'm going to try implementing this and see how it goes, pain-in-the-butt wise. It won't be hard. I'm pretty sure it'll be just fine -- I've got some ideas about how to "virtually" send an email without actually sending one. (Write to the text file, close up shop, then have the "thank-you" page just email a link to the text file without accepting and POST data. The txt file also acts as an email log ... )

Thanks again. Any tips on how to check if they've used the mail server directly?

vincevincevince

4:33 am on Nov 16, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Please, please, write the data (properly checked and escaped) to a database; or at least to a file above the web root.

SPAM emails hurt your reputation and your host. Allowing hackers to write something to a file in your hosting account is potentially much much more damaging.

markis

5:29 am on Nov 16, 2008 (gmt 0)

10+ Year Member



Uh oh. How could a text file be more damaging? I thought it was fail-safe ... what could they do to damage anything?

To clarify -

- The text file isn't used for anything. It just sits on the server and receives updates.
- The text file is in a password-protected directory, so people can't get email addys from it. (Right?)
- If someone put a credit card number in it (unlikely) I would just log on and erase it.

Right now the data isn't checked or escaped, because I'm curious about what exactly they're trying to type in the form! But once the "voyeur" novelty wears off, I'll do both.

vincevincevince, please tell me how they could do damage through writing to a text file!

vincevincevince

5:45 am on Nov 16, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



markis - please please be careful - unless you know server security inside out. Most successful server attacks I see documented start by finding some way to write something to a file on the server. That's a very powerful attack vector.

markis

6:07 am on Nov 16, 2008 (gmt 0)

10+ Year Member



I see ... except what could they possibly write that would be dangerous? They're not creating a new file -- they're writing to an existing file, and since they're filling out a form, the only thing they can do is write. And since it's in a protected directory, they couldn't even open it if they somehow found out the filename.

Since the file is never executed or used in a program, any malicious data will just sit there idle ... right?

I should mention that my site is almost all "sit-back" content, with the exception of web forms which collect data like I mentioned for my viewing. No forums, usernames, credit info, ... no uploading possibilities, except what we're talking about now.

Anyango

7:52 am on Nov 16, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I agree with vincevincevince

And also note Markis that saving all those texts in a database would make it easier for you to manage them and use a nice simple interface and be easily able to delete them or clean your table up. Imagine receiving 1000 spam comments and going to your text file to be able to delete them and in between those thousand comments there were 2 good ones. Its really tough. So His idea of using a database is great in this respect too.

daveginorge

10:13 am on Nov 16, 2008 (gmt 0)

10+ Year Member



A while ago I read a long forum on this problem. The results were that nothing will be 100% spam proof but the ideas were good to reduce the chances.
Main ideas I liked were

1. Do not label you email address fields email or such like.
2. Have a css hidden field and check that the field is empty when validating data, only the bots will complete that. Even call the hidden field email or such like to attract it to the bots.
3. Encrypt all text for the field labels using a javascript routine to make it only human readable. Place that code in a .js file so it is not available in the source code. (Not liked by some because of the use of Javascript and the user can have it disabled.
4. Encrypt all email addresses the same way making them only human readable
5. Validate all data.
6. Check your server logs on a regular basis.

The conclusion was expect to get hacked just try and hinder them as much as possible.

Anyango

12:15 pm on Nov 16, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member




2. Have a css hidden field and check that the field is empty when validating data, only the bots will complete that. Even call the hidden field email or such like to attract it to the bots.

That is some COOOOL thinking! I like it ;)

londrum

2:37 pm on Nov 16, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



you'd have to be sure they could only write so much in a single session though. otherwise they could write potentially limitless amounts of data into your text file. that might result in a huge file (not just big, but GIGANTIC)

sure, it might not actually do anything dangerous -- but the size of it could cause a problem.

markis

5:37 pm on Nov 16, 2008 (gmt 0)

10+ Year Member



Thanks everybody!

I'm going to stick with a text file for now, though I'll consider the database if the number of comments gets out of hand. I'd have to be careful of SQL injection and suchlike, though.

londrum, I'm going to implement your idea right after I finish writing this. That's something I'm not checking for. (Just a max-length on the form fields, right?)

Some things I've noticed about the 'hackers' already:

- They're people, not bots. They have no problem with the CAPTCHA, and they always write a different (fairly silly) comment in the comment box, making it clear the English is NOT their first language. If it were a bot, the comment would have been proof-read, at least a little bit.

- They usually write a 'test' form first, to give a potential bot an idea of which fields to fill out. (In this case, the 'honey-pot' field won't work, as the bot will skip it.) In fact, most of the emails I get are test forms. The bots fall down on the CAPTCHA, even though it's a very simple one.

- They have little interest in spamming me. Once they fill out a form and it doesn't send an email, or post to the guestbook, or whatever they were hoping for, they give up. Actually, somewhat disappointingly, I haven't gotten a spam in almost 2 days. I was hoping to do a real study here!

In addition to the comments above, I think it's important to check the user's IP addy on form-fillout and again on processing. This prevents bots from submitting pre-stuffed form data directly to the processing script, essentially forcing the user to actually fill out the form.

Thanks again, everyone! Again, if anyone has any CONCRETE ideas about how these txt files could be used for an attack, please write in!

MWpro

6:18 am on Nov 17, 2008 (gmt 0)

10+ Year Member



2. Have a css hidden field and check that the field is empty when validating data, only the bots will complete that. Even call the hidden field email or such like to attract it to the bots.

Brilliant!

londrum

1:19 pm on Nov 17, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



londrum, I'm going to implement your idea right after I finish writing this. That's something I'm not checking for. (Just a max-length on the form fields, right?)

that would work for 99.9% of people, but it wouldn't stop the one hacker you need to keep out -- because he doesn't have to go through your form. he can just write his own form and send it to your processing page. (by amending his 'action' attribute in the form tag.)

that's why you should never trust any of the data that reaches your processing page, because it might not have even come from your site.

to be truly safe you need to check it with some kind of server side script (PHP, ASP etc) and only write it to the file after that.

markis

4:53 pm on Nov 17, 2008 (gmt 0)

10+ Year Member



Yeah, I thought about that a little harder and decided that was dumb ... I'm just trimming the post data instead. I'll implement max-lengths or a textbox counter warning later just to let the honest user know how much is too much.

How's an allowance of 80 characters for emails / names, and 2500 characters for comments sound?

londrum

8:48 pm on Nov 17, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



sounds like it's going to be just as much work as doing it the normal way. i think you're worrying too much after your bad experience. you just need a secure form.

here is the one that i use. it's pretty secure i think, and works okay for me. i can't recall the last time i even got one piece of spam through it.
it checks all the data server-side and even spits out some warning messages next to the boxes which the user has got wrong.

if you decide to try it out, you won't be able to visit the page directly -- that is one of the security features. any one who tries to visit the page without going through another page on your site will just be presented with an error message.

so you will have to link to it on another page, and visit it that way.

you will notice that it deposits a cookie onto the bad guys system if it trips one of the traps (this probably wont matter for spambots, but will catch out humans). you can then check for the existence of that cookie on the same page, or any other page on your site, and send them packing.

<?php

// things to note -- there is a hidden input file at the end of the form. you can make this into a normal one and hide it via CSS if you want. if a spambot fills it in, then it automatically gets rejected

// this first bit will make sure that the any visitors to the contact page definitely came through the site

if(($_SERVER['SCRIPT_NAME']) == '/contact_form.php')
{ // you may have to amend this so it points to this same file
if (!isset($_SERVER['HTTP_REFERER'])) {
$_SERVER['HTTP_REFERER'] = 'http://www.example.com/'; }
if ((isset($_SERVER['HTTP_REFERER'])) && ($_SERVER['HTTP_REFERER']!= '')) {
$referring_url = parse_url($_SERVER['HTTP_REFERER']);
$lowercase_url = strtolower($referring_url['host']);
if($lowercase_url != 'localhost' && $lowercase_url != 'www.yourwebsite.com' && $lowercase_url != 'yourwebsite.com') { // this list contains all of the URLs which are allowed to access the form -- add or delete as many as you need
header('HTTP/1.1 403 Forbidden');
print("<html><head>\n");
print("<title>Error</title>\n");
print("<meta name=robots content=\"noindex,nofollow,noarchive\">\n");
print("</head><body>\n");
print("<p>[9] This page has been left intentionally blank</p>\n");
print("</body></html>");
exit;
} } }

// process the email
if (array_key_exists('send', $_POST)) {

$success = FALSE;

// list expected fields
$expected = array('name', 'email', 'comments');
// set required fields
$required = array('name', 'email', 'comments');
// create empty array for any missing fields
$missing = array();

// assume that there is nothing suspect
$suspect = false;
// create a pattern to locate suspect phrases
$pattern = '/url=¦Content-Type:¦Bcc:¦Cc:/i';

// function to check for suspect phrases
function isSuspect($val, $pattern, &$suspect) {
// if the variable is an array, loop through each element
// and pass it recursively back to the same function
if (is_array($val)) {
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
} }
else {
// if one of the suspect phrases is found, set Boolean to true
if (preg_match($pattern, $val)) {
$suspect = true;
} } }

// check the $_POST array and any sub-arrays for suspect content
isSuspect($_POST, $pattern, $suspect);

if ($suspect) {
$mailSent = false;
unset($missing); }
else {
// process the $_POST variables
foreach ($_POST as $key => $value) {
// assign to temporary variable and strip whitespace if not an array
$temp = is_array($value) ? $value : trim($value);
// if empty and required, add to $missing array
if (empty($temp) && in_array($key, $required)) {
array_push($missing, $key);
}
// otherwise, assign to a variable of the same name as $key
elseif (in_array($key, $expected)) {
${$key} = $temp;
} } }

// validate the email address
if (!empty($email)) {
// regex to ensure no illegal characters in email address
$checkEmail = '^[_\\.0-9a-z-]+@([0-9a-z][0-9a-z-]+\\.)+[a-z]{2,6}$^';
// reject the email address if it doesn't match
if (!preg_match($checkEmail, $email)) {
array_push($missing, 'email');
} }

// reject as spam if the hidden field has been filled in, and writes the spambot to a blacklist file
if ($_POST['user_info'] != '') {
setcookie("uid", md5(rand()), time()+86400*365, "/");
header('HTTP/1.1 403 Forbidden');
sleep(20);
$badbot = 0;
/* scan the security-log file to see if it's already been added, to prevent filling it up with duplicates */
/* you will have to amend the security_file.txt URL so it points to wherever the file is */
$fp = fopen ('/security_file.txt', 'r', true) or die ("Error opening file...\n");
while ($line = fgets($fp, 255)) {
$u = explode(" ", $line);
if (ereg($u[0], $users_IP_address)) {
$badbot++;
} }
fclose($fp);
if($badbot == 0) {
if(!isset($HTTP_REFERER)) {
$HTTP_REFERER = 'Unknown'; }
if(!isset($HTTP_USER_AGENT)) {
$HTTP_USER_AGENT = 'Unknown'; }
/* we've just see a new bad bot not listed! so send a mail to the webmaster */
$tmestamp = time();
$datum = date("d-m-Y H:i", $tmestamp);
$from = "alert@example.com"; // this doesn't have to be a real address.
$to = "info@example.com"; // you will have to change this to whatever your email address is
$subject = "Comment form spammer alert";
$msg = "A comment form spammer just hit $REQUEST_URI $datum \n";
$msg .= "address is $users_IP_address, user_agent is $HTTP_USER_AGENT\n";
@mail($to, $subject, $msg, "From: $from");
/* append bad bot address data to blacklist log file */
$fp = fopen($toppath.'/security_file.txt', 'a+', true);
fwrite($fp, "$users_IP_address - - [$datum] - - [SPAMMED THE CONTACT FORM] -- $REQUEST_METHOD $REQUEST_URI - - $HTTP_REFERER - - $HTTP_USER_AGENT\n");
fclose($fp); }
print("<html><head>\n");
print("<title>Error</title>\n");
print("<meta name=robots content=\"noindex,nofollow,noarchive\">\n");
print("</head><body>\n");
print("<p>Error &ndash; You spammed the comment form</p>\n");
print("</body></html>");
exit;
}

// go ahead only if not suspect and all required fields OK
if (!$suspect && empty($missing) && $_POST['user_info'] == '') {

// limit line length to 70 characters
$comments = wordwrap($comments, 70);

// send it -- remember to change the email address to your own one!
@mail("info@example.com", "Feedback from contact form", "$comments", "From: $name<$email>");
// $missing is no longer needed if the email is sent, so unset it
unset($missing);
$success = TRUE;
} }

?>

<title>Contact form</title>

<h1>Contact form</h1>

<form action="contact_form.php" method=post><fieldset>

<legend>Contact form</legend>

<?php
if ($_POST && isset($missing)) {
echo'<p>Your message has not been sent.</p>';
}
elseif ($_POST && !$success) {
echo'<p>Sorry, but there was a problem sending your message. Please try again later.</p>';
$success = TRUE;
}
elseif ($_POST && $success) {
echo'<p>Thankyou -- Your message has been sent.</p>';
}
global $success; if ($success != TRUE) {
?>

<label for=name>Your name: <?php if (isset($missing) && in_array('name', $missing)) {
echo'<strong>Please enter your name</strong>'; }
if(isset($_POST['name']) && strlen($_POST['name']) > 35){
echo'<strong>Your name is too long</strong>';} ?></label><br>

<input name=name id=name type=text size=30 maxlength=35 <?php
if (isset($missing)) { echo 'value="'.htmlentities($_POST['name']).'"'; } ?>><br>

<label for=email>Your email address: <?php if (isset($missing) && in_array('email', $missing)) {
echo'<strong>Please enter a valid email address</strong>'; }
if(isset($_POST['email']) && strlen($_POST['email']) > 50){
echo'<strong>Your email address is too long</strong>';} ?></label><br>

<input name=email id=email type=text size=40 maxlength=50 <?php
if (isset($missing)) { echo 'value="'.htmlentities($_POST['email']).'"'; } ?>><br>

<label for=comments>Your message: <?php if (isset($missing) && in_array('comments', $missing)) {
echo'<strong>Please write your message</strong>'; }
if(isset($_POST['comments']) && strlen($_POST['comments']) > 3500){
echo'<strong>Your message is too long</strong>';} ?></label><br>

<textarea name=comments id=comments cols=60 rows=8><?php
if (isset($missing)) { echo htmlentities($_POST['comments']); } ?></textarea><br>

<input type=hidden id=user_info name=user_info value="<?php
if(isset($_POST['user_info'])) { echo $_POST['user_info']; } ?>">

<input name=send id=send type=submit value=" Send ">

<?php } ?>

</fieldset></form>

[edited by: jatar_k at 5:24 pm (utc) on Nov. 18, 2008]
[edit reason] Re-formatted code and fixed user_info [/edit]

markis

2:58 am on Nov 18, 2008 (gmt 0)

10+ Year Member



Wow! Thanks londrum! Actually, I've already implemented everything (wasn't too hard -- maybe 2 hours) and it works like a charm. 3 days, 0 spam, 0 hacking attempts -- what puzzles me is how they knew I changed something? I was getting a dozen spam and at least 1 attempt at mass mailing per day ... go figure. It's kind of disappointing actually. I was hoping to learn a little something from the input attempts.

HOWEVER, I haven't completed all the security measures I plan to put in place yet, and I will definitely use parts of your code for that. I agree, it looks very thorough. Also, I'm sure many people will trip over this forum with the same problem and make good use of your script!

I'm just sticking with the text file to try a little something different. Who knows -- I may come up with something useful. Don't know until you try, right?

One question I have about the honeypot fields: if they're hidden via CSS or just as a "hidden" form element, is it possible that a browser may "auto-fill" them? (Firefox, or some lesser browser with time-saving intentions.) How good are major browsers at ignoring hidden fields?

Anyango

4:21 am on Nov 18, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member




HOWEVER, I haven't completed all the security measures I plan to put in place yet,

Hey Markis,

Next time FBI people announce a job, go for it !

Kidding, Cheers:)

daveginorge

7:37 am on Nov 18, 2008 (gmt 0)

10+ Year Member



One question I have about the honeypot fields: if they're hidden via CSS or just as a "hidden" form element, is it possible that a browser may "auto-fill" them? (Firefox, or some lesser browser with time-saving intentions.) How good are major browsers at ignoring hidden fields?

Isn't it the case that they will prompt you for the input by dropping down the available values after you have taken focus to the field and started to input or click with the mouse, as this will not be the case then they should be OK. Completely filled fields like "Username" are normally coded in by the programmer from a cookie or suck like. I always use css hidden as I think the bots might be looking at the <tag> type="text" and ignore a type="hidden" but I'm no expert on this subject only the spammers are.

I also use my hidden field directly above the field it's masquerading ie.

Please enter your email address
<div class="email-h"><input type="text" name="email" /></div>
<div class="email"><input type="text" name="value1" /></div>

A -h on my classes means I have the <div> hidden for some reason.
By doing this I think that if the form is handled by humans the first time they will map the fields for the bot to use. They will only catch the error if they look at the source code.

I like this idea of checking the referrer is your own domain.

if you decide to try it out, you won't be able to visit the page directly -- that is one of the security features. any one who tries to visit the page without going through another page on your site will just be presented with an error message.

I will definitely be using this idea.

you will notice that it deposits a cookie onto the bad guys system if it trips one of the traps (this probably wont matter for spam bots, but will catch out humans). you can then check for the existence of that cookie on the same page, or any other page on your site, and send them packing.

g1smd

2:08 pm on Nov 18, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I recently looked at a site with an unprotected email script, and was contemplating looking up something better, when I just happened to notice this thread as the "next oldest" from one that I replied to in another topic area. Thanks for posting it. I'll give it a go soon.

londrum

2:59 pm on Nov 18, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



actually, i've just noticed a stupid little typo in the code -- i'm glad i posted it now or i wouldn't have spotted it.

in the honeypot field i've put a bit saying user_name, when it should say user_info.