综述:
待 Update:QUIC, SSO, HSTS等协议研究,SHA-1 可破解
总结: 没有绝对的安全,能不自己做就不自己做, 要么 openID(不了解), 要么OAuth(推荐QQ & Github)
本文不考虑键盘记录器等养毒行为, 本文不考虑堆栈溢出, exploit, SQL注入等。 单纯谈论偏向登陆会话过程
思维养成key: 时间, 信任
无解问题: CSRF, 中间人攻击(有应对方案,但没有根本解决的方案)
SSL必加, 客户端加密可选, localStorage 存 token(CSRF), 指纹ID:UUID(Java),cookie 设计Chrome Dev参考网站猜测大致保存用户名,session, jsessionid, uuid, token(浏览器不支持localStorage), Hm_lvt_siteid(记录访客当前访问序列的开始时间,如果没有设置这个cookie,则访客为新访客。当本次访问是一个新的访问开始时,更新该cookie为当前时间.), Hm_lpvt_siteid(当前浏览页面时的时间,每次浏览时设置该cookie为当前时间)等
正文
09年老5则
加密单向并且强壮(MD5我100个不推荐, 看刷ctf的整天口算md5玩, 看开头,所以推荐 SHA2)
密码长度强制长
密码字符无限制, 至少满足ascii(出开头结束空格)
不要给用户发送他的密码(VMware就这么蠢比过) IMAP 应用,中间人攻击 between the mail client and GMail
- Anyone who had access to the network or SMTP systems involved in the hand-off chain between VMware’s systems and Google.
- Anyone who has administrative access to Google’s email systems, including the eternal GMail backups.
- Anyone capable of compromising the integrity of Google’s systems.
- Anyone capable of compromising the integrity of my laptop.
- Anyone who gains access to a public machine I was using after I forgot to log out of GMail.
OpenID 不适合作为安全的关键部分, 但大部分适用
忠告: Don’t implement your own authentication system unless you absolutely have to
总结: 赞忠告,为啥不试试OAuth 2.0 操作之类的?我选 github & qq sdk
初步探索
token, user_id, track ip
产生一个随机token发到客户端作为cookie, 这个cookie 与 服务器做出匹配来确定用户
除非实现session策略或者网站分布在集群之类的,不要用数据库存储session
hash + salt 密码加密, 打算加盐(一个用户一种盐)防彩虹表?
Wiki
认证分两部分: 登陆表单, per-request-check
会话劫持
方式:
猜测
fixation: xss, 网络嗅探
应对:
cookie设置 HTTP only。 可仿浏览器伪造,仿 js 注入
登陆不仅仅依赖sessionid
session 时间, client指纹(一般是设备信息决定)变化, 登出请求 时销毁cookie, 清理session
Note: 指纹举例 fingerprint=hashhmac(′sha256′, _SERVER[‘HTTP_USER_AGENT’], hash(‘sha256’, $_SERVER[‘REMOTE_ADDR’], true));
SSL 是最好的仿白文传送, 对比而言, 别用客户端加密这个方法(因为劫持了就相当于拿到了加密,加密本身也就没了效果)
所以要加密就在server端加密
盐的生成可以从加密来挑字符,关于盐的长度: Even 4 random bytes of salt will increase the complexity of a rainbow table attack by a factor of 4 billion.
SSL 是必须的,无论如何这个都必须
Bcrypt
Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power…. Cryptotheoretically, this is no stronger than the standard Blowfish key schedule, but the number of rekeying rounds is configurable; this process can therefore be made arbitrarily slow, which helps deter brute-force attacks upon the hash or salt.
SSL安装: 自己做个证书强迫用户装(又是12306), 买(阿里送了个1000元一年的, 想想都贵), let ‘s encrypt(免费 且 贼棒,crontab设置个90天内翻新就行了)
爆力攻击减速, 如果失败, 等待一定时间后返回失败(考虑用户体验取舍,建议可加)
惯用行为: 。。。。感觉这个太他妈难, 就是类似twitter那样换个浏览器换个ip换个设备立马晓得然后通知你
验证码(CAPTCHA): 这是个用户体验很差的东西,想想12306? 总之做的有点意思或者大气上档次才行
休息一下
攻击:堆栈溢出。 解决:防伪式编程, 验证和过滤恶意输入
攻击:留言,打倒GCD这种神奇操作。 解决: 苟利国家生死以,岂因福祸避趋之。 给他过掉
错误:仿debug模式报异常等。 解决:控制好异常的边界
初步设计Cookie
Cookie 设计, 三个东西:
用户名
登陆序列, 仅当强制用户输入口令时更新
登陆 token
仅一个登录Session内有效
新的登录Session会更新它
登录Token是单实例登录 Singleton
当前两者正确,第三者却不正确时(有人盗用更新了token)用户回来访问时不对,系统便清除登陆序列和token令cookie失效
重要操作必须输入口令, 比如支付, 改密信息之类的, 不能因cookie直接操作(大概想防XSS?)
密保设定私人信息(比如你爸妈叫啥)有点白痴,因为可以社工之类的
邮件重置较为安全, 根据用户(uuid,timestamp,token等)生成MD5 url,并设定操作权限时间。
系统操作封IP之类的, 冻结账户这种操作由用户决定, 少做让用户反感的事
重点:必看 Cookie 参数详解
CSRF Token
Token 保存
csrf token 保存在浏览器的localStorage, 不是cookie
请求时利用 csrf token 作为盐值, 每次请求的时候使用CsrfToken作为盐值对参数进行Md5签名,网关从请求的Cookie中取出userToken并AES解密出CsrfToken,再进行验签。用户退出或者userToken时效的时候都会主动从LocalStorage中清除CsrfToken,从cookie中清除Token和用户信息,任何需要访问用户登录态的数据因为都需要加密,所以首先会判断CsrfToken是否存在,不存在直接跳登录页了。
这样一来, 盐值不参与请求, 只是加密用
短信检测考虑成本必加时限和图片验证
Token 可选位置
验证 HTTP Referrer 字段;(仿盗图用也不错,但可以手工修改,所以不是好方案)
在请求地址中添加 token 并验证, 表单生成带token
在 HTTP 头中自定义属性并验证。
总结: CSRF 难解,可以说各有利弊,换句话说差不多就是解不了
- Note: Ajax 同源, CORS 跨源, JSONP
Demo
数据结构设计: 用户数据 和 认证分开存储, 从而便于认证扩展
验证成功的token要更新
客户端加密公开, 所以要加密就加盐,让他推也难受
实例示范:
浏览器主要完成以下工作:
获取用户输入的用户名及密码
通过输入的用户名和密码,进行哈希,得到浏览器端密文
将用户名和密文提交给后端
// 密码与用户名的哈希
function encryptPwd(username, password) {
username = username.toLowerCase();
return sha256(
username + sha256 (
sha256(sha256(sha256(password))) + sha256(username)
)
);
}
$scope.login = function(){
// 检查用户名和密码的合法性,比如是否输入,长度是否足够等
if($scope.check()) {
return;
}
$scope.successMessage = '';
$scope.errorMessage = '';
$scope.status = 'loading';
// 向后端提交登录请求
$resource('/user/login')
.save({
username: $scope.username,
password: encryptPwd($scope.username, $scope.password)
}, function(res){
$scope.status = 'done';
$scope.successMessage = 'login successful!';
}, function(reason){
$scope.status = 'done';
$scope.errorMessage = reason.data || 'failed';
});
};
后端验证:
获取前端提交的用户名及浏览器端密文
根据用户名,在数据库中查询出对应的盐 id
通过盐 id 取出对应的盐,再通过用户名、浏览器端密文和盐算出后端密文
根据用户名和后端密文到用户表查询,如果有结果,则表明登录信息正确,返回给浏览器登录成功的响应
生成新的盐,算出新的后端密文,并将两者更新到数据库中
function encryptPwd(usr, pwd, salt){
usr = usr.toLowerCase();
return sha256(
sha256(usr + sha256(pwd + salt)) + salt + sha256(usr + salt)
)
}
function login(req, res, next){
// 用户名密码获取和检查已省略
// 根据用户名,获取盐 id
req.models.user
.findOne({select:['username', 'saltId'], where: {username: username}})
.exec(function(err, userDoc){
if(err) return next(err);
if(!userDoc) return next(new Error('username not exists'));
// 取盐
req.models.salt
.findOne({id: userDoc.saltId})
.exec(function(err, saltDoc){
if(err) return next(err);
if(!saltDoc) return next(new Error('can NOT find salt'));
// 根据用户名、密码和盐推算出密文
var pwdHash = encryptPwd(username, password, saltDoc.salt);
// 在数据库中核对用户名和密文
req.models.user
.findOne({select: ['id'], where: {username: username, password: pwdHash }})
.exec(function(err, doc){
if(err) return next(err);
if(!doc) return next(new Error('password error'));
res.json({
username: username
});
return updateSalt(saltDoc, userDoc, password, next);
});
});
});
}
前面返回给用户成功登录的响应之后,调用了更新盐和密文的方法,该方法具体流程如下:
生成并存储新盐
根据新盐、用户名和浏览器端密文,生成新的后端密文
存储后端密文到用户信息表
function updateSalt(saltDoc, userDoc, passwordInputed, next){
saltDoc.salt = Math.random().toString(15).substr(3, 27);
saltDoc.save(function(err){
if(err) return next(err);
userDoc.password = encryptPwd(userDoc.username, passwordInputed, saltDoc.salt);
userDoc.save(function(err){
if(err) return next(err);
return next();
});
});
}
数据存储这块,使用了 Waterline 这个 ORM 中间件使用它的目的主要是为了将用户信息和盐存储到不同的地方。本例中将盐用 sails-disk 存储到了文件中,用户信息用 sails-mongo 存储到了 MongoDB 中。
后记
-
盗用 cookie ,获取敏感信息。
利用植入 Flash ,通过 crossdomain 权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
利用 iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果。
防御: 过滤; Http制定头类型, 避免被解析为html
TLS MITM 这玩意能解?
参考链接:
https://www.zhihu.com/question/20744215
一般能防MITM的要具有以下特征:
1、握手加密而且具有可信CA(SSL、TLS之类,CNNIC和WoSign就不点评了)或拥有预协商密钥(L2TP)
2、加密方法足够强大(AES、CHACHA20之类,RC4和SM2就不点评了)
综上,一般情况下,以下的协议是安全的:
1、HTTPS(排除RC4、3DES算法,排除CNNIC、WoSign的CA证书)
2、OpenVPN(基于TLS,只要你能连上就是安全的)
3、L2TP Over IPSec(必须要Over IPSec,没了就没加密了)
4、IKEv2(基于TLS和可信CA发的证书,安全性比OpenVPN还好)
5、SSTP(基于TLS和可信CA发的证书,安全性和IKEv2相同,稳定性加强,用443端口)
6、ShadowSocks(基于N+1种加密算法,只要你不选RC4或RC4-MD5算法就是安全的)
7、任何用SSL Stream过又验证CA甚至客户端证书的TCP连接(UDP大家都懂,DNS什么的)