认证与会话管理

“认证”是最容易理解的一种安全。如果一个系统缺乏认证手段,明眼人都能看出来这是“不安全”的。最常见的认证方式就是用户名与密码,但认证的手段却远远不止于此。本章将介绍Web中常见的认证手段,以及一些需要注意的安全问题。

Who am I?

很多时候,人们会把“认证”和“授权”两个概念搞混,甚至有些安全工程师也是如此。实际上“认证”和“授权”是两件事情,认证的英文是Authentication,授权则是Authorization。分清楚这两个概念其实很简单,只需要记住下面这个事实:

认证的目的是为了认出用户是谁,而授权的目的是为了决定用户能够做什么。

形象地说,假设系统是一间屋子,持有钥匙的人可以开门进入屋子,那么屋子就是通过“锁和钥匙的匹配”来进行认证的,认证的过程就是开锁的过程。

钥匙在认证过程中,被称为“凭证”(Creden-tial),开门的过程,在互联网里对应的是登录(Login)。

可是开门之后,什么事情能做,什么事情不能做,就是“授权”的管辖范围了。

如果进来的是屋子的主人,那么他可以坐在沙发上看电视,也可以进到卧室睡觉,可以做任何他想做的事情,因为他具有屋子的“最高权限”。可如果进来的是客人,那么可能就仅仅被允许坐在沙发上看电视,而不允许其进入卧室了。

可以看到,“能否进入卧室”这个权限被授予的前提,是需要识别出来者到底是主人还是客人,所以如何授权是取决于认证的。

现在问题来了,持有钥匙的人,真的就是主人吗?如果主人把钥匙弄丢了,或者有人造了把一模一样的钥匙,那也能把门打开,进入到屋子里。

这些异常情况,就是因为认证出现了问题,系统的安全直接受到了威胁。认证的手段是多样化的,其目的就是为了能够识别出正确的人。如何才能准确地判断一个人是谁呢?这是一个哲学问题,在被哲学家们搞清楚之前,我们只能够依据人的不同“凭证”来确定一个人的身份。钥匙仅仅是一个很脆弱的凭证,其他诸如指纹、虹膜、人脸、声音等生物特征也能够作为识别一个人的凭证。认证实际上就是一个验证凭证的过程。

如果只有一个凭证被用于认证,则称为“单因素认证”;如果有两个或多个凭证被用于认证,则称为“双因素(Two Factors)认证”或“多因素认证”。一般来说,多因素认证的强度要高于单因素认证,但是在用户体验上,多因素认证或多或少都会带来一些不方便的地方。

密码的那些事儿

密码是最常见的一种认证手段,持有正确密码的人被认为是可信的。长期以来,桌面软件、互联网都普遍以密码作为最基础的认证手段。

密码的优点是使用成本低,认证过程实现起来很简单;缺点是密码认证是一种比较弱的安全方案,可能会被猜解,要实现一个足够安全的密码认证方案,也不是一件轻松的事情。“密码强度”是设计密码认证方案时第一个需要考虑的问题。在用户密码强度的选择上,每个网站都有自己的策略。

注册页面的密码强度要求

一般在用户注册时,网站告知用户其所使用密码的复杂度。

注册页面的密码强度要求

目前并没有一个标准的密码策略,但是根据OWASP推荐的一些最佳实践,我们可以对密码策略稍作总结。

密码长度方面:

普通应用要求长度为6位以上;

重要应用要求长度为8位以上,并考虑双因素认证。

密码复杂度方面:

密码区分大小写字母;

密码为大写字母、小写字母、数字、特殊符号中两种以上的组合;

不要有连续性的字符,比如1234abcd,这种字符顺着人的思路,所以很容易猜解;

尽量避免出现重复的字符,比如1111。

除了OWASP推荐的策略外,还需要注意,不要使用用户的公开数据,或者是与个人隐私相关的数据作为密码。比如不要使用QQ号、身份证号码、昵称、电话号码(含手机号码)、生日、英文名、公司名等作为密码,这些资料往往可以从互联网上获得,并不是那么保密。

微博网站Twitter在用户注册的过程中,列出了一份长达300个单词的弱密码列表,如果用户使用的密码被包含在这个列表中,则会提示用户此密码不安全。

目前黑客们常用的一种暴力破解手段,不是破解密码,而是选择一些弱口令,比如123456,然后猜解用户名,直到发现一个使用弱口令的账户为止。由于用户名往往是公开的信息,攻击者可以收集一份用户名的字典,使得这种攻击的成本非常低,而效果却比暴力破解密码要好很多。

