如何构建安全的“记住我”的特性

开发 后端 前端
看下这个场景 – 一个用户登录了你的网站,第二天再次回来访问时...他必须再次登录。“记住我”这种功能 – 让我们关注下,我们之前都见过它 – 是指在立即使用之后把已认证状态进行持久化。

看下这个场景 – 一个用户登录了你的网站,第二天再次回来访问时...他必须再次登录。“记住我”这种功能 – 让我们关注下,我们之前都见过它 – 是指在立即使用之后把已认证状态进行持久化。这意味着他们可以关闭浏览器,关掉电脑,然后过一天或者过一周或者过一个月或者无论多久之后再回来访问时,该网站依然能识别出来他们是谁,并给他们提供和他们离开时所拥有的一模一样的功能。

我所说的就是下面这个小东西:

"Keep me logged in" from Facebook 

看起来很简单, 是吧? 也许是,但是你将会看到把它弄得一团糟的情况并不少见,而且,即使你做对了,也会有一大队人等着告诉你,实际上你仍然不够正确。让我们从一些完全错误的做法开始我们的工作。

适得其反的模式

这看起来挺明显的吗,不是吗?我是说关于“记住我”这个特性的模型还是挺基础的,不是那种很容易搞错的东西。你确定吗?显然不是。

在我们讨论怎么做才能做对之前,我们先来分享几个适得其反的模式的案例。第一个案例就是Black & Decker,就是我前段时间在Security is hard, insecurity is easy里写到的那个B&D。这就是你准备登录时的样子:

Logging in to Black & Decker 

这还是挺中规中矩的,有意思的东西要等你登录后才能看到。然我们来看下Cookie记录:

Black & Decker cookies

这是cookies的一些相关属性和值。但是高亮部分是真正值得关注的地方。假如用户没有选择"记住我 "按钮,这些cookies的信息不会存在,所以它们的功能纯粹是为了用户稍后登录。我的用户名是相当的清楚,我的密码被加过密码。注意看看这个密码,那是Base64编码!这就意味着密码被实现由Base64编码,你能拦截它在某些地方例 如 base64decode.org 这样做:

Base 64 decoding the Black & Decker cookie 

现在并非所有人知道Base64这一门加密技术 (是的,用户可以真正地这样做) 。它是相当完美合法代替数据用ASCII格式, 这个真正的故事在这里是告诉我们的密码在浏览器以文本方式。你可能会惊奇-为什么有问题呢。它是在你的浏览器,真的?黑客怎么攻击它呢?

我将要去讨论两种简单的方式。第一种方式是涉及到前面链接的不安全的信息。Black & Decker 已经暴露了ElEMH日志和这些日志相关的未处理服务器异常。我会通知它们在 50,000的北方。当ELMAH日志发生异常,那么意味着cookies(所有请求头信息)将会被记录。系统将会抛出大量的错误信息,你会接受到一个用户凭证。是的,他们应该开始恰当地保护ELMAH日志信息。但它是一个很好的例子,你怎么可以很容易地通过一个非常简单的配置撤消错误信息。

这里有另外的一个:

Logging in to Aussie Farmers Direct 

这就是 Aussie Farmers Direct 。它的登录界面看起来非常传统。当我们开始登录并打上"记住我"的标志的时候, 我们看一眼相关的cookies信息。

Aussie Farmers Direct cookies 

哇亲爱的,相同的处理方式但是没有任何Base64编码。事实上,这样是相当的不合理,因为你能够操作像下面这样。

Improperly encoded cookie on Aussie Farmers Direct

XSS 你自己的 JSON cookie? 确定不! 另外的方式是改变你的密码而不要改变你的cookie。所以当你下一次返回的时候,尝试重新登入用旧的密码。哎呀。

#p#

Aussie Farmers Direct 没有暴露出ELMAH日志信息 (因为PHP友好的处理了它) 。但是,它仍然有其它的风险例如XSS(顺便说一下,它们多次负责任的关闭和处理了风险。)另外一件事情是上面2个网站的cookies处理密码没有标记作为HttpOnly, 你能看见它们在cookie列表的第二列。那就意味着客户端脚本访问这些cookies,那就意味着如果你能到网站上得到XSS。假如你能够让用户去运行 XSS负载(现在有多种方式去做),你就能偷到包含的密码的cookies信息,访问Aussie Farmers 网站。缺少了HttpOnly属性是代表这两个网站上的马虎,但是核心的问题用cookies存储你的密码,然后使他们很容易通过其他疏漏。

