前言:我这个人还是比较懒的,所以很多东西也懒的整理,今天分享下之前做的一个口令卡二次登陆验证。这个东西对于单一密码的安全性还是有一定提高的,当然肯定也是存在问题的。
基本流程就是后台添加需要二次验证的用户后系统会生成对应用户的一个数据序列,将序列储存到数据库中,而用户的展现形式则是一张二维口令卡。前台登录的时候更改原论坛提交流程,首先发送包含用户名的一个请求发送到该插件前台处理文件,程序根据用户名检索数据库,返回该用户是否需要口令验证。如果需要验证,则生成一个口令卡坐标及生成一个储存坐标的cookie,登录页面则弹出一个对话框,提示用户输入口令卡上对应坐标的散列。完成后转回到页面提交表单流程,提交登录表单。登录程序验证完合法用户以后,继而验证用户输入散列与cookie里储存坐标产生的散列是否一致,如不一致则登录不成功!
以下是后台生成口令卡核心代码。
public function createCard($codeNum = '3') { //生成口令卡
$arr = array();
for ($x = 1; $x <=8; $x++) {
for ($y = 1; $y <=10; $y++ ) {
$arr[chr(74 + $x)][chr(64 + $y)] = str_pad(rand(0, 999), $codeNum, '0', STR_PAD_LEFT);
}
}
return $arr;
}
以下是打印口令卡页面
<!--<?php
print <<<EOT
-->
<table border="0" cellspacing="1" bgcolor="#3399FF">
<tr>
<th bgcolor="#FFFFFF"> </th>
<th bgcolor="#FFFFFF">A</th>
<th bgcolor="#FFFFFF">B</th>
<th bgcolor="#FFFFFF">C</th>
<th bgcolor="#FFFFFF">D</th>
<th bgcolor="#FFFFFF">E</th>
<th bgcolor="#FFFFFF">F</th>
<th bgcolor="#FFFFFF">G</th>
<th bgcolor="#FFFFFF">H</th>
<th bgcolor="#FFFFFF">I</th>
<th bgcolor="#FFFFFF">J</th>
</tr>
<!--
EOT;
foreach ($card['cardcode'] as $key => $val) {
print <<<EOT
-->
<tr bgcolor="#FFFFFF">
<th>{$key}</th>
<!--
EOT;
foreach ($val as $k =>$v) {
print <<<EOT
-->
<td>{$v}</td>
<!--
EOT;
}
print <<<EOT
-->
</tr>
<!--
EOT;
}
print <<<EOT
-->
</table>
<!--
EOT;
?>
-->
执行的结果如下
以下是前台请求时生成的字母坐标并生成一个cookie
public function outPutCode() {//随机生成用户验证序列并生成cookie
$num = array();
$x = $num['x'] = array(rand(1, 10), rand(1, 8));
$y = $num['y'] = array(rand(1, 8), rand(1, 10));
Cookie('cardhash', StrCode($this->timestamp."\t\t".serialize($num))); //储存的是一个坐标序列化字串
$cardNum = array(chr(64 + $x[0]). chr(74 + $x[1]), chr(74 + $y[0]). chr(64 + $y[1]));
return implode(':',$cardNum);
}
接下来我们要做的是修改登录页面,使用户登录时产生二次验证对话框。
打开wind/login.htm在用户登录表单里面加一个onsubmit事件用于提交前验证是否需要口令卡验证。函数名为checklogin
<form action="login.php?" method="post" name="login" id="loginform" onSubmit="return checklogin(this);">
函数代码如下
function checklogin(f) {
//f.submit.disabled = true;
if (f.pwuser.value == '' || f.pwpwd.value == '') {
alert('用户名或密码不能为空');
return false;
}
login_user = f.pwuser.value;
ajax.send("hack.php?H_name=passcard&action=ajax&do=checkuser&username=" + encodeURIComponent(login_user) + '&nowtime=' + new Date().getTime(), '', function(){
var rText = ajax.request.responseText.split('|');
if (rText[0] == 'needcheck') {
showDialog({type:'confirm',message:'口令卡验证:<input size=12 id="cardcode"></input> <font style="font-size:14px;font-weight:bold">' + rText[1] + '</font>',okText:'验证登陆',onOk:function() {
if (getObj('cardcode').value == '') {
alert('请正确输入验证码');
} else {
getObj('form_cardnum').value = getObj('cardcode').value;
//console.log(f.submit)
f.submit();
}
}});
//f.submit.disabled = false;
} else if (rText[0] == 'pass' || rText[0] == 'nouser') {
f.submit();
}
});
return false;
}
如需要验证则会弹出以下对话框
如果输入对应的散列则进入正常密码验证流程,当系统调用uc_click.php获取用户信息之后进入口令卡验证流程。
所在文件 require/checkpass.php
require_once(R_P . 'uc_client/uc_client.php');
$uc_user = uc_user_login($username, $password, $lgt);
if ($uc_user['status'] == -1) {
$GLOBALS['errorname'] = $username;
//Showmsg('user_not_exists');
return 'user_not_exists';
}
//以上是确定用户是存在的
//口令卡验证
$card = new passcard;
if ($codeinfo = $card->getCard($uc_user['uid'])) { //如果已经绑定口令卡则进入验证流程
if (!$cardnum) { //$cardnum是修改checkpass()函数,传入的一个参数,由loging.php接收。
return '您的账户需要口令卡验证,请返回重新填写';
}
$cookieData = explode("\t", StrCode(GetCookie('cardhash'), 'DECODE'));
if($timestamp - $cookieData[0] > 1800) {
Cookie($cookieName, '', 0);
return '口令密码超时,请刷新页面重试';
}
$cookiehash = unserialize($cookieData[2]);//反序列化,对应之前序列化存储
if (!$card->checkHash($codeinfo['cardcode'], $cardnum, $cookiehash['x'], $cookiehash['y'])) {
return '口令卡验证失败,请重新填写';
}
}
checkHash()函数是验证口令卡函数,代码如下
public function checkHash($cardHash, $cardNum, $x, $y) {//验证口令卡
$hashArr = array();
$hashArr = unserialize($cardHash);//反序列化,对应之前序列化存储
$vy = $hashArr[chr(74 + $y[0])][chr(64 + $y[1])];//等到y轴值
$vx = $this->getX($hashArr, $x);得到x轴值
if ($vx. $vy == $cardNum) {//对比
return true;
}
return false;
}
getX()函数如下
public function getX($hashArr, $x) { //得到x方向序列
$arr = array();
foreach ($hashArr as $val) {
$arr[] = $val[chr(64 + $x[0])];
}
return $arr[$x[1] - 1];
}
以上就是主要流程与代码。之后有需要我会整理一个程序包供大家使用。。
下面是这个流程可能会长生的问题
验证过程的问题一,如果恶意用户知道其中一个坐标对应散列,则可以通过已知道坐标重写一个cookie从而绕过二次验证系统原有的随机验证码产生流程。进而直接绕过二次验证。