Lots of websites provide a protected area for authorized users to log in to. For instance, you might have a downloads section for products on your site where customers can access the software that they have bought.
There are many different ways to protect web pages with a user name and password. Their login and password could be quickly spread around. Once the details are common knowledge, anyone could login and access the site without paying.
Did you know that TrafficScript can be used to detect when a username and password are used from several different locations? You can then choose whether to disable the account or give the user a new password. All this can be done without replacing any of your current authentication systems on your website:
Looks like the login details for user 'ben99' have been leaked! How can we stop people leeching from this account?
For this example, we'll use a website where the entire site is protected with a PHP script that handles the authentication. It will check a user's password, and then set a USER cookie filled in with the user name. The details of the authentication scheme are not important. In this instance, all that matters is that TrafficScript can discover the user name of the account.
First of all, TrafficScript needs to ignore any requests that aren't authenticated:
$user = http.getCookie( "USER" ); if( $user == "" ) break;
Next, we'll need to discover where the user is coming from. We'll use the IP address of their machine. However, they may also be connecting via a proxy, in which case we'll use the address supplied by the proxy.
$from = request.getRemoteIP(); $proxy = http.getHeader( "X-Forwarded-For" ); if( $proxy != "" ) $from = $proxy;
TrafficScript needs to keep track of which IP addresses have been used for each account. We will have to store a list of the IP addresses used. TrafficScript provides persistent storage with the data.get() and data.set() functions.
$list = data.get( $user ); if( !string.contains( $list, $from )) { # Add this entry in, padding list with spaces $list = sprintf( "%19s %s", $from, $list ); ...
Now we need to know how many unique IP addresses have been used to access this account. If the list has grown too large, then don't let this person fetch any more pages.
# Count the number of entries in the list. Each entry is 20 # characters long (the 19 in the sprintf plus a space) $entries = string.length( $list ) / 20; if( $entries > 4 ) { # Kick the user out with an error message http.sendResponse( "403 Permission denied", "text/plain", "Account locked", "" ); } else { # Update the list of IP addresses data.set( $user, $list ); } }
That's it! If a single account on your site is accessed from more than four different locations, the account will be locked out, preventing abuse.
As this is powered by TrafficScript, further improvements can be made. We can extend the protection in many ways, without having to touch the code that runs your actual site. Remember, this can be deployed with any kind of authentication being used - TrafficScript just needs the user name.
This has a few new improvements. First of all, the account limits are given a timeout, enabling someone to access the site from different locations (e.g. home and office), but will still catch abuse if the account is being used simultaneously in different locations. Secondly, any abuse is logged, so that an administrator can check up on leaked accounts and take appropriate action. Finally, to show that we can work with other login schemes, this example uses HTTP Basic Authentication to get the user name.
# How long to keep data for each userid (seconds) $timelimit = 3600; # Maximum number of different IP addresses to allow a client # to connect from $maxips = 4; # Only interested in HTTP Basic authentication $h = http.getHeader( "Authorization" ); if( !string.startsWith( $h, "Basic " )) continue; # Extract and decode the usernameassword combination $enc = string.skip( $h, 6 ); $userpasswd = string.base64decode( $enc ); # Work out where the user came from. If they came via a proxy, # then ensure that we don't log the proxy's IP address(es) $from = request.getRemoteIP(); $proxy = http.getHeader( "X-Forwarded-For" ); if( $proxy != "" ) $from = $proxy; # Have we seen this user before? We will store a space separated # list of all the IPs that we have seen the user connect from $list = data.get( $userpasswd ); # Also check the timings. Only keep the records for a fixed period # of time, then delete them. $time = data.get( "time-" . $userpasswd ); $now = sys.time(); if(( $time == "" ) || (( $now - $time ) > $timelimit )) { # Entry expired (or hasn't been created yet). Start with a new # list and timestamp. $list = ""; $time = $now; data.set( "time-" . $userpasswd, $time ); } if( !string.contains( $list, $from )) { # Pad each entry in the list with spaces $list = sprintf( "%19s %s", $from, $list ); # Count the number of entries in the list. Each entry is 20 # characters long (the 19 in the sprintf plus a space) $entries = string.length( $list ) / 20; # Check if the list of used IP addresses is too large - if so, # send back an error page! if( $entries > $maxips ) { # Put a message in the logs so the admins can see the abuse # (Ensure that we show the username but not the password) $user = string.substring( $userpasswd, 0, string.find( $userpasswd, ":" ) - 1 ); log.info( "Login abuse for account: " . $user . " from " . $list ); http.sendResponse( "403 Permission denied", "text/html", "Your account is being accessed by too many users", "" ); } else { # Update the list and let the user through data.set( $userpasswd, $list ) ; } }
This article was originally written by Ben Mansell in March 2007