密码安全与会话安全

安全 应用安全 数据安全
对于登录大家并不陌生,访问系统几乎都需要登录。看似简单的几个步骤,但里面涉及的安全问题却有很多。

[[419945]]

对于登录大家并不陌生,访问系统几乎都需要登录。因为系统也不知道是谁在访问,所以需要你告诉系统你是谁,还需要证明你真的是你,如何证明?给系统展示你的密码,因为密码只有你才拥有,你有这个密码,你就能证明你真的是你,这就是一个登录。

看似简单的几个步骤,但里面涉及的安全问题却有很多。

密码储存安全

首先我们看关于密码存储安全的问题。系统服务器需要存储用户密码,才能在用户登录时验证密码的正确性,但存储就会有泄露的风险,比如数据库被偷,服务器被入侵,内部员工泄露数据,被撞库等风险。因此我们需要认真地考虑如何安全存储用户密码。

我认为作为一名软件开发工程师, 严禁明文存储密码是common sense。那该如何解决不能明文存储密码的问题?也许看官你会说,md5 ?没错,md5 可以。那么md5属于什么?它是一种单向散列算法,单向散列算法主要就是通过算法生成一个摘要,来解决密码不能明文化问题。比如:

  1. md5(123456) = e10adc3949ba59abbe56e057f20f883e 

这样虽然能解决密码不明文化,但是它还是会存在安全问题。因为现代计算机的计算能力实在太强了,一秒可以计算十亿次 ,可以通过穷举对比的方式破解密码。这也就是所谓的彩虹表攻击。

解决被彩虹表攻击的问题对密码也有一定的要求,比如要求密码的复杂度,需要不同类型的字符进行组合,在生成摘要时加点盐来防止穷举破解密码。但这就安全了吗?还不够。一次算法远远不够满足安全要求,如md5(md5(md5(password+salt))),现在往往采用自适应的方式来存储密码,可以设置重复计算一万次,盐使用随机生成的16+位字符串。

这种方式虽然性能不会很好,但对于密码生成摘要存储来说,性能不好往往是好事,毕竟用户注册或修改密码只是一次操作,用户是可以接受的,但对于黑客来说,这是致命的,黑客从原来的一秒产生几百万甚至上千万的md5值,变成了一秒只能产生一个,黑客想要破解一个密码,从现代的计算机算力来看,需要上千年的时间。目前推荐的使用密码存储算法已不再推荐md5了,推荐采用Bcrypt Scrypt pdkdf2算法。

(很多可以通过MD5/SHA值进行反向查询,都是已经存储了大量的彩虹表)

密码传输安全

解决了密码存储安全,再来看密码传输安全。有人会说使用https就能解决网络传输的安全问题,但这还是不够。浏览器到认证服务器之间请求可能会经过一层或多层gateway,如nginx,zuul,spring cloud gateway等。很多系统都是在gateway处安装证书设置https协议,浏览器到gateway处是加密传输的,但gateway到认证服务器还是http协议。

攻击人可以在这条链路上窃取明文密码,那全链路https不就可以解决问题了?还不够,这些gateway内部都可能会接触到明文密码,都有密码泄露的风险,有些gateway不是我们负责的,无法保证他人会不会开个后门拿出明文密码,或者安全意识较低的程序员打印日志不小心把明文密码打印出来。那如何解决这个问题?我们可以采用浏览器传输密码之前就对密码先加密的方法。

加密方式分为对称加密与非对称加密。

对称加密:加密与解密用的是同一个密钥。如DES,AES非对称加密:加密与解密用的是不同的密钥,一个叫公钥,一个叫私钥,公钥加密的数据只能由对应的私钥才能解,如RSA。

如果采用对称加密方式,需要浏览器在调登录api之前,先获取认证服务器的密钥,拿到密钥后对密码进行加密传输,经过的gateway都只能获取密文,密码到了认证服务器,认证服务器再通过密钥对密文进行解密,获取到密码明文,就可以进行密码验证。但有一个安全问题,浏览器获取密钥也会经过gateway,如果gateway把密钥也打印到了日志中,密文也打印到了日志中,那攻击人同样可以通过日志获取明文密码。

