如何实现一个安全的Web登录

  • 2017-10-14
  • 54
  • 0

综述:

建议点击文中所有的链接获取较为详细的信息

待 Update:SSO, QUIC、HSTS等协议研究, SHA-1 可破解

总结: 没有绝对的安全,能不自己做就不自己做, 要么 openID(不了解), 要么OAuth(推荐QQ & Github)

本文不考虑键盘记录器等养毒行为, 本文不考虑堆栈溢出, exploit, SQL注入等。 单纯谈论偏向登陆会话过程

思维养成key: 时间, 信任

无解问题: CSRF, 中间人攻击(有应对方案,但没有根本解决的方案)

方案:SSL必加(非对称,不是绝对但是已经超安全了), 客户端加密可选, 加密关键词(非对称, Hash 加盐), localStorage 存 token(仿CSRF), 指纹ID:比如UUID(Java),cookie 设计参考Chrome Dev查看知名网站,猜测大致保存用户名,session, jsessionid, uuid, token(浏览器不支持localStorage), Hm_lvt_siteid, Hm_lpvt_siteid等等,文中有部分cookie 参数链接

正文

09年老5则

  1. 加密单向并且强壮(MD5我100个不推荐, 看刷ctf的整天口算md5玩, 看开头,所以推荐 SHA2)

  2. 密码长度强制长

  3. 密码字符无限制, 至少满足ascii(出开头结束空格)

  4. 不要给用户发送他的密码(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.
  5. OpenID 不适合作为安全的关键部分, 但大部分适用

忠告: Don't implement your own authentication system unless you absolutely have to

总结: 赞忠告,为啥不试试OAuth 2.0 操作之类的?我选 github & qq sdk

初步探索

  1. token, user_id, track ip

  2. 产生一个随机token发到客户端作为cookie, 这个cookie 与 服务器做出匹配来确定用户

  3. 除非实现session策略或者网站分布在集群之类的,不要用数据库存储session

  4. hash + salt 密码加密, 打算加盐(一个用户一种盐)防彩虹表?

Wiki

  1. 认证分两部分: 登陆表单, per-request-check

  2. 会话劫持

  • 方式:

    • 猜测

    • fixation: xss, 网络嗅探

  • 应对:

    • cookie设置 HTTP only。 可仿浏览器伪造,仿 js 注入

    • 楼上那个, 不好意思,可解

    • 登陆不仅仅依赖sessionid

    • session 时间, client指纹(一般是设备信息决定)变化, 登出请求 时销毁cookie, 清理session

    • Note: 指纹举例 $fingerprint = hash_hmac('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 是必须的,无论如何这个都必须, 即便这也不是最安全的方案,比如:https://www.zhihu.com/question/22779469

    • 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

  1. Cookie 设计, 三个东西:
  • 用户名

  • 登陆序列, 仅当强制用户输入口令时更新

  • 登陆 token

    • 仅一个登录Session内有效

    • 新的登录Session会更新它

    • 登录Token是单实例登录 Singleton

    • 当前两者正确,第三者却不正确时(有人盗用更新了token)用户回来访问时不对,系统便清除登陆序列和token令cookie失效

  1. 重要操作必须输入口令, 比如支付, 改密信息之类的, 不能因cookie直接操作(大概想防XSS?)

  2. 密保设定私人信息(比如你爸妈叫啥)有点白痴,因为可以社工之类的

  3. 邮件重置较为安全, 根据用户(uuid,timestamp,token等)生成MD5 url,并设定操作权限时间。

  4. 系统操作封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 难解,可以说各有利弊,换句话说差不多就是解不了

  • Token 必知必会

  • Note: Ajax 同源, CORS 跨源, JSONP

Demo

  1. 数据结构设计: 用户数据 和 认证分开存储, 从而便于认证扩展

  2. 验证成功的token要更新

  3. 客户端加密公开, 所以要加密就加盐,让他推也难受

  4. 实例示范:

浏览器主要完成以下工作:

获取用户输入的用户名及密码
通过输入的用户名和密码,进行哈希,得到浏览器端密文
将用户名和密文提交给后端

// 密码与用户名的哈希
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 中。

后记

  1. XSS & CSRF , SSRF 不考虑
  • 盗用 cookie ,获取敏感信息。

  • 利用植入 Flash ,通过 crossdomain 权限设置进一步获取更高权限;或者利用Java等得到类似的操作。

  • 利用 iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。

  • 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。

  • 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果。

  • 防御: 过滤; Http制定头类型, 避免被解析为html

  1. TLS MITM 这玩意能解?

参考链接:

https://www.zhihu.com/question/20744215

https://www.v2ex.com/t/161520

  • 一般能防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什么的)

评论

还没有任何评论,你来说两句吧

正在获取,请稍候...
00:00/00:00