PVK - Protected Virtual Keypad - (updated 2007-07-29)
|
Protection against... |
Detection... |
Ease to install |
User usage
(green=easy) |
|
funds transfert |
simple phishing |
MITM phishing |
ISP pharming |
trojan keylogger |
advanced trojan |
before-fraud |
after-fraud |
PVK - Protected Virtual Keypad |
|
|
|
|
|
|
N/A |
N/A |
|
|
Goal : protecting user against dumb malware
Installed by : business lines
In this solution, we'll try to prevent a malicious trojan from catching anything.
We'll use cryptographic hashes to prevent sniffer from analyzing trafic.
We'll use javascript tips to prevent formgrabbing.
We'll
use crypto virtual keypad to prevent keyloggers and position mouse captures.
Some part of this solution is one of the most common
protection used by banking group to prevent from phishing. Depending on the way it's done, it can be an interesting solution to fight malicious code but it won't help against phishing sites.
Main PHP page
<?
// This is the authentication page
where the user has to give his password
// This password
is composed of 8 digits where user will have to give only 4
// To prevent brute force, account will be disabled after 20 wrong tries.
$keypad_layout=array();
//a 24 blobk array with key value inside
$keys=arary(); // a 8 block array with keypad postion inside
$keystar=array(); // a 4 block array with password postion stared
$is_position_stared()=array() // a 8 block array defining if password digit is stared
/////////////////////// PHP FUNCTIONS PART/////////////////////
function generate_keypad_layout(){
srand ((int) ((double) microtime() * 1234567)); // If needed
for ($i=0;$i<8;$i++){ // For each possible key
while($keys[$i]=rand (0,23)){ // We try to find a free block in a 24 block array
if (!$keypad_layout[$keys[$i]]) {
$keypad_layout[$keys[$i]]=$i;
break;
}
}
}
}
function chose_star_positions(){
for ($i=0;$i<8;$i++){ // place false in is_position_stared
$is_position_stared[$i]=false;
}
srand ((int) ((double) microtime() * 2345678)); // If needed
for ($i=0;$i<4;$i++){ // For each possible key
while($keystar[$i]=rand (1,8)){ // We define which password digits will be stared
if (!$is_position_stared[$keystar[$i]]) {
$is_position_stared[$keystar[$i]]=true;
break;
}
}
}
}
/////////////////////// INIT PART/////////////////////
// We generate a random keypad to prevent mouse capture
generate_keypad_layout();
// We will obfuscate some digits from password so that the password will never be visible completely.
chose_star_positions();
srand ((int) ((double) microtime() * 3456789))
$password_seed=rand(1000,9999);
/////////////////////// SESSION PART/////////////////////
session_start();
// We suppose we have received the user identifier value from a previous POST (may be the identification page)
$_SESSION['user'] = $_POST['user_id'];
$_SESSION['seed'] = $password_seed;
$_SESSION['keys'] = serialize($keys);
$_SESSION['is_position_stared'] = serialize($is_position_stared);
?>
<!-- HTML PART -->
<html>
<body>
<!-- Write whatever you want here -->
<!-- JAVASCRIPT PART -->
<script language="javascript" src="sha-1.js">
// This script willl be download from http://pajhome.org.uk/crypt/md5/sha1.js
</script>
<script>
function clear(){
document.passform.password.value=document.passform.password_cleared.value;
document.passform.code_position.value='';
}
function hash(a_string){
// We call the hex_sha1 function from the sha-1.js script
// With this technique, even a formgraber trojan won't get much information
document.passform.code_position.value=hex_sha1(a_string);
}
function fill(position){
document.passform.code_position.value.=position.'-';
var old_password=document.passform.password.value;
//Next instruction will replace the first space by a star
var new_password=old_password.replace(/ /,'*');
document.passform.password.value=new_password;
}
</script>
Please insert your missing digits below. <br>
<?
/////////////////////// KEYPAD PART/////////////////////
// We place the keypad at a random position to prevent static mouse capture
echo "<span style='position: absolute;top: ".rand (0,700)."px;left: ".rand (0,400)."px;filter:alpha(opacity=25);-moz-opacity:.25;opacity:.25;'>";
echo "<table>";
for ($i=0;$i<4;$i++){ //for each line
echo " <tr>";
for ($j=0;$j<5;$j++){ //for each row
if ($keypad_layout($i*5+$j)){ // if a digit is at this position
echo "<td onmousedown=\"fill(".$keypad_layout($i*5+$j)".))\">".$keypad_layout($i*5+$j)." </td>";
}
else{
echo "<td> </td>";
}
}
echo " </tr>";
}
echo "</table> ";
echo "<form action='verify.php' name='passform' method='POST' >";
// We define the password view when cleared depending on the stars
$password_cleared="";
for ($i=0;$i<7;$i++){
if ($is_position_stared[$i]) $password_cleared.="*-";
else $password_cleared.=" -";
}
if ($is_position_stared[7]) $password_cleared.="*";
else $password_cleared.=" ";
// We fill the password with 4 random stars
echo "<input type='text' size='15' maxlength='15' name='password' value='".$password_cleared."'>";
echo "<input type='hidden' name='password_cleared' value='".$password_cleared."'>";
echo "<input type='hidden' name='code_position' value=''>";
echo "<input type='button' value='OK' onmousedown='hash(document.passform.code_position.value.".$password_seed.");this.submit'>";
echo "<input type='button' value='Clear' onmousedown='clear()'>";
echo "</form>";
echo "</span>";
?>
</body>
</html>
verify.php
As defined previously, this page will be in charge of authenticating the user. We'll use the form result and verify that the hash is the good one.
<?
/////////////////////// SESSION PART/////////////////////
session_start();
$user=$_SESSION['user'] ;
$password_seed=$_SESSION['seed'] ;
$keys=unserialize($_SESSION['keys']) ;
$is_position_stared=unserialize($_SESSION['is_position_stared']);
/////////////////////// PHP FUNCTION PART/////////////////////
function hash(astring) {
return sha-1($astring);
}
function get_user_password(user){
$mysql_link=mysql_connect("server_location","user1", "password");
mysql_select_db("mydatabase",$mysql_link);
$query="SELECT password from users WHERE user_id=".mysql_real_escape_string($user);
$password = mysql_query ($query) or die (mysql_error ());
mysql_close($mysql_link);
return $password;
}
/////////////////////// MAIN PHP PART/////////////////////
$code_position = $_POST['code_position'];
$some_positions="";
for ($i=0;$i<8;$i++){ // i_max=number of digits in password
if ( !$is_position_stared[$i]){
$some_positions.=$keys[substr($password,$i,1)]."-";
}
}
$some_positions.=$password_seed;
// At this step, $some_positions should be 5-23-12-20-6123 like
if (strcmp(hash($some_positions),$code_position)){
// Successful authentication
// Write whatever you want
}
else {
// Wrong authentication.
// Write whatever you want. For instance a refresh on the identification page
}
?>
This solution deals correctly with keyloggers and mouse captures but is useless against screen scrappers.
In order to make this application more robust, it could be possible to increase the passcode to 10 digits,it would be more difficult to remember for user but it could protect against brute force.
Another interesting feature here would be to restrict missed user authentication to 3 in one hour.
I would highly advise to add a captcha feature in your Bank Account
Number record page or direct transfer page so that you require the
action is made by a human user and not by a bot user with an automated
script. Even if the captcha feature can be made useless by some smart
OCR evil code, it helps you fight Man In the Middle attack until the
Man in the Middle is a real Man...
|