播放有关cookie和会话的框架安全问题

时间:2013-05-12 14:05:37

标签: security playframework-2.0

对于我的应用程序,我正在实现与zentask中显示的相同的安全性。

public class Secured extends Authenticator {

@Override
public String getUsername(Context ctx) {
    return ctx.session().get("email");

}

@Override
public Result onUnauthorized(Context ctx) {
    ctx.flash().put("error", "please login to proceed");
    return redirect(routes.Application.index());
}

}

当用户通过身份验证时,用户session().put("email", email);

我有两个问题。第一:当用户在不使用注销的情况下离开应用程序时,如何使会话无效?第二个更严重的是我使用firefox插件cookies manager+检查了cookie,我可以复制一个cookie然后粘贴它,这样我就可以访问方法而无需首先登录,基本上我可以窃取会话

4 个答案:

答案 0 :(得分:38)

Play Framework使用无状态会话。服务器端没有存储状态,而是所有状态都存储在会话cookie中。为了验证会话,Play使用密钥对会话进行签名,并在有会话cookie的请求到达时验证签名。如果用户要篡改会话数据,例如,如果他们将会话中的电子邮件地址更改为其他人的电子邮件地址,则签名将不匹配,因此Play将拒绝会话cookie。

是的,您可以复制cookie并在以后使用它。但是你不能改变cookie。这意味着你可以“窃取”的唯一cookie就是你自己的,但偷你自己并不是真正的偷窃。所以不,你不能窃取会话,除非通过利用其他漏洞,如XSS,但会话令牌也容易受到攻击。

默认情况下,Play配置为创建“会话”Cookie,即关闭浏览器时过期的Cookie。因此,如果用户退出浏览器,浏览器将删除所有会话cookie,用户将不再登录。会话令牌也是如此。

有一个需要注意的事项,那就是会话令牌也会在服务器上到期,因为服务器保持状态。无状态签名会话(例如Play中使用的会话)不会。但是,您可以自己实现过期机制,方法是在创建会话时在会话中存储时间戳,并验证该时间戳是否早于getUsername()方法中已配置的有效期。由于时间戳存储在已签名的会话中,因此在不更改签名的情况下不能篡改时间戳,因此这种简单的机制非常安全。更高级的解决方案可能是实现每次请求进入时更新该时间戳的过滤器,以便过期可以基于上次访问,而不是用户登录时。

答案 1 :(得分:7)

您的假设绝对正确,您不能在Zentask示例之后使服务器上的会话无效。虽然会话cookie使用配置文件中的私钥进行签名,但相同的值会生成相同的签名cookie。正如您已经想到的那样,如果有人从用户那里窃取cookie,用户和您(服务器)都不能阻止小偷“登录”用户的帐户。

现在基本上有两种选择:

  1. 将您已有的有关用户的易失信息存储在Cookie中,只有您和用户知道并不时更改。一个例子是密码哈希的一部分。用户更改密码后,信息将不再有效,并且所有旧会话cookie均无效。这种方法的缺点:如果用户没有更改存储的信息,cookie将在很长一段时间内有效,甚至可能永远有效。
  2. 创建服务器端会话管理。为此,您必须拥有数据库,键值缓存或类似的东西。在那里,您存储会话的随机生成(加密安全)密钥,用户的名称/ ID以及会话将自动失效的日期。您还可以存储IP地址以提高cookie窃取的安全性。然后必须将会话密钥写入cookie。当用户单击注销按钮时,您将使当前会话无效(或者该用户的所有会话)。

答案 2 :(得分:4)

简单地将用户ID放在cookie中根本不是安全性。正如你所指出的,任何人都可以发明一个cookie值。

会话:相反,您需要在Cookie中放置任意(例如随机)值,然后在服务器上查找映射表中用户的身份。该任意值必须经常更改,因此您通常会有一个持续时间为30分钟的登录会话。每次登录都会提供一个新的任意值,该值称为会话ID。

无效:通过在没有任何请求(例如30分钟)的一段时间后从查找表(在服务器端)中删除该条目来使会话无效。任何具有不在表中的会话ID的请求都被视为未经身份验证的请求,并且您再次提示登录。用户忘记注销无关紧要。

黑客攻击:因为该值是任意的,所以黑客无法事先知道将来的会话ID是什么。你仍然容易受到会话窃取的影响,但是要困难得多:黑客只有在使用它时才能找到会话ID,然后才能在一段时间内使用它。您可以采取一些措施来防止这种情况发生,例如仅允许特定会话的请求来自特定的IP地址。您也可以快速循环会话ID,甚至是每个请求,但这有不利之处。通常,为每个登录提供唯一的会话ID,特别是在通过HTTPS完成时,这对于大多数身份验证需求来说已经足够了。

持久性:如果在任何给定的会话期间(例如30分钟)并发用户数很少,那么您不一定需要将其放在数据库中。在内存中维护这个开销很低,但缺点是如果你循环服务器,所有用户都需要再次登录。如果您确实将会话ID放在数据库中,则需要确保在服务器启动时它可以清除所有旧会话。

用户信息:将用户的电子邮件地址放在Cookie中仍然有价值,但仅用作登录的“默认”用户ID。这应仅被视为对用户的方便,而不是对用户进行身份验证的指示。

答案 3 :(得分:4)

  • 我建议有一个模块可以为您生成会话ID。 在这个模块中,您可以使用createSessionId()等方法。 生成此方法中保存的会话ID的逻辑。

  • 我会创建会话ID作为(userId + providerId(Facebook / Google-在OAuth / UsernamePassword /任何提供商的情况下)+当前时间戳+ UUID)的组合,并在创建此会话ID之后,我将加密它有一些算法。这将为我提供会话ID

  • 这样做的好处是:

    • 虽然生成会话ID需要时间,但没有人会理解它。
    • 另一个优点是,您可以更改加密逻辑/策略 在createSessionId()方法中随时创建会话ID。

  • Playframework中会话的另一个问题是会话没有到期:
    • 要处理这个问题,一旦用户登录,我们就可以在会话中存储时间戳,即只有cookie(通过加密可能是?)
    • 现在为会话中的每个请求检查时间戳。如果时间戳大于30分钟,则会话无效。如果时间戳不大于30分钟,则将会话中的时间戳更新为当前时间戳