从根本上来说,最重要的原因主要是这两个网站的疏忽。他们保护用户的证书被用在网站。在网站上,用户每一次使用"记住我"特征,用户发送了一个请求。现在有一个好的选择就是发送他们的用户名和密码到他们的邮箱,或者eBay,或者它们的银行通过线路,有时候用文本,有时候访问用客户端脚本。总是放在浏览器中未受到保护。密码重用是疯狂,而这些该死的用户该付出血淋淋的责任(我在这里转述!),我们—作为开发者—当我们处理凭据时,我们必须认识到保护远远超过只是我们自己的网站。

因此,我们应该建立"记住我"的功能的真正误区。让我们开始走向正确的实践道路。

一个参考的实现样本

安全领域中你能经常听到的箴言之一就是“不要使用你自己的——使用已经证实是安全的”。它被非常频繁地应用于加密和身份验证方案中,但是现在我们可以将这个扩展到“记住我”的特性,以便在我们深入细节之前,先从一个好的参考实现开始。

在一个由Visual Studio 2012新建的ASP.NET MVC 4网站中,你能得到这个开箱即用的登录界面:

Logging in to a sample ASP.NET application 

其它的框架有其它实现这个特性的标准模式,但这个参考起来很容易。当我们像上面这样填写字段登陆的时候(即不要让它记住我),验证成功则会返回如下cookie结果:

  1. Set-Cookie:.ASPXAUTH=6891A5EAF17A9C35B51C4ED3C473FBA294187C97B758880F9A56E3D335E2F02
  2. 0B86A85E1D0074BDAB2E1C9DBE590AF67895C0F989BA137E292035A3093A702DEC9D0D8089E1D007089F
  3. 75A77D1B2A79CAA800E8F62D3D807CBB86779DB52F012;
  4. path=/; HttpOnly 

这只是个简单的验证cookie, 在无状态的HTTP世界里, 一个用户发送的所有请求若不是被这一小段数据关联起来的话, 都将变成完全独立的请求. 每一次这个对我唯一的cookie被发送后, 网站就会知道来访的用户是我而且我已经通过了验证. 我们可以在Chrome的Cookie记录下面看得更仔细一点儿:

Cookies from a sample ASP.NET application without the "remember me" box checked 

顺带说一下, 第二个cookie是一个用于阻止CSRF攻击的反伪造令牌, 这和我们的验证状态没啥关系, 除此以外就没有其他cookie了.

现在, 我们登录并要求网站记住我, 下面是响应的cookie值:

  1. Set-Cookie:.ASPXAUTH=3A92DAC7EFF4EE5B2027A13DD8ABEA8254F0A16D8059FCAF60F5533F1B7D994
  2. 62DDF57320D069A493481978750526DF952D5C9EA0371C84F5CF1BFC0CCA024C2052824D4BA09670A42B
  3. 85AEC7FFCB4088FC744C6C0A22749F07AF6E65E674A4A;
  4. expires=Tue, 02-Jul-2013 00:27:05 GMT;
  5. path=/; HttpOnly 

啊哈 – 看到了吧?! 如果你在Chrome下面查看被分解开来的cookie值就会变得更加清楚:

Cookies from a sample ASP.NET application with the "remember me" box checked 

现在, 我们有了一个从当前开始有效期为48小时的cookie, 相反的是不设置过期时间的cookie将会在浏览器被关闭之后被立即清除. 让我们来近距离的看一看.

纵观授权cookie的失效

事实上,这个一个可笑的安全结构和它仅仅只是前面的例子我感觉是值得写出来,但是我们在这里。在实现当中,“记住我”功能简单地归结为当授权的cookie到期。这里做的事情是控制你想让某人登入到你网站多久,它是简单的。

在这里例子当中,ASP.NET 默认使用session cookie 或者换一句说话, a cookie 没有明确的失效时间。当用户浏览器一关闭,cookie就强制到期。 这是一种方法,另外一种就是明确一个简短时间以至于浏览器是开着的,用户自动退出的经过一段时间。当然,假如系统是运行顺畅的,你也是能够控制服务器的行为,由服务器响应增加失效时间,你也能够扩展授权cookies信息的生命周期。

