Session Fixation 原理与防御

Session 与登录机制

Session 概念

指一类用来在客户端和服务器之间保持状态的方案的存储结构

Session 特点

由于Session是以文本文件形式存储在服务器端,所以不怕客户端修改内容,可以直接存储在内存中,也可以存储在redis这种内存数据库中,当然存储到其他数据库中也是可以的。

Session是有生命周期的

Session实例时轻量级的,所谓的轻量级,指的是它的创建和删除不需要消耗太多的资源

Session对象内部有一个缓存

Session 用法

用于存储特定用户会话的属性以及配置信息,比如用户的身份信息等,这些信息在web页面跳转的时候信息将不会丢失。

通常用于以下操作:

  • 存储整个会话过程中保持用户状态的信息,比如登录信息或者用户浏览时产生的其他信息
  • 存储只需要页面新加载过程中,或者一组功能页之间保持状态的对象
  • 在web服务器上保持用户的状态信息,供在任何时间从任何设备上的页面进行访问

限制:

  • 用户登陆越多,session需要的内存越大
  • 每个session对象的持续时间是用户访问时间加上不活动时间

为何需要Session

因为HTTP协议本身是无状态的,没有办法用户登陆之后能够在一段时间之内记住登录状态,那就需要另外一种工具来讲登录状态记录下来,并为这个记录生成一个id返回给客户端,客户端保存下来之后,接下来的每个请求都会带上这个id自动去认证这个登录态。

举个喝咖啡的例子:

1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。

2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。

3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

具体机制

  1. 当程序需要为某个客户端的请求创建一个session(一般是登陆信息验证成功之后为这些信息创建一个session)的时候,服务器首先检查这个客户端的请求里是否已包含了一个 session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session idsession id的值应该是一个 既不会重复,又不容易被找到规律以仿造的字符串 ,这个session id将被在本次响应中返回给客户端保存(一般通过返回头中的set-cookie头将这个sessionid设置到cookie中,下次的请求就会通过cookie自动将这个sessionid发送到后台)。

  2. 由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写

    两种形式:

    1
    2
    3
    4
    5
    6
    7
    // 作为url附加路径
    'http://..../xxx;jsessionid=abcdefjijeoijoifjioe'


    // 作为查询字符串
    'http://..../xxx?jsessionid=abcdefjijeoijoifjioe'
    复制代码
  3. 较老的技术,表单隐藏字段,此方法在防止csrf中有用

Session Fixation 漏洞

定义

会话固定攻击(session fixation attack)是利用应用系统在服务器的会话ID固定不变机制,借助他人用相同的会话ID获取认证和授权,然后利用该会话ID劫持他人的会话以成功冒充他人,造成会话固定攻击

攻击流程

1、攻击者Attacker能正常访问该应用网站;

2、应用网站服务器返回一个会话ID(attackSessionID)给他;

3、攻击者Attacker用该会话ID构造一个该网站链接(http://website.kom/login.php?sessionid=attackerSessionID)发给受害者Victim;

4、受害者Victim点击该链接,携带攻击者的会话ID和用户名密码正常登录了该网站,会话成功建立;这个时候如果再用attackerSessionID去访问服务器,服务器就会认为攻击者是受害者。

5、攻击者Attacker用该会话ID成功冒充并劫持了受害者Victim的会话。

防御方式

整个攻击过程的根本原因就是直接利用了链接发送过来的sessionID生成session,并直接将这个sessionID返回。

解决方式也很简单,类似登陆这种有敏感信息的接口需要重新生成一个新的sessionID

Express-session 防御 Session Fixation 举例

express-session是node服务器框架express的一个插件,具体如何使用可以参考一下express-session文档,如果想要了解原理,也可以去看一下源码,其实原理都很简单,只不过是被封装了起来,就是请求头部的cookie和set-cookie的生成,session的CRUD的封装。

值得注意的是它的store选项,也就是存储位置,如果不传值会默认存储到内存中,我们可以自己实现一个插件,只要这个插件实现了上面文档中提到的get,set,touch等接口就可以,置于你的这几个接口是把数据存储到redis,MongoDB还是其他什么地方都无所谓。

如果只是使用express-session来进行session的管理,是没有办法防御session fixation的,而且也没有必要对每一个请求都重新生成sessionID,我们只需要对某些敏感操作,如登陆进行重新生成sessionID的操作就好。如:

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
const fixation = (options) => {
options = options || {};
return function(req, res, next) {
req.resetSessionID = function() {
return new Promise(function(resolve, reject) {
let session = req.session;
req.session.regenerate(function(err) {
if (err) {
reject(err);
} else {
for (let i in session) {
if (!req.session[i]) {
req.session[i] = session[i];
}
}
resolve(req.session);
}
});
});
};
if (options.everyRequest && req.headers['X-Requested-With'] !== 'XMLHttpRequest' && !req.xhr) {
req.resetSessionID().then(function() {
next();
}).catch(function(err) {
next(err);
});
} else {
next();
}
};
};

上述代码通过express-session自带regenerate方法重新生成一个id,并将原有的session内容设置给新生成的session。

我们只需要在需要重置id的请求中调用该方法即可。

参考文章:

https://juejin.im/post/5af828e96fb9a07ab83e1f10