既然对称加密不可取,我们来看看非对称加密。浏览器登录前经过gateway获取认证服务器的公钥,使用公钥进行加密,最终密文到认证服务器,再通过私钥解密拿到明文密码进行密码认证。这种方式gateway只能拿到公钥和密文,无法解密,就算打印到日志中,攻击人无法拿到明文密码了。

但这样就安全了吗?

如果攻击人拿gateway中的密文直接去调认证服务器中的登录api,认证服务器一样可以通过私钥进行解密,并登录成功。所以我们还需要加一些限制:保证密文有过期时间,并且是只能使用一次。

浏览器获取认证服务器公钥时,携带用户名到认证服务器,认证服务器生成随机数并与用户名关联,随机数只保存5分钟,随机数与公钥一起返回给浏览器。浏览器使用随机数加密码通过公钥一起加密调登录api,认证服务器通过私钥解密,获取到明文密码与随机数,验证随机数的有效性与合法性,都正常就进行正常登录,比较完随机数后立刻删除随机数,如不正常拒绝登录。

攻击人就算获取到了密码密文,公钥,随机数,也只能在5分钟之内赶在gateway正常请求登录之前,发起登录攻击,但这个难度太大。还有就是认证服务器保证客户端是gateway或可信的服务发起的请求,认证服务器可以对客户端做白名单限制,方式有很多种,在这就不一一赘述了。

但现在就安全了吗?还真不一定。如果攻击人攻破了gateway,在浏览器请求认证服务器获取公钥时,gateway返回攻击人颁发的公钥,待用户输入完账号密码后,浏览器虽然进行了加密,数据到了gateway,攻击人再通过自己的私钥进行解密拿到明文密码,再通过明文密码在登录页进行正常的登录,同样可以登录成功。因此浏览器也需要做安全验证,验证公钥的合法性。

认证服务器可以采用CA机构颁发的公钥,认证服务器与浏览器都相信CA机构(做安全总得相信点东西,如果什么都不信任就没法做安全了,有永无止境的安全问题),通过CA机构的方式验证公钥的合法性来避免中间人篡改公钥的问题(讲得不是很清楚,比如CA机构是个啥,为什么CA机构可信?这里面可聊的话题太多,有兴趣可以查看《密码学与网络安全》等书籍或一起探讨研究)。

那密码安全了吗?还是远远不够。比如黑客知道了你密码的长度,可以不断地调登录或修改密码的接口来试错,总会试出来正确的密码,因此需要对任何会验证密码合法性的接口都需要加频率限制。如登录连续错了5次锁5分钟,再错5次锁半小时,防止黑客试出密码。但这种方式也有问题。如竞争对手公司不断地使用用户的账号和错误的密码去登录,导致用户的账号一直处于被锁状态,正常用户也没法使用,这就违背了安全中的可用性。那就需要加ip限制和验证码机制了。为了用户的体验性,可以做成第一次登录用户可以正常登录,错误之后,就需要使用验证码的方式登录,超过5次锁定账号,同一ip登录错误次数过多,将ip加入黑名单中。

无密码安全

密码有很多安全问题,复杂密码对于用户来说也挺麻烦的,那采用无密码技术。没有密码是不是就安全了呢?虽然现在可以采用指纹登录与刷脸登录,但新的安全问题也随之而来。密码是需要私密性的,但指纹可以从照片中获取,美国国防部某个官员因在拍照时露出了大拇指,随后就有了这个大拇指的清晰指纹图(拍照的时候不要剪刀手或点赞了,最好指纹对准自己吧,手动狗头)。

还有就是存在不确定性,刷脸登录时,如果灯光太暗或太亮,脸部受伤了,化妆了,那登录能保证成功吗?脸部相似的人,登录时能保证区分开来吗?如果不能就违背了账号唯一性,日后审计也是个问题。还有一个问题就是不可修改。当密码泄露了可以修改密码,但你的指纹已经作为登录凭据了,换个手指头就好了,当十个手指头都用过了,那是不是该用脚指头了?当然无密码肯定是比有密码使用上更方便快捷,随着技术的发展,这些问题也都会解决,只是也会有更多的安全问题。

