Stackoverflow使用本地存储进行授权似乎不安全。这是否正确?如果不是,我们该如何加强它?

37
我正在开发一个类似于StackExchange的身份验证和授权模块。现在我确认他们使用了某种oAuth模型或令牌生成服务器来授权用户访问他们的各个网站。我进行了一个小实验,当我登录StackOverflow时,我从开发者控制台中删除所有的cookie,但保留包含针对StackOverflow域的“se:fkey xxxxxxxxxxxxxxxxxxxxxxxxx”的键的本地存储对象。此外还有另一个用于stackauth域的键“GlobalLogin: xxxxxxxxxxxxxxxxxxxxxxx”。如果我尝试使用“se.fkey”进行会话劫持,没有任何反应,但是可以复制和劫持我的会话的“GlobalLogin”,因此我的问题是:S/O在每个站点进行身份验证后如何处理授权?是否有一种方法在使用GlobalLogin一次后使其无效?{EDIT1}只有“globalLogin”一个键就足够了。如果您获取了该键,则只需打开一个私人浏览实例。在您处于登录页面时,创建key-value映射并刷新页面,即可登录。{EDIT2}全局登录键似乎在多个会话之间是一致的。一天过去了,我的“globalLogin”键没有刷新。可以安全地假设如果您的密钥被劫持,攻击者将永久访问您的个人资料。{EDIT3}对于所有投票并将对此问题投票为不涉及编程问题的人,请让我这样说,我们如何安全地在Web浏览器上存储SSO,并且它们容易受到损坏,我们需要做些什么来防止它发生?我的一位同事思考得很周到,给了我他的GlobalLogin密钥,我能够从另一台计算机上劫持他的会话,尽管它在同一个网络上。PS:这只是为了理论理解而做的。

2
我已经在我的系统上尝试了你所建议的方法,这看起来像是一个笑话,太简单了。这绝不是一种安全的实现方式,只需要在受损系统上花费5分钟,复制全局登录密钥(对于给定用户保持不变),然后使用该个人的全局登录密钥在任何系统上使用隐身浏览随意登录。Stack Overflow要小心,这很危险,肯定会导致欺诈分子攻击用户资料。 - Mrinal Kamboj
1
我认为没有问题,除非你有发布你的令牌的习惯... - dandavis
3
@Shouvik: Chrome通常会保存密码。窃取(或借用)设备是已知的攻击方式,但这不是Stack的问题,也不是他们系统的缺陷。所涉及的域是https,因此物理上的妥协是唯一已知的可能导致问题的方式。 - dandavis
1
@dandavis同意物理妥协将会带来问题,但是全局登录密钥在长时间内不改变,并且可以用于无限访问,这也应该引起关注。此外,如果我要利用这种授权模型用于企业框架,在那里每个人都可以访问其他人的系统,我相信这是不可接受的。这个概念很新颖,只是想找出如何使其变得完美无缺。 - Shouvik
1
为什么我在 Chrome 调试工具中看不到 stackauth.com 的第二个本地存储条目?这个改了吗? - Manish Jain
显示剩余8条评论
1个回答

19

好的,与其看漏洞,不如看可能的攻击路径。我会在这里添加一个表格作为 TL/DR。

Attacker      | Vulnerable?
Eavesdropper  | Yes
MITM          | Yes
Local Attack  | Yes
Server Attack | Yes

是的,这确实是一个问题。

远程攻击者可以观察流量,但无法修改流量

考虑一下在咖啡店中的被动攻击者。他们可以看到所有的TCP级别流量。

SO的请求和响应默认情况下不加密。您可以通过HTTPS浏览,但默认情况下只有HTTP。

因此,攻击者可以看到任何请求,并检查/窃取数据。

我们来看看GlobalLogin令牌是否会在请求中被发送...

事实上,它是会发送的。在登录页面上,通过iframe发送了一个请求到以下URL:

https://stackauth.com/auth/global/read?request=//snip//

那个 URL 返回一个脚本:

