[原]防刷短信的简单机制(不使用第三方行为验证)

需求

网站短信接口发送使用url请求,容易被攻击。

目前第三方的行为验证码,防御效果应该是最好的,像国内极验、顶象,国外google做的最牛掰,勾选按钮“I'm not a robot”自动区别人机操作。

像ip限制、发送次数限制本文不再说明,目前机器太容易绕过了

机制原理

主要通过增加请求参数的有效性的验证

1.参数name有效性的验证
2.参数value有效性的验证

前台假设的注册页面

<form action='' method='post'>
    <dd>手 机 号<input name='mobile' placeholder="填写正确的手机号码" ></dd>
    <dd>设 置 密 码<input type="password" name='password' placeholder="填写登录密码" ></dd>
	<dd>验 证 码<input type="text" name='captcha'  placeholder="填写右边图片所示的字符"><img src='' /></dd>
	<dd>手机验证码<input name="mcode" type="text"><button onclick="getcode();">获取验证码</button></dd>
	<dd><input class="btn" type="submit" value="注 册"></dd>
</form>

具体实现

1.页面加载,后端随机生成randNum(session存储,设置有效时间为1分钟),生成当前分钟curMin,根据curMin不同值分别通过不同加密方式(randNum和curMin参入运算)生成页面tokenName, 通过加密方式(tokenName)生成tokenValue。
tokenName、tokenValue、curMin 传递前台页面,他们的有效性时间为1分钟

2.点击发送请求,前台js根据curMin和用户输入手机mobile 通过加密方式生成前台tokenName1和tokenValue1

3.点击发送请求js方法中,请求url传入tokenName=tokenValue 和tokenName1=tokenValue2,后端代码通过同样加密方式进行匹配验证

代码实现(php)

1.后端随机生成randNum和curMin

$smsRandNum = mt_rand(100000,999999);
Session::set('sms_rand_num',$smsRandNum);

2.后端生成tokenName和tokenValue

$this->currentMinute = substr(date("i",time()-60*5),1,1);
$tokenValue = $this->createSmsToken($this->currentMinute);
$tokenName = "a".md5($tokenValue)

createSmsToken函数

private function createSmsToken($minRand)
{
    $token = "";
    //时间随机码
    $smRandNum = Session::get('sms_rand_num');
    switch($minRand)
    {
        case 0:
            $token = md5($minRand.$smRandNum.date("Y"));break;
        case 1:
            $token = md5($minRand.$smRandNum.date("m"));break;
        case 2:
            $token = md5($minRand.$smRandNum.date("d"));break;
        case 3:
            $token = md5($minRand.$smRandNum.date("H"));break;
        case 4:
            $token = md5($minRand.$smRandNum.date("Y-m"));break;
        case 5:
            $token = md5($minRand.$smRandNum.date("Y-d"));break;
        case 6:
            $token = md5($minRand.$smRandNum.date("Y-H"));break;
        case 7:
            $token = md5($minRand.$smRandNum.date("m-d"));break;
        case 8:
            $token = md5($minRand.$smRandNum.date("m-H"));break;
        case 9:
            $token = md5($minRand.$smRandNum.date("d-H"));break;
        default:
            $token = md5($minRand.$smRandNum.date("Y-m-d"));break;
    }
    return $token;
}

3.生成前台tokenName1和tokenValue1

var tokenName1 = createToken(mobile, randNumber);
var tokenName1Val = sha1(tokenName1);

js函数createToken

function createToken(code, randNumber) {
    var c = String.fromCharCode(code.charCodeAt(0) + code.length + randNumber);
    for (var i = 1; i < code.length; i++) {
        c += String.fromCharCode(code.charCodeAt(i) + code.charCodeAt(i - 1) + randNumber);
    }
    c = escape(c);
    // 去掉特殊字符
    c = c.replace(/[\@\#\$\%\^\&\*\{\}\:\"\L\<\>\?]/, '');
    return ("b" + c);
}

js函数sha1

function sha1(s) {
    var data = new Uint8Array(encodeUTF8(s))
    var i, j, t;
    var l = ((data.length + 8) >>> 6 << 4) + 16,
        s = new Uint8Array(l << 2);
    s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);
    for (t = new DataView(s.buffer), i = 0; i < l; i++) s[i] = t.getUint32(i << 2);
    s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);
    s[l - 1] = data.length << 3;
    var w = [],
        f = [

        function() {
            return m[1] & m[2] | ~m[1] & m[3];
        }, function() {
            return m[1] ^ m[2] ^ m[3];
        }, function() {
            return m[1] & m[2] | m[1] & m[3] | m[2] & m[3];
        }, function() {
            return m[1] ^ m[2] ^ m[3];
        }],
        rol = function(n, c) {
            return n << c | n >>> (32 - c);
        },
        k = [1518500249, 1859775393, -1894007588, -899497514],
        m = [1732584193, -271733879, null, null, -1009589776];
    m[2] = ~m[0], m[3] = ~m[1];
    for (i = 0; i < s.length; i += 16) {
        var o = m.slice(0);
        for (j = 0; j < 80; j++)
        w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1), t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0, m[1] = rol(m[1], 30), m.pop(), m.unshift(t);
        for (j = 0; j < 5; j++) m[j] = m[j] + o[j] | 0;
    };
    t = new DataView(new Uint32Array(m).buffer);
    for (var i = 0; i < 5; i++) m[i] = t.getUint32(i << 2);

    var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function(e) {
        return (e < 16 ? "0" : "") + e.toString(16);
    }).join("");
    return hex;
}

function encodeUTF8(s) {
	var i, r = [], c, x;
    for (i = 0; i < s.length; i++)
    if ((c = s.charCodeAt(i)) < 0x80) r.push(c);
    else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));
    else {
        if ((x = c ^ 0xD800) >> 10 == 0) //对四字节UTF-16转换为Unicode
        c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000, r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));
        else r.push(0xE0 + (c >> 12 & 0xF));
        r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
    };
    return r;
}

4.后端匹配验证

请求url示例

http://www.xxx.com/action=sendcode&mobile=18306561234&captcha=cihom&a386b4aad651fddec12bb7fdaae92c73dcf189a11=2c911c34936903f93fd766e55b8707c0&bAnphkpplfjp=d77c9f6debeb7904b64daec6872ab6f8d7fce4f7

后端验证

$minRand = $this->currentMinute;
$tokenName1 = $this->createSmsTokenTwo($mobile,$minRand);
$tokenName1Val = sha1($tokenName1)

private function createSmsTokenTwo($code,$randNumber)
{
    $code = array_values(unpack('n*', iconv('utf-8', 'ucs-2', $code)));
    $c[] = $code[0] + count($code)+$randNumber;
    for ($i = 1; $i < count($code); $i ++)
    {
        $c[] = $code[$i] + $code[$i - 1]+$randNumber;
    }
    $r = '';
    foreach ($c as $v)
    {
        if ($v < 256)
            $r .= urlencode(chr($v));
        else
           $r .= '%u' . strtoupper(dechex($v));
    }
    $pattern = array(
        '/\@/',
        '/\#/',
        '/\$/',
        '/\%/',
        '/\^/',
        '/\&/',
        '/\*/',
        '/\{/',
        '/\}/',
        '/\:/',
        '/\"/',
        '/\</',
        '/\>/',
        '/\?/'
        
    );
    $r = preg_replace( $pattern , '', $r);
    return "b".$r;
}
posted @ 2019-09-07 14:54  sentangle  阅读(818)  评论(0编辑  收藏  举报