密码的保存也有一些需要注意的地方。一般来说,密码必须以不可逆的加密算法,或者是单向散列函数算法,加密后存储在数据库中。这样做是为了尽最大可能地保证密码的私密性。即使是网站的管理人员,也不能够看到用户的密码。在这种情况下,黑客即使入侵了网站,导出了数据库中的数据,也无法获取到密码的明文。

2011年12月,国内最大的开发者社区CSDN的数据库被黑客公布在网上。令人震惊的是,CSDN将用户的密码明文保存在数据库中,致使600万用户的密码被泄露。明文保存密码的后果很严重,黑客们曾经利用这些用户名与密码,尝试登录了包括QQ、人人网、新浪微博、支付宝等在内的很多大型网站,致使数以万计的用户处于风险中。

一个提供彩虹表查询的MD5破解网站

为了避免密码哈希值泄露后,黑客能够直接通过彩虹表查询出密码明文,在计算密码明文的哈希值时,增加一个“Salt”。“Salt”是一个字符串,它的作用是为了增加明文的复杂度,并能使得彩虹表一类的攻击失效。

Salt的使用如下:

1
MD5(Username+Password+Salt)

其中,Salt = abcddcba……(随机字符串)。

Salt应该保存在服务器端的配置文件中,并妥善保管。

多因素认证

对于很多重要的系统来说,如果只有密码作为唯一的认证手段,从安全上看会略显不足。因此为了增强安全性,大多数网上银行和网上支付平台都会采用双因素认证或多因素认证。

比如中国最大的在线支付平台支付宝,就提供很多种不同的认证手段:

支付宝提供的多种认证方式

除了支付密码外,手机动态口令、数字证书、宝令、支付盾、第三方证书等都可用于用户认证。这些不同的认证手段可以互相结合,使得认证的过程更加安全。密码不再是唯一的认证手段,在用户密码丢失的情况下,也有可能有效地保护用户账户的安全。

多因素认证提高了攻击的门槛。比如一个支付交易使用了密码与数字证书双因素认证,成功完成该交易必须满足两个条件:一是密码正确;二是进行支付的电脑必须安装了该用户的数字证书。因此,为了成功实施攻击,黑客们除了盗取用户密码外,还不得不想办法在用户电脑上完成支付,这样就大大提高了攻击的成本。

Session与认证

密码与证书等认证手段,一般仅仅用于登录(Login)的过程。当登录完成后,用户访问网站的页面,不可能每次浏览器请求页面时都再使用密码认证一次。因此,当认证成功后,就需要替换一个对用户透明的凭证。这个凭证,就是SessionID。

当用户登录完成后,在服务器端就会创建一个新的会话(Session),会话中会保存用户的状态和相关信息。服务器端维护所有在线用户的Session,此时的认证,只需要知道是哪个用户在浏览当前的页面即可。为了告诉服务器应该使用哪一个Ses-sion,浏览器需要把当前用户持有的SessionID告知服务器。

最常见的做法就是把SessionID加密后保存在Cookie中,因为Cookie会随着HTTP请求头发送,且受到浏览器同源策略的保护(参见“浏览器安全”一章)。

Cookie中保存的SessionID

SessionID一旦在生命周期内被窃取,就等同于账户失窃。同时由于SessionID是用户登录之后才持有的认证凭证,因此黑客不需要再攻击登录过程(比如密码),在设计安全方案时需要意识到这一点。

Session劫持就是一种通过窃取用户Ses-sionID后,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。

Cookie泄露的途径有很多,最常见的有XSS攻击、网络Sniff,以及本地木马窃取。对于通过XSS漏洞窃取Cookie的攻击,通过给Cookie标记httponly,可以有效地缓解XSS窃取Cookie的问题。但是其他的泄露途径,比如网络被嗅探,或者Cookie文件被窃取,则会涉及客户端的环境安全,需要从客户端着手解决。

SessionID除了可以保存在Cookie中外,还可以保存在URL中,作为请求的一个参数。但是这种方式的安全性难以经受考验。

在手机操作系统中,由于很多手机浏览器暂不支持Cookie,所以只能将SessionID作为URL的一个参数用于认证。安全研究者kxlzx曾经在博客上列出过一些无线WAP中因为sid泄露所导致的安全漏洞。其中一个典型的场景就是通过Ref-erer泄露URL中的sid,QQ的WAP邮箱曾经出过此漏洞,测试过程如下。

首先,发送到QQ邮箱的邮件中引用了一张外部网站的图片:

然后,当手机用户用手机浏览器打开QQ邮箱时:

在手机中浏览QQ邮箱

手机浏览器在解析图片时,实际上是发起了一次GET请求,这个请求会带上Referer。

Referer的值为:

可以看到sid就包含在Referer中,在www.inbreak.net的服务器日志中可以查看到此值,QQ邮箱的sid由此泄露了。

在sid的生命周期内,访问包含此sid的链接,就可以登录到该用户的邮箱中。

在生成SessionID时,需要保证足够的随机性,比如采用足够强的伪随机数生成算法。现在的网站开发中,都有很多成熟的开发框架可以使用。这些成熟的开发框架一般都会提供Cookie管理、Session管理的函数,可以善用这些函数和功能。

Session Fixation攻击

什么是Session Fixation呢?举一个形象的例子,假设A有一辆汽车,A把汽车卖给了B,但是A并没有把所有的车钥匙交给B,还自己藏下了一把。这时候如果B没有给车换锁的话,A仍然是可以用藏下的钥匙使用汽车的。

这个没有换“锁”而导致的安全问题,就是SessionFixation问题。

在用户登录网站的过程中,如果登录前后用户的SessionID没有发生变化,则会存在Session Fix-ation问题。

具体攻击的过程是,用户X(攻击者)先获取到一个未经认证的SessionID,然后将这个Ses-sionID交给用户Y去认证,Y完成认证后,服务器并未更新此SessionID的值(注意是未改变Ses-sionID,而不是未改变Session),所以X可以直接凭借此SessionID登录进Y的账户。

X如何才能让Y使用这个SessionID呢?如果SessionID保存在Cookie中,比较难做到这一点。但若是SessionID保存在URL中,则X只需要诱使Y打开这个URL即可。在上一节中提到的sid,就需要认真考虑Session Fixation攻击。

在discuz 7.2的WAP版本中,就存在这样的一个Session Fixation攻击。

认证前的URL是

1 2 3
http://bbs.xxxx.com/wap/index.php? action=forum&fid=72&sid=2iu2pf

其中,sid是用于认证的SessionID。用户登录后,这个sid没有发生改变,因此黑客可以先构造好此URL,并诱使其他用户打开,当用户登录完成后,黑客也可以直接通过此URL进入用户账户。

解决Session Fixation的正确做法是,在登录完成后,重写SessionID。

如果使用sid则需要重置sid的值;如果使用Cookie,则需要增加或改变用于认证的Cookie值。值得庆幸的是,在今天使用Cookie才是互联网的主流,sid的方式渐渐被淘汰。而由于网站想保存到Cookie中的东西变得越来越多,因此用户登录后,网站将一些数据保存到关键的Cookie中,已经成为一种比较普遍的做法。Session Fixation攻击的用武之地也就变得越来越小了。

Session保持攻击

一般来说,Session是有生命周期的,当用户长时间未活动后,或者用户点击退出后,服务器将销毁Session。Session如果一直未能失效,会导致什么问题呢?前面的章节提到Session劫持攻击,是攻击者窃取了用户的SessionID,从而能够登录进用户的账户。

但如果攻击者能一直持有一个有效的Ses-sion(比如间隔性地刷新页面,以告诉服务器这个用户仍然在活动),而服务器对于活动的Session也一直不销毁的话,攻击者就能通过此有效Session一直使用用户的账户,成为一个永久的“后门”。

但是Cookie有失效时间,Session也可能会过期,攻击者能永久地持有这个Session吗?

一般的应用都会给session设置一个失效时间,当到达失效时间后,Session将被销毁。但有一些系统,出于用户体验的考虑,只要这个用户还“活着”,就不会让这个用户的Session失效。从而攻击者可以通过不停地发起访问请求,让Session一直“活”下去。

安全研究者kxlzx曾经分享过这样的一个案例,使用以下代码保持Session:

其原理就是不停地刷新页面,以保持Session不过期:

测试环境

而Cookie是可以完全由客户端控制的,通过发送带有自定义Cookie头的HTTP包,也能实现同样的效果。

安全研究者cnqing曾经开发过一个叫“Ses-sionIE”的工具,其中就实现了Session状态的保持:

SessionIE工具的界面

想使得Cookie不失效,还有更简单的方法。

在Web开发中,网站访问量如果比较大,维护Session可能会给网站带来巨大的负担。因此,有一种做法,就是服务器端不维护Session,而把Ses-sion放在Cookie中加密保存。当浏览器访问网站时,会自动带上Cookie,服务器端只需要解密Cookie即可得到当前用户的Session了。这样的Session如何使其过期呢?很多应用都是利用Cookie的Expire标签来控制Session的失效时间,这就给了攻击者可乘之机。

Cookie的Expire时间是完全可以由客户端控制的。篡改这个时间,并使之永久有效,就有可能获得一个永久有效的Session,而服务器端是完全无法察觉的。