var data = {
    "ReadSession":"https://stackauth.com/auth/global/read-session",      
    "Request":"//snip//",
    "Nonce":"//snip//",
    "Referrer":"//snip//",
    "StorageName":"GlobalLogin"
};

var toMsg = window.parent;
var obj = localStorage.getItem(data.StorageName);

if(obj != null) {
    var req = new XMLHttpRequest();
    req.open(
        'POST', 
            data.ReadSession+
            'request='+encodeURIComponent(data.Request)+
            '&nonce='+encodeURIComponent(data.Nonce)+
            '&seriesAndToken='+encodeURIComponent(obj), 
        false
    );
    req.send(null);

    if(req.status == 200){
        toMsg.postMessage(req.responseText, data.Referrer);
    }else{
        toMsg.postMessage('No Session', data.Referrer);
    }
}else{
    toMsg.postMessage('No Local Storage', data.Referrer);
}

请注意,GlobalLogin是通过HTTPS发送到服务器的。因此,远程攻击者无法获取GlobalLogin令牌,即使他们能够读取流量。

因此,GlobalLogin部分是安全的,不会被窃听者窃听。

但是,请注意,由于会话cookie是通过HTTP发送的,因此仍然很容易受到嗅探的攻击。

远程攻击者可以修改流量(MITM)

如果您可以修改流量,则可以做一些有趣的事情。

初始页面通过HTTPS创建一个包含上述stackauth.com URL的iframe。如果您可以修改初始页面(也可以通过XSS实现),则可以将请求降级为HTTP。

StackAuth.com将不会受到影响。当它向stackauth.com发出请求时,您需要拦截该请求,并将其ReadSession URL也更改为HTTP。

但是,您只需要观察对ReadSession URL的调用,就可以窃取GlobalLogin令牌。

但是,流量已经是HTTP了,因此如果您不需要通过这种方式来窃取cookie,那么这没有任何意义。那为什么要费心呢?

本地攻击者

如果有人可以访问计算机以读取本地存储文件,则他们可以做比仅窃取您的登录令牌更严重的事情。

有一类攻击称为“浏览器中间人”,其中浏览器中的妥协使攻击者可以随心所欲地操作。

除了尝试保持浏览器安全之外,没有真正有效的保护方法(您无法从自己的端口执行任何操作)。

因此,如果攻击者可以获取对计算机的本地访问权限,则游戏结束了。

基于服务器的攻击者

如果攻击者可以访问StackOverflow的服务器,则无论如何游戏都结束了...

结论

只要允许HTTP(因为MITM始终可以将连接降级为HTTP),就没有什么可保护的,因为会话密钥始终可以通过窃听来窃取。

唯一保护此信息的方法是使用HSTS并强制使用HTTPS。

值得注意的是,您可以通过在stackauth.com上强制使用HSTS,使GlobalLogin受到保护,而仍然可以通过HTTP访问主站点。这将无法防止攻击的影响(会话劫持)。但是它会保护一个向量。

但是,通过HSTS全面使用HTTPS只是预防这些问题的最佳且唯一方法。其他任何措施都是给枪伤涂上创可贴。

注意:在发布此文章之前,我已经与SO进行了交谈。


据我所知,最主要的问题来自机器被攻陷,由于“GlobalLogin”从不过期,如果被盗,用户基本上就完了。其他攻击向量需要在多个层面上进行攻击。我想保护可能存在漏洞的“GlobalLogin”的最简单方法是将登录绑定到用户存储实例上。每次新登录都会生成一个新的“GlobalLogin”。这样,如果用户注销他的机器,他将有效地终止所有“被盗”的会话。 - Shouvik
你已经验证了令牌在不同机器上是否相同吗? - ircmaxell
是的,它是一样的。我已经解释了黑客是如何进行攻击的。即使您注销并重新登录,令牌也不会改变。 - Shouvik
你从SO那里得到关于这个实现的回复了吗?他们有什么意见? - Shouvik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接