我们再来看会话安全(密码安全还有各种各样的问题,篇幅有限,不再聊了)。

会话标识储存安全

登录完成后,用户不可能每一次操作都需要输入密码。因此系统需要记录用户的登录状态,又称会话状态。常见的做法是系统保存session。session存入用户信息,生成随机数sessionId,将sessionId返回浏览器,并存入浏览器的cookie中,下一次用户访问系统,携带cookie,系统通过cookie找到session,就可以知道用户是谁

对于集群服务,用户首次登陆,访问的A服务器,A服务器存入session,下次访问到了B服务器,B服务没有session,认为用户没有登陆,提示用户需登陆,这是一个bug。我们将每台服务器都识别到有session就可以解决这个问题了。session存入redis,登陆时往redis存session,之后都从redis取session。或者每台服务器都有session,每台服务器的session同步也能解决这个问题。

不管采用哪种方式,都有一个安全风险,sessionId给出去了,不论sessionId是随机数生成还是加密算出来的字符串,黑客并不关心,黑客只关心这个字符串代表了用户的会话状态。黑客也不需要拿到密码只需要拿到这个字符串,就可以模拟用户进行诈骗,转账,发表非法政治评论等非法活动。

保护sessionId不被非法利用与保护密码同等重要。大多数情况下sessionId存储在cookie中,我们先了解cookie。

这是登录okta后生成的其中一个cookie,有name,value,domain,path,Expires/Max-Age,Httponly,Secure等属性,这里重点介绍其中几个。

  • Domain:cookie对于哪个域有效。这个cookie的域是thoughtworks.okta.com,则只有访问thoughtworks.okta.com下的api,浏览器才会将该cookie发送至后端服务器。这个值可以包含子域,如设置domain为okta.com时,访问thoughtworks.okta.com也会带上该cookie。
  • HttpOnly:当值为true时,告诉浏览器不能通过js访问到该cookie,只有在发送请求到后端时,才会携带该cookie。
  • Secure:当值为true时,告诉浏览器,只有访问协议问https的api时,才会携带该cookie。
  • Expires/Max-Age:cookie有两种,本地cookie与session cookie。如果设置了cookie的过期时间则为本地cookie,不设置为session cookie。session cookie的特点是没有具体的过期时间,随着浏览器关闭而清除。本地cookie即使浏览器关闭也不会清除,而是到了时间自动清除。这也是为什么关闭浏览器后再次打开浏览器有些系统需要重新登录,而有些不需要的原因。

知道cookie的几个特性后我们再来看看攻击人常用的几种攻击方式:XSS攻击,CSRF攻击。

会话标识传输安全

(1) XSS攻击叫做跨站脚本攻击,指用户的输入拼接了正常的html+js+css,变成了带有攻击性的html+js+css。浏览器可能无法识别具有攻击性的html+js+css,按照正常的逻辑执行代码,这可能会导致攻击人偷走cookie(XSS还有其他的危害,但这里仅讨论与会话标识相关)。如果黑客在html中插入隐藏的form表单,通过document.cookie()获取到浏览器中cookie,作为参数并自动发送post请求到攻击人的后端api中,攻击人就可以拿到用户的cookie,也就可以拿到sessionId了。这种方式可以通过设置cookie的HttpOnly为true来防止js获取cookie值。从而避免通过XSS攻击获取sessionId。

(2) CSRF攻击叫做跨站请求伪造。XSS攻击是指本网站的代码执行攻击脚本造成了对本网站的影响。CSRF攻击则是用户打开了其他网站,浏览器执行了其他网站的攻击脚本,却对本网站造成了伤害。举个例子,当我在浏览器中登录了某银行的网站,进行了转账操作,浏览器调用了

https://www.xxx.com/transfer?toBankId=123456&money=100,我的账户少了100块,收到短信扣了100块。这时来了一封邮件,标题为你想得到力量吗?内容是一个链接,我点击这个链接,看到url是www.yyy.com/index.htm,立马又收到一个短信,我账号又少了1000块,我刷新下页面,又少1000块。打开页面查看源码,发现有个隐藏的标签,