记住某个人是简单的,保存授权cookie信息是激活的。多长时间让它存活呢?在这个例子中默认是2天,这个可能是短暂的对一些合法的用户来说。(尽管它可以简单的在 ASP.NET中配置) ,Facebook 可以让你的cookie信息存活持续一年。短暂的时间说明减少风险但是不方便,长时间使得用户增加窗口的潜在攻击可能。让我们看一下风险更详细。

利用长期运行的认证状态

当你不认证的时候,你的会话不能被劫持。我知道那种情况。但严重的是类似Aussie Farmers Direct的情况。cookie有效的时间是6个月。由于这个事实,网站没有标记为HttpOnly。因为网站里面有XSS缺陷并且cookie的有效时间是6个月,所以在这6个月当中,在你不经意访问网站的时候,它会把链接相关的证书发送给攻击者。假如有效时间是一个月,虽然他们在设计方面会有严重的缺陷,但是攻击者攻击窗口的机会将会减少。

另一方面,Black&Decker公司有一个相对短的一个星期的有效期,所以在他们的情况下,与外露的ELMAH日志。是的,有一系列坏的失败对他们的一部分,但除非有人已经记录在“记住我”复选框打勾,在上周引起了未处理的异常,凭据不会被公开展出。好吧,如果你是在网站上开始有一个很好的机会(至少和Farmers例子比较,在那个网站上,你会不自觉地失去密码在你的网站浏览过程中)但你可以看到,风险状况如何变化在cookie的有效期间内。

当然所有这一切的基础是需要仔细地保护认证cookie;HttpOnly和安全属性是绝对必须要做的事情。不过古典的劫持威胁仍然要重视,然而要分离这些最终与cookie相关的特性,你就需要非常非常仔细地看看这些cookie。

最终需要考虑诸多因素,比如站点的用户数据对于攻击者的价值、返回给用户的未通过认证的阻挡页面和站点的其余安全配置等,然后再做权衡。例如,脸谱有一些有关社交方面的非常有用的数据,而且用户期望在返回时得到低冲突(甚至没有冲突)的处理,另外他们在安全方面已经给予大量的投资。另外,Aussie Framer站点用户个人拥有识别用户的数据和财务信息 而且人们认证后才获得的所提供的服务(支付功能)仍然很少理解重要的安全理念。因此这两个站点有着非常不同的风险配置,而且它们应当有非常不同的认证 cookie过期策略。

增强授权途径

很多人在审视通过cookie进行授权的途径的时候都会绝望地翻白眼, 对于安全这件事其实就是始终都有一种更好的方式(去实现), 这依赖于你愿意为之付出的时间/金钱/复杂度, 而且总会有那么一个人准备告诉你: 你搞错了! 现在让我们以授权cookie作为一个起始点, 来探寻一些可能的方式来增强这个途径.

反对长期有效的授权cookie一个论据就是, 它们实际上会保证用户处于已授权状态并且处于被攻击的风险之下, 类似于CSRF或者点击劫持之类, 当然, 应用程序需要暴露其他的风险之处才能让袭击者利用长期性的cookie, 但是关于整体纵深防御的论证又再度出现了. 一个可选方式就是保持一个专用cookie来应对那些用户 -他们在通过了服务器对会话真实性的验证之后能够在回来的时候发起一轮新的已授权会话, 这样原始的会话就马上失效了, 这里的技巧就是, 当用户带着专用的"记住我"cookie的返回的时候重新设置一个新的会话, 并在这个过程中包含进一些额外的验证.

提供额外的验证的一种方式是包含用户的IP地址/用户代理/等其他显著特点的“记住我”的cookie。理由这样的:这种方式提供了一些防御劫持的cookie。这样也有个问题,就是在正常使用的情况下这些也需要。在移动世界中的情况更常见,许多终端常常共用一个IP,即使在平常的物理网络中,你也不能完全信任ISP提供的静态IP地址。至于用户代理,如Chrome和Firefox这些 浏览器更新恍如隔日,你或许会认定一个用户代理中的字符串做 属性进行存储和比较,但这将是一个有危险的做法。 使用独特的用户属性,在会话上下文间增加安全特性,cookie授权,我相信这都是有效的措施(再次回到银行),我不能说我已经看过我测试过的网站已经在使用,但使用是一个很好的理由…

