ReZero's Utopia.

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

Word count: 3.3kReading time: 14 min
2017/10/14 Share

综述:

建议点击文中所有的链接获取较为详细的信息 待 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什么的)

CDN to Real IP

  1. First let us know about cdn. The next content list as follows from wiki.

    A content delivery network or content distribution network (CDN) is a globally distributed network of proxy servers deployed in multiple
    data centers. The goal of a CDN is to serve content to end-users with
    high availability and high performance. CDNs serve a large fraction of
    the Internet content today, including web objects (text, graphics and
    scripts), downloadable objects (media files, software, documents),
    applications (e-commerce, portals), live streaming media, on-demand
    streaming media, and social networks. The term CDN means many things to different people and is an umbrella
    term that covers a lot of different types of content delivery
    services. Video streaming, software downloads, web and mobile content
    acceleration, licensed/managed CDN, transparent caching, and services
    to measure CDN performance, load balancing, multi-CDN switching and
    analytics and cloud intelligence. It’s a complex ecosystem with a lot
    of vendors both large and small and some CDN vendors cross over into
    other industries like security and WAN optimization.[1] Content owners such as media companies and e-commerce vendors pay CDN
    operators to deliver their content to their end-users. In turn, a CDN
    pays ISPs, carriers, and network operators for hosting its servers in
    their data centers.

  2. How to get the CDN service?
    At home, you can try to use baidu CloudSpeed which is free.
    just check the info and change the dns as it provide on your domain register site.
    but if you were not in China, you can see this link Ten free cdn for wordpress

  3. Here the question come:
    How could we get the real ip of the server instead of cdn?

    • let us try ping it, actually we know that ping may not work for the without www child domain, so you should ping like xxx.cn not www.xxx.cn.This because many operators did not provide the www cdn service.
    • since we mention the second-level domain, you should try all the possible child domain.(whatever the way you choose, alexa , chinaz, or any tool could brute force… )
    • find the history (How about netcraft.com)(espically this way, I found the target Ip from ip138 history)
    • still ping, but with vpn or any foreign proxy.
    • guess the phpinfo (It seems seek far and neglect what lies close at hand, anyway it is a good idea.)
    • The last but effective significantly method is e-mail. For example, you can register account on the target site. Most of them will send you an email to you. You can check the real content in it to find its real IP.
CATALOG
  1. 1. 综述:
  2. 2. 正文
    1. 2.1. 09年老5则
    2. 2.2. 初步探索
    3. 2.3. Wiki
    4. 2.4. 休息一下
    5. 2.5. 初步设计Cookie
    6. 2.6. CSRF Token
    7. 2.7. Demo
    8. 2.8. 后记
  • CDN to Real IP