src=https://www.xxx.com/transfer?toBankId=123456&money=1000。也就意味着每次刷新页面,浏览器都会执行一次

https://www.xxx.com/transfer?toBankId=123456&money=1000 GET请求。大多数浏览器有同源策略(协议\主机\端口组成源),其中一个限制是同源的网页才会共享cookie。但浏览器对html标签有白名单,img就是其中之一,通过img标签的src就可以发送get 请求,因访问的是xxx(银行)的域名,携带了cookie,银行认为是合法请求,转账成功。因img是get请求,那把转账等高危操作改成post接口不就可以了? 也不行,因为form表单的post请求也在白名单中。

CSRF攻击之所以成功,是因为攻击人可以完全伪造用户的请求,那让攻击人无法伪造就可以解决这个问题了。在转账时,要求用户再次输入密码或输入验证码,就可以解决CSRF攻击。转账操作可以这么做,发表评论这类的操作,每次都要求用户输入密码或验证码用户体验就很很差了。

(3) 还有Referer check,浏览器发送请求时,携带Referer header,值为网站url中的域名,异常转账时,虽然调用的www.xxx.com的api,但referer 值为www.yyy.com。在服务端只要验证Referer值就可以判断这是不是一个CSRF攻击。这种方式也有问题,就是完全相信了第三方(浏览器)。对于低版本的浏览器已经有办法可以篡改Referer值,高版本的浏览器目前无法篡改,如用户使用低版本的浏览器,Referer check将无法保证安全性。

那还有什么办法可以解决CSRF攻击的问题? 我们来看下okta是如何做到解决这个问题的。我们登陆okta成功后,打开网页源代码查看html,搜索token可以看到

在span中保存了一个token值 我们再创建一个tab页

打开浏览器的f12,查看网络请求,可以看到request header中有x-okta-xcrftoken这个header。

这就是为了解决CSRF攻击的方式:CSRF Token方式。

csrf token工作原理就是在用户登录成功后,服务端生成token并保存一段时间,返回给浏览器,浏览器保存在html标签中。当用户操作访问后端api时,将该token放入request header中。后端验证该token 的合法性即可判断是否是CSRF攻击。这种方式能生效的重点在于攻击人无法拿到目标网站的html。

最近在思考一个问题,就是如果黑客同时发起XSS攻击和CSRF攻击,这种方式是不是也失效了?黑客通过XSS攻击,获取到了CSRF token,攻击人立马发送钓鱼邮件给目标用户,目标用户点击了链接,网站打开时,先从黑客处获取CSRF Token,并携带CSRF Token发起了CSRF攻击,还有个前提是浏览器版本太低没有Referer,那不就可以攻击成功了?(我杞人忧天了吗?) cookie+session有这么多安全需要考虑,那不要cookie+session不就没这么多问题吗?现在流行的jwt就可以做到无session的登录认证,但jwt也有各种各样的安全问题。

【本文是51CTO专栏作者“ThoughtWorks”的原创稿件,微信公众号:思特沃克,转载请联系原作者】

戳这里,看该作者更多好文

 

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2023-05-08 15:21:05

JavaScripWeb 存储数据存储

2010-05-05 19:05:03

负载均衡器会话保持

2021-06-09 13:28:40

密码安全身份认证数据安全

2019-06-14 15:10:54

密码管理存储密码攻击

2021-09-03 17:01:42

密码安全

2013-07-17 17:15:57

2020-12-07 11:42:37

物联网密码置换网络安全

2020-12-01 15:35:06

Web安全明文密码漏洞

2020-03-01 17:49:16

Linux脚本语言操作系统

2013-07-15 10:15:28

2012-11-30 13:43:30

2009-11-03 13:46:56

Oracle密码

2011-07-06 09:19:37

2020-12-28 09:42:25

弱密码密码加密

2011-12-26 15:43:01

2012-08-14 10:34:01

2011-08-16 17:18:44

iPhone开发安全

2018-03-21 07:08:40

2013-09-12 11:07:01

2021-02-02 14:02:48

网络安全对称密码密钥
点赞
收藏

51CTO技术栈公众号