以下代码由JavaScript实现,在XSS攻击后将Cookie设置为永不过期。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// 让一个Cookie不过期 anehta.dom.persistCookie = function(cookieName){ if (anehta.dom.checkCookie(cookieName) == false){ return false; } try{ document.cookie = cookieName + "=" + anehta.dom.getCookie(cookieName) + ";" + "expires=Thu, 01- Jan-2038 00:00:01 GMT;"; } catch (e){ return false; } return true; }

攻击者甚至可以为Session Cookie增加一个Expire时间,使得原本浏览器关闭就会失效的Cookie持久化地保存在本地,变成一个第三方Cookie(third-party cookie)。

如何对抗这种Session保持攻击呢?

常见的做法是在一定时间后,强制销毁Ses-sion。这个时间可以是从用户登录的时间算起,设定一个阈值,比如3天后就强制Session过期。

但强制销毁Session可能会影响到一些正常的用户,还可以选择的方法是当用户客户端发生变化时,要求用户重新登录。比如用户的IP、UserA-gent等信息发生了变化,就可以强制销毁当前的Session,并要求用户重新登录。

最后,还需要考虑的是同一用户可以同时拥有几个有效Session。若每个用户只允许拥有一个Session,则攻击者想要一直保持一个Session也是不太可能的。当用户再次登录时,攻击者所保持的Session将被“踢出”。

单点登录(SSO)

单点登录的英文全称是Single Sign On,简称SSO。它希望用户只需要登录一次,就可以访问所有的系统。从用户体验的角度看,SSO无疑让用户的使用更加的方便;从安全的角度看,SSO把风险集中在单点上,这样做是有利有弊的。

SSO的优点在于风险集中化,就只需要保护好这一个点。如果让每个系统各自实现登录功能,由于各系统的产品需求、应用环境、开发工程师的水平都存在差异,登录功能的安全标准难以统一。而SSO解决了这个问题,它把用户登录的过程集中在一个地方。在单点处设计安全方案,甚至可以考虑使用一些较“重”的方法,比如双因素认证。此外对于一些中小网站来说,维护一份用户名、密码也是没有太大必要的开销,所以如果能将这个工作委托给一个可以信任的第三方,就可以将精力集中在业务上。

SSO的缺点同样也很明显,因为风险集中了,所以单点一旦被攻破的话,后果会非常严重,影响的范围将涉及所有使用单点登录的系统。降低这种风险的办法是在一些敏感的系统里,再单独实现一些额外的认证机制。比如网上支付平台,在付款前要求用户再输入一次密码,或者通过手机短信验证用户身份等。

目前互联网上最为开放和流行的单点登录系统是OpenID。OpenID是一个开放的单点登录框架,它希望使用URI作为用户在互联网上的身份标识,每个用户(End User)将拥有一个唯一的URI。在用户登录网站(Relying Party)时,用户只需要提交他的OpenID(就是用户唯一的URI)以及OpenID的提供者(OpenID Provider),网站就会将用户重定向到OpenID的提供者进行认证,认证完成后再重定向回网站。

OpenID的认证流程可以用下图描述。OpenID的认证过程

在使用OpenID时,第一步是向网站提供OpenID。

第二步,网站重定向到OpenID的提供者进行身份认证,在本例中OpenID的提供者是 my-openid.com。

第三步,用户将在OpenID的提供者网站登录,并重定向回网站。

OpenID模式仍然存在一些问题。OpenID的提供者服务水平也有高有低,作为OpenID的提供者,一旦网站中断服务或者关闭,都将给用户带来很大的不便。因此目前大部分网站仍然是很谨慎地使用OpenID,而仅仅是将其作为一种辅助或者可选的登录模式,这也限制了OpenID的发展。

小结

本章介绍了认证相关的安全问题。认证解决的是“Who Am I?”的问题,它就像一个房间的大门一样,是非常关键的一个环节。

认证的手段是丰富多彩的。在互联网中,除了密码可以用于认证外,还有很多新的认证方式可供使用。我们也可以组合使用各种认证手段,以双因素认证或多因素认证的方式,提高系统的安全强度。

在Web应用中,用户登录之后,服务器端通常会建立一个新的Session以跟踪用户的状态。每个Session对应一个标识符SessionID,SessionID用来标识用户身份,一般是加密保存在Cookie中。有的网站也会将Session保存在Cookie中,以减轻服务器端维护Session的压力。围绕着Session可能会产生很多安全问题,这些问题都是在设计安全方案时需要考虑到的。

本章的最后介绍了单点登录,以及最大的单点登录实现:OpenID。单点登录有利有弊,但只要能够合理地运用这些技术,对网站的安全就都是有益处的。

浙ICP备11005866号-12