一个更为务实的缓解方案是:依然把认证cookie从一个专门的“记住我” cookie中分离出来,并使用后者来对用户进行重新认证,但要施加一些限制。事实是,自动持久化某人的认证状态的过程 – 不管是什么方式 – 都会要求安全模型做出妥协。一个缓解方案可以是这样:在查看特定类型的数据或者执行特定操作之前,要求一个自动重新认证的用户再次明确的输入他们的凭证。在“记住我”特性之外,这种做法并不是闻所未闻的新做法,你应该在执行高价值的操作时见过,比如银行转账。这种情形下,我们说认证用户面临着更高的风险,因为更有可能他们并不是他们所声称的那个人(也就是说,另外一个人已经坐在了电脑前并且有效的劫持了本次会话)。

我们可以应用到这种方法的另一个强化措施是确保用来记住用户的cookie在使用了以后要重置。这也意味着在服务器上使它失效,这就需要cookie的值既具有惟一性又具有持久性,例如在数据库和cookie里保存的临时变量。这有利于确保如果cookie被攻击者获得,那么它有仅仅只有一次单独的使用机会---也就是如果用户在它们失效之前不能合理的使用cookie才出现这样的情形。这儿有一片很好的小小文章,它再次谈论了这种方法的某些缓解措施,有些使用情形可能是有帮助的,不过你将需要投入更多地努力构建cookie,还有些情形是cookie困扰着合法用户(即试图在横跨多个服务器的同一个站点上记住你自己)。

#p#

最后一个值得提及的事情是:需要考虑已认证的活动会话的同一个账户的管理原则在“记住我”的上下文里是紧密相关的。例如,来自同一个用户的多个同时已认证会话是否允许?如果用户更改了它们的密码的话,是否要断开其余的会话? 管理员是否结束已认证了的会话?这儿出现的所有问题实际上是有关如何验证和管理已认证会话的一部分,这儿的讨论仅仅关注在如何恢复哪个会话上。

什么时候不应该允许“记住我”? (以及一些中间路线选择)

有时允许一个认证用户长时间保持认证态根本没有意义。比如银行业就是典型的案例,如果你想强制尽快的重新认证,却将已认证的浏览器会话丢在一边不用,就会使风险变得很大。

但实际上在这里可以采取一些中间路线:

A "remember me" box on the American Express website 

这不完全是你看到的样子——当你选择了“记住我”,又在会话过期以后返回,你将得到这个:

The American Express website pre-populating the username on return 

我没有混淆用户名,上面显示的带有星号的用户名是存在于cookie中的,这个cookie会连同一些其他的必须被用来标识用户的数据一起,保留3个月。坦率地说,在这里面我没有觉得有很多的意义,记住你的用户名并不总会成为问题!

但也不是说就一定是非此即彼,你会有中间的选择。例如,我早些时候发表的观点,如果会话由“记住我”功能继续下去,就需要在关键过程执行之前再认证。这似乎有点两全其美。

总的来说…

在某些方面下,这个特性是一个非常好的主意。它可以非常简单的去实现大部分你需要的功能。坦率地说,当你去增加cookie的生命周期的时候,你仍然有一些困惑前面的两个例子是怎么错的。

通过这篇文章,另外一个关键的地方是理解"记住我"特性的安全机制,例如一个多层次,相互交互的系统。你可能能够摆脱在cookies里面加入证书,但是结合ELMAH情况或者缺少HTTPOnly属性和XSS缺陷是非常愚蠢的,将会是一个严重的风险。那么"纵深防御"是怎么一回事呢!

英文原文:How to build (and how not to build) a secure “remember me” feature

译文链接:http://www.oschina.net/translate/how-to-build-and-how-not-to-build

责任编辑:林师授 来源: OSCHINA编译
相关推荐

2014-02-19 15:38:42

2017-09-25 12:31:51

2023-08-10 17:14:13

2013-08-26 13:58:20

2012-08-27 09:13:02

2015-03-12 09:42:56

2021-07-12 09:00:00

网络安全Web技术

2022-07-06 10:33:06

云安全SaaS

2017-11-23 15:09:16

2014-08-19 08:47:58

2015-12-18 13:44:13

2017-03-09 19:16:56

2009-07-04 15:13:33

LinuxvsftpdFTP服务

2009-05-27 10:40:57

2019-04-01 10:15:02

2011-11-03 14:19:15

2010-01-04 15:27:05

2010-02-04 16:43:50

2023-09-04 14:52:48

2011-03-11 13:52:46

点赞
收藏

51CTO技术栈公众号