Forum Moderators: coopster
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.
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?
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?
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!
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.
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.
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.
sure, it might not actually do anything dangerous -- but the size of it could cause a problem.
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!
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.
How's an allowance of 80 characters for emails / names, and 2500 characters for comments sound?
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') // process the email $success = FALSE; // send it -- remember to change the email address to your own one! ?> <title>Contact form</title> <h1>Contact form</h1> <form action="contact_form.php" method=post><fieldset> <legend>Contact form</legend> <?php <label for=name>Your name: <?php if (isset($missing) && in_array('name', $missing)) { <input name=name id=name type=text size=30 maxlength=35 <?php <label for=email>Your email address: <?php if (isset($missing) && in_array('email', $missing)) { <input name=email id=email type=text size=40 maxlength=50 <?php <label for=comments>Your message: <?php if (isset($missing) && in_array('comments', $missing)) { <textarea name=comments id=comments cols=60 rows=8><?php <input type=hidden id=user_info name=user_info value="<?php <input name=send id=send type=submit value=" Send "> <?php } ?> </fieldset></form>
{ // 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;
} } }
if (array_key_exists('send', $_POST)) {
// 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 – 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);
@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;
} }
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) {
?>
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>
if (isset($missing)) { echo 'value="'.htmlentities($_POST['name']).'"'; } ?>><br>
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>
if (isset($missing)) { echo 'value="'.htmlentities($_POST['email']).'"'; } ?>><br>
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>
if (isset($missing)) { echo htmlentities($_POST['comments']); } ?></textarea><br>
if(isset($_POST['user_info'])) { echo $_POST['user_info']; } ?>">
[edited by: jatar_k at 5:24 pm (utc) on Nov. 18, 2008]
[edit reason] Re-formatted code and fixed user_info [/edit]
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?
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.