SuperWebDeveloper.com

June 17, 2008

Protect weak passwords with login trap

Filed under: — Tags: , , — pbg @ 9:37 pm

f one thing is for sure, the weaker the user, the weaker the password they use. Its a disaster waiting to happen. I decided to shore things up on a site I take care of just so I can sleep at night. It has lots of users, but weak ones. Many sites out there already have safeguards in place to forbid further login attempts if you keep failing. If you let users own their passwords, and you probably do, there are no doubt some weak ones that could fall to some kind of rainbow attack if you allow an attacker to keep trying. There should be more than one example of this process on the web to compare this to.

It would work like this:

  • set a number of allowed login attempts.
  • set the time limit in seconds for duration of access denial.
  • keep track of the number of failed login attempts.
  • keep track of when login attempts started with timestamp function.
  • test for meeting or exceeding the number of allowed login attempts.
  • let them keep trying if they have waited past the time limit.
  • set a time limit for when they can come back, and forbid them.
  • give them some messages and links to help.
  • if the login has been successful, wipe out all the tracking for login attempts.
  • You are done.

Here we go, into your login processor after initial validation and constructing a sql query.

$loginAttemptsAllowed = 5;
if( $_SESSION['loginAttempt']['Count'] <= $loginAttemptsAllowed ) {
  $result = $db->queryRow($sql); // only query db if allowed to do so
}
if( !$result ){
  $seconds = 300; // 5 minutes
  // if trying again after lockout time limit ....
  if( $_SESSION['loginAttempt']['Count'] >= $loginAttemptsAllowed ) {
    $difference  = abs($_SESSION['loginAttempt']['LockoutTime'] - $_SESSION['loginAttempt']['Time']);
    $diffSeconds = round($difference);
    if( $diffSeconds > $seconds ) {
      unset($_SESSION['loginAttempt']); // they failed but have a new set of chances
      } else {
      $minutes = $seconds / 60;
      $message = “Sorry, you have had $loginAttemptsAllowed failed login attempts. <br />
      We temporarily forbid access in order to protect your private information. <br />
      Please wait $minutes minutes before logging on again.”;
      }
    } else {
    if( !isset($_SESSION['loginAttempt']['Time']) ) {
      $_SESSION['loginAttempt']['Time']  = get_microtime();
      $_SESSION['loginAttempt']['Count'] = 1;
    } else {
      $_SESSION['loginAttempt']['Count']++;
    }
    if( $_SESSION['loginAttempt']['Count'] >= $loginAttemptsAllowed ) {
      $_SESSION['loginAttempt']['LockoutTime'] = get_microtime();
    }
    $message = “login error”;
  }
  addMessage($message, “MsgErr”);
  redirect($_SESSION["backPage"]);
  exit();
}

….. go on and log them. Dont forget to unset( $_SESSION['loginAttempt'] );
// a couple of the functions in there are custom ones, they are basically just wrappers.
// I forget where I got the following function, but it is used for benchmarking. Maybe php.net?


function get_microtime() {
  $mtime = microtime();
  $mtime = explode(" ",$mtime);
  $mtime = doubleval($mtime[1]) + doubleval($mtime[0]);
  return ($mtime);
}

So there you have it. Forcing users to have highly secure passwords, while a good idea, is not always possible.

Keep your users safe. And curses to wordpress for screwing up my code formatting…

May 16, 2008

Doing Mod_Rewrite Right

Filed under: — Tags: , , , , — pbg @ 1:35 pm

There are a few different things to do to make Apache_mod rewrite right. Overall the difficulty isnt too great, but setting it up right at the beginning is the key. You dont really want to have to catch every little exception in mod_rewrite regular expressions. Using your database to store safe strings to use in your url makes the whole process much more efficient. This little fact is usually not mentioned in tutorials for mod_rewrite.

You really do want to keep the mod_rewrite rules simple. Dont try to write a complex regexp in mod_rewrite that handles all kinds of apostropes, special characters, etc. (like I did). You dont have to have question marks, quotations, colons in the rewritten url for it to be useful to search engines. You can turn a title like “O’mally’s dog’s bone” into http://domain.com/Omallys_dogs_bone and there is definitely enough textual sense in that rewritten url for a search engine to deal with it.

Take your table with all your content data in it. Create a field for your content for a safe title. Then you can process your old titles into the new field. In your looping construct, use a bit of php to clean out your old titles for spaces, quotes, slashes, and other silly things.

$punctuations = array('.', '\'', '?','!','*','=','Ó','%','@','&',',','/');
$safeTitle = str_replace($punctuations, "", $title);// get rid of the junk
$safeTitle   = str_replace(" ", "_", $safeTitle);// replace spaces with underscores

Now you have a content resource which you can add to your output queries that will fill in your url link on your page for mod_rewrite goodness.

Make your mod_rewrite rule in your .htaccess file. Note here that the rule has a place for 2 variables, and is looking for all instances of strings with upper and lower case letters, the numbers 0-9, and the underscore character. And of course, it turns it all back into a query string to submit to your content page.

RewriteRule ^/?([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)(/)?$ item.php?safeTopicName=$1&safeTitle=$2

Almost done right? Eh, not quite. Almost though. Dont screw over your existing users, who may have linked to something of yours to the past. You can still account for your old reference style to your web content, and you most definitely should. You can write checks for query string data validation to allow for transparent access to content through either the old query string method or the new one.

if($_GET["safeTopicName"]){
  $sql = sprintf("SELECT topicId
                  FROM contentTopics
                  WHERE safeTopicName
                  LIKE '%s'",
                mysql_real_escape_string($_GET["safeTopicName"]));
  diode($topicId = $db->getOne($sql), $sql); // my db connection wrapper
  $sql = sprintf("SELECT articleid
                  FROM content
                  WHERE safeTitle
                  LIKE '%s'",
                mysql_real_escape_string($_GET["safeTitle"]));
  diode($articleid = $db->getOne($sql), $sql);
} else {
  if($_GET["topicId"]) {
    $topicId =  (int)$_GET["topicId"]);
  }
  if($_GET["articleid"]) {
    $articleid =  (int)$_GET["articleid"];
  }
}
if(!isset($topicId) || !isset($articleid)) {
    addMessage("no item found", "MsgErr");
    redirect();
    exit();
}

A couple notes: Im using PEAR, and a couple of custom functions for efficiency sake. Note the use of (int) and mysql_real_escape_string() for sanitizing and typing.  And yes, there are probably better ways to write this up, but you get the idea. Look for your $_GET vars, and if you dont have one set or the other, no result, otherwise, process it so the rest of the code needs no further reliance on these initial options so a user can get to your site with /Planets/earth as well as with item.php?topicId=2&articleid=249.

To Recap:

  • Set up safe versions of your content titles
  • process the old titles with a script
  • make a simpler rewrite rule as a result
  • set up your validation to process both kinds of queries
  • marvel about how much simpler it was to do it that way than to try and do it all with Mod_Rewrite alone.
Newer Posts »

Powered by WordPress