What is Session Fixation and How to Prevent it in Node.js

Poorshad Shaddel
Level Up Coding
Published in
7 min readNov 13, 2023

--

By Session Fixation attacker can hijack a valid user session and it is absolutely important to know this vulnerability and protection against it.

Session Fixation and How to Fix it

Before diving into this we need to know what, the session is and how session authentication works. If you are already familiar with this you can skip to the part: What is Session Fixation or How to Prevent Session Fixation

What is Session?

As we know HTTP requests are stateless, it means that when we send my Login request, and we have a valid username and password there is not a default mechanism to know that I am the same person who is sending the next request. For solving this and in other words make the requests stateful there are some suggested ways like Cookies, Hidden Form Fields, URL Parameters, HTML5 Web Storage, JWT and Session. In this article we are focusing on Session.

Session is a data stored on the server. Each client is given a unique identifier associated with this data on the server. The client must send this unique identifier on each request, so we know who is sending this request. This identifier could be sent in a cookie or URL parameter.

A simplified example to show the session and identifier (sessionId) in an expressjs application:

const app = require('express')();
const session = require('express-session');
app.use(require('cookie-parser')());
app.use(require('body-parser').json());

app.use(session({
secret: 'secret',
cookie: { maxAge: 60000 },
name: 'sessionId'
}));

app.get('/', (req, res) => {
res.send('ping');
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

When sending a request for the first time the express-session middleware is creating a new unique identifier and sets it as a cookie and stores this somewhere (in this case memory, but we can pass our custom store too). In the options of session middleware, we have used sessionId as the name of our key that stores this unique identifier. Now if we send a request, we see something like this:

Session Middleware Assinging a new Session Id
Session Middleware setting a new Session Id

Browsers now set this cookie and store it automatically for further requests. If we send a request that contains a valid session (the session exists in our session store - memory in our case) we do not get the Set-Cookie header in the response back:

No Set-Cookie When we have a valid session.

When the user signs in we can store the user information in the cookie(serialized) or we can store it in a database and associate the data with the sessionId. Let’s use a Map as our database:

const db = new Map();
app.get('/me', (req, res) => {
const user = db.get(req.sessionID);
res.json({ mySessionId: req.sessionID, me: user ? user : 'anonymous' });
});
const users = [{ name: 'bob', age: 19 }, { name: 'joe', age: 20 }];
app.post('/login', (req, res) => {
const { name } = req.body;
const user = users.find(u => u.name === name);
if (user) {
db.set(req.sessionID, user);
res.send('ok');
} else {
res.send('try again');
}
});

If we login and then use the cookie to send another request to /me we get this result:

User Data associated with Session Id

This was a simplified summary of why we have to use session and how we can do that.

Can an Attacker Create a Valid Session Id?

In this case that we were using express-session . you saw that we passed a secret to session middleware. This secret is used for signing the value of our cookie. It simply means that we are sure that it was us who generated the sessionId. So as long as you are sending signed values to your clients, it is not possible.

A sample of session:
sessionId=s%3AL6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP.x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs

First Part: s%3A simply means: s: which is a prefix to indicate that our cookie-session is signed!

Second Part: L6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP this is our sessionId, we are using this in our database for associating data.

Third Part: This is the third part: x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs This is the signing part. We have generated this text by using our secret, so we can be sure that this cookie is generated by us.

We can simply regenerate this sign and check if it is valid or not:

const crypto = require('crypto');
const secret = 'secret';
const sessionId = 'L6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP';
const hmac = crypto.createHmac('sha256', secret);
hmac.update(sessionId);
const signature = hmac.digest('base64').replace(/\=+$/, '');
console.log(signature); // x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs

This is how express-session is checking it.

What is Session Fixation?

In Session Fixation attacks, the attacker hijacks a valid user session. We said that we sign the cookie in order to be sure that no one can hijack another user's valid session. But what if the attacker has his own valid session and tries to associate it with another user? in this case he can perform actions on behalf of the victim.

The problems occur when we are not generating new sessionIds(unique identifier) on actions like Login.

How can the Attacker do this?

One of the cases is when attacker has physical access to the computer. As an attacker, I go to the university and I choose one of the shared computers, then I sign into my account on the vulnerablewebsite.com and then without doing the logout (which normally destroys the session in the server store), I leave an open login page on vulnerablewebsite.com and before that I have to copy my valid sessionId. Now the victim is using this computer and if the victim signs in, the attacker sessionId is associated with the victim's account. Sounds complicated? Not at all, let’s see that in action:

Let’s login with our first user Bob(Attacker):

Now the browser sets this cookie for this website. It means if someone else tries to send the login request, express-session is not generating a new sessionId and what it does is overriding the existing sessionId.

Let’s imagine Joe (victim) decides to use this shared computer, the cookie and the valid session of Bob is also sent:

Login with sending an existing valid session

We did not get a new session or cookie!

The magic happened and now the sessionId of Bob is associated with Joe’s user. So, if the attacker (Bob) sends a request to /me he will get data of Joe back:

Using Bob Session Id for getting Joe Data

We were able to get Joe Data by using the Session of Bob. In this example the attacker had physical access, but it is possible to do it without physical access if there are some other vulnerabilities such as XSS.

Some websites are passing sessionId as URL parameters in requests. In this case if the attacker gives a link to login page with his sessionId on URL parameters there is a possibility to exploit.

Session Fixation by URL Parameter

Read more about security challenges of this method in this stack exchange question.

How to Prevent Session Fixation?

Generate New Sessions on Login!

The main solution is really easy, by doing that you are always sure that this session override is not happening!

Let’s change our code:

app.post('/login', (req, res) => {
const { name } = req.body;
req.session.regenerate(err => {
if (err) {
res.send('error');
} else {
const user = users.find(u => u.name === name);
if (user) {
db.set(req.sessionID, user);
res.send('ok');
} else {
res.send('try again');
}
}
});
});

We can use regenerate function, in order to assign a new session each time someone wants to log in. It does not matter anymore if you pass a session cookie or not, it will generate a new session Id and sends it to the client in the Set-Cookie Header.

Each Time We Log in we get a new sessionId and Set-Cookie Header

Only Use HTTP Only Cookies

When you are using HTTP Only, it means that only server can set cookies by Set-Cookie header and the client side (Browser JavaScript) cannot change it. So even if your app has a XSS vulnerability, the attacker cannot change sessionId (cookie).

Protect against XSS

Session Fixation can be combined with XSS attacks to be more effective, so it does make sense to take XSS attacks also serious if you are concerned about session fixation.

Sensible Session Expiration Time

The sessions expiration should match your application specific requirements, if you are more concerned about security then it should be shorter and vice versa.

Correct Logout Implementation

On Logout you have to correctly destroy the existing session and its association with any data. Otherwise, these sessions could be used after logout. (It is not enough to remove the cookie from client browser!)

Was Passportjs Vulnerable to Session Fixation?

Yes, in versions before 0.6.0 the issue was there, Passport maintainers thought that session regeneration should be done on the application side, but after a while they realized the importance of the issue and they fixed it in 0.6.0 version. If you are interested in the details of this fix, you can read all the details here.

Conclusion

Session fixation could occur in case of overriding an existing sessionId with another user data. The solution is quite straight forward by generating a new session each time someone signs in, using HTTP Only cookies, proper expiration time, correct logout implementation.

References

https://owasp.org/www-community/attacks/Session_fixation#

https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_fixation

--

--

Full Stack Developer with more than 6 years of expericence. I spend partial part of the day working with Node.js and React.