PHP基于timestamp和nonce实现的防止重放攻击方案分析


Posted in PHP onJuly 26, 2019

本文实例讲述了PHP基于timestamp和nonce实现的防止重放攻击方案。分享给大家供大家参考,具体如下:

以前总是通过timestamp来防止重放攻击,但是这样并不能保证每次请求都是一次性的。今天看到了一篇文章介绍的通过nonce(Number used once)来保证一次有效,感觉两者结合一下,就能达到一个非常好的效果了。

重放攻击是计算机世界黑客常用的攻击方式之一,所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。

首先要明确一个事情,重放攻击是二次请求,黑客通过抓包获取到了请求的HTTP报文,然后黑客自己编写了一个类似的HTTP请求,发送给服务器。也就是说服务器处理了两个请求,先处理了正常的HTTP请求,然后又处理了黑客发送的篡改过的HTTP请求。

基于timestamp的方案

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。因为一次正常的HTTP请求,从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间相比较,是否超过了60s,如果超过了则认为是非法的请求。

假如黑客通过抓包得到了我们的请求url:
http://koastal.site/index/Info?uid=ZX07&stime=1480862753&sign=80b886d71449cb33355d017893720666

其中

$sign=md5($uid.$token.$stime);
// 服务器通过uid从数据库中可读出token

一般情况下,黑客从抓包重放请求耗时远远超过了60s,所以此时请求中的stime参数已经失效了。
如果黑客修改stime参数为当前的时间戳,则sign参数对应的数字签名就会失效,因为黑客不知道token值,没有办法生成新的数字签名。

但这种方式的漏洞也是显而易见的,如果在60s之内进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效。

基于nonce的方案

nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同,所以该参数一般与时间戳有关,我们这里为了方便起见,直接使用时间戳的16进制,实际使用时可以加上客户端的ip地址,mac地址等信息做个哈希之后,作为nonce参数。
我们将每次请求的nonce参数存储到一个“集合”中,可以json格式存储到数据库或缓存中。
每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。

假如黑客通过抓包得到了我们的请求url:
http://koastal.site/index/Info?uid=ZX07&nonce=58442c21&sign=80b886d71449cb33355d017893720666

其中

$sign=md5($uid.$token.$nonce);
// 服务器通过uid从数据库中可读出token

nonce参数在首次请求时,已经被存储到了服务器上的“集合”中,再次发送请求会被识别并拒绝。
nonce参数作为数字签名的一部分,是无法篡改的,因为黑客不清楚token,所以不能生成新的sign。

这种方式也有很大的问题,那就是存储nonce参数的“集合”会越来越大,验证nonce是否存在“集合”中的耗时会越来越长。我们不能让nonce“集合”无限大,所以需要定期清理该“集合”,但是一旦该“集合”被清理,我们就无法验证被清理了的nonce参数了。也就是说,假设该“集合”平均1天清理一次的话,我们抓取到的该url,虽然当时无法进行重放攻击,但是我们还是可以每隔一天进行一次重放攻击的。而且存储24小时内,所有请求的“nonce”参数,也是一笔不小的开销。

基于timestamp和nonce的方案

那我们如果同时使用timestamp和nonce参数呢?
nonce的一次性可以解决timestamp参数60s的问题,timestamp可以解决nonce参数“集合”越来越大的问题。

我们在timestamp方案的基础上,加上nonce参数,因为timstamp参数对于超过60s的请求,都认为非法请求,所以我们只需要存储60s的nonce参数的“集合”即可。

假如黑客通过抓包得到了我们的请求url:
http://koastal.site/index/Info?uid=ZX07&stime=1480862753&nonce=58442c21&sign=80b886d71449cb33355d017893720666

其中

$sign=md5($uid.$token.$stime.$nonce);
// 服务器通过uid从数据库中可读出token

如果在60s内,重放该HTTP请求,因为nonce参数已经在首次请求的时候被记录在服务器的nonce参数“集合”中,所以会被判断为非法请求。超过60s之后,stime参数就会失效,此时因为黑客不清楚token的值,所以无法重新生成签名。

综上,我们认为一次正常的HTTP请求发送不会超过60s,在60s之内的重放攻击可以由nonce参数保证,超过60s的重放攻击可以由stime参数保证。

因为nonce参数只会在60s之内起作用,所以只需要保存60s之内的nonce参数即可。

我们并不一定要每个60s去清理该nonce参数的集合,只需要在新的nonce到来时,判断nonce集合最后一次修改时间,超过60s的话,就清空该集合,存放新的nonce参数集合。其实nonce参数集合可以存放的时间更久一些,但是最少是60s。
随机数集合可以根据业务场景采用定期清理或根据大小自动清理的方案,例如该接口每秒的请求数最高为1000,则60s内的请求数量最多为1500*60=90000,则我们在每次请求后检查集合大小是否超过90000,若超高该数量则清空。

验证流程

//判断stime参数是否有效
if( $now - $stime > 60){
  die("请求超时");
}
//判断nonce参数是否在“集合”已存在
if( in_array($nonce,$nonceArray) ){
  die("请求仅一次有效");
}
//验证数字签名
if ( $sign != md5($uid.$token.$stime.$nonce) ){
  die("数字签名验证失败");
}
/*
if( $now - $nonceArray->lastModifyTime > 60 ){
  $nonceArray = null;
}
$nonceArray.push($nonce);
*/
//处理随机数
$key = 'nonce'+$uid;
if($redis->sismember($key,$nonce) === true){
  die('拒绝重放攻击请求');
}
if($redis->scard($key) > 90000){
  $redis->del($key);
}
$redis->sadd($key,$nonce);
//重放攻击检查完成

参考文章:

http://www.360doc.com/content/14/0116/16/834950_345740386.shtml

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
对Session和Cookie的区分与解释
Mar 16 PHP
实用函数9
Nov 08 PHP
php array_filter除去数组中的空字符元素
Jun 21 PHP
php生成zip压缩文件的方法详解
Jun 09 PHP
php+mysqli批量查询多张表数据的方法
Jan 29 PHP
分享微信扫码支付开发遇到问题及解决方案-附Ecshop微信支付插件
Aug 23 PHP
4种PHP异步执行的常用方式
Dec 24 PHP
深入浅析PHP无限极分类的案例教程
May 09 PHP
一个简单安全的PHP验证码类 附调用方法
Jun 24 PHP
用php+ajax新建流程(请假、进货、出货等)
Jun 11 PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 PHP
ThinkPHP5分页paginate代码实例解析
Nov 10 PHP
YII2.0框架行为(Behavior)深入详解
Jul 26 #PHP
php使用socket调用http和smtp协议实例小结
Jul 26 #PHP
php使用curl模拟多线程实现批处理功能示例
Jul 25 #PHP
yii框架使用分页的方法分析
Jul 25 #PHP
php实现的生成排列算法示例
Jul 25 #PHP
Yii框架中使用PHPExcel的方法分析
Jul 25 #PHP
PHP保留两位小数的几种方法
Jul 24 #PHP
You might like
php计算两个文件相对路径的方法
2015/03/14 PHP
php实现约瑟夫问题的方法小结
2015/03/23 PHP
thinkphp3.x连接mysql数据库的方法(具体操作步骤)
2016/05/19 PHP
Javascript 刷新全集常用代码
2009/11/22 Javascript
js修改table中Td的值(定义td的单击事件)
2013/01/10 Javascript
javascript实现倒计时并弹窗提示特效
2015/06/05 Javascript
jQuery实现的仿百度分页足迹效果代码
2015/10/30 Javascript
一不小心就做错的JS闭包面试题
2015/11/25 Javascript
AngularJS入门(用ng-repeat指令实现循环输出
2016/05/05 Javascript
关于Jquery中的事件绑定总结
2016/10/26 Javascript
JavaScript中关于for循环删除数组元素内容时出现的问题
2016/11/21 Javascript
Bootstrap表单控件学习使用
2017/03/07 Javascript
Javascript实现登录记住用户名和密码功能
2017/03/22 Javascript
JS实现线性表的链式表示方法示例【经典数据结构】
2017/04/11 Javascript
解决JavaScript中0.1+0.2不等于0.3问题
2018/10/23 Javascript
layui实现三级联动效果
2019/07/26 Javascript
python 循环while和for in简单实例
2016/08/16 Python
python traceback捕获并打印异常的方法
2018/08/31 Python
解决Python下imread,imwrite不支持中文的问题
2018/12/05 Python
Python魔法方法详解
2019/02/13 Python
python获取磁盘号下盘符步骤详解
2019/06/19 Python
Puppeteer使用示例详解
2019/06/20 Python
Python虚拟环境的原理及使用详解
2019/07/02 Python
python 一个figure上显示多个图像的实例
2019/07/08 Python
在notepad++中实现直接运行python代码
2019/12/18 Python
TensorFlow设置日志级别的几种方式小结
2020/02/04 Python
python解释器安装教程的方法步骤
2020/07/02 Python
jupyter使用自动补全和切换默认浏览器的方法
2020/11/18 Python
纯CSS3实现Material Design效果
2017/03/09 HTML / CSS
html5图片上传预览示例分享
2014/04/14 HTML / CSS
马克华菲官方商城:Mark Fairwhale
2016/09/04 全球购物
Rosetta Stone官方网站:语言学习
2019/01/05 全球购物
八一建军节部队活动方案
2014/02/04 职场文书
课内比教学心得体会
2014/09/09 职场文书
Mysql数据库索引面试题(程序员基础技能)
2021/05/31 MySQL
Java基础之详解HashSet的使用方法
2021/06/30 Java/Android