CVE-2024-35220 (published 2024-05-21T21:15Z)

@fastify/session is a session plugin for fastify. Requires the @fastify/cookie plugin. When restoring the cookie from the session store, the `expires` field is overriden if the `maxAge` field was set.\nThis means a cookie is never correctly detected as expired and thus expired sessions are not destroyed. This vulnerability has been patched 10.8.0.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/fastify/session
commit_details: repo_url: https://github.com/fastify/session
author: Paolo Insogna
author_date: 2024-05-21 18:24:15+02:00
msg: Merge pull request from GHSA-pj27-2xvp-4qxg

* fix: Use original expiration date when evaluating restored session.

* fix: Also serialize new field.
repository: repo_url: https://github.com/fastify/session
repo_name: fastify/session
description: Session plugin for fastify
repo_language: JavaScript
owner: fastify
file_changes: filename: cookie.js
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,10 @@ module.exports = class Cookie {
     this.partitioned = cookie.partitioned
     this._expires = null
 
+    if(cookie.expires) {
+      this.originalExpires = new Date(cookie.expires)
+    }
+        
     if (originalMaxAge) {
       this.maxAge = originalMaxAge
     } else if (cookie.expires) {
@@ -56,6 +60,7 @@ module.exports = class Cookie {
     return {
       expires: this._expires,
       originalMaxAge: this.originalMaxAge,
+      originalExpires: this.originalExpires,
       sameSite: this.sameSite,
       secure: this.secure,
       path: this.path,

programming_language: CoffeeScript
filename: fastifySession.js
change_type: ModificationType.MODIFY
diff: @@ -92,7 +92,9 @@ function fastifySession (fastify, options, next) {
         decryptedSessionId
       )
 
-      if (restoredSession.cookie.expires && restoredSession.cookie.expires.getTime() <= Date.now()) {
+      const expiration = restoredSession.cookie.originalExpires || restoredSession.cookie.expires
+
+      if (expiration && expiration.getTime() <= Date.now()) {
         restoredSession.destroy(err => {
           if (err) {
             done(err)

programming_language: CoffeeScript
filename: expiration.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,63 @@
+"use strict";
+
+const test = require("tap").test;
+const { buildFastify, DEFAULT_SECRET } = require("./util");
+const { setTimeout } = require("node:timers/promises");
+
+test("sessions should be deleted if expired", async (t) => {
+  t.plan(5);
+
+  const sessions = {};
+  const options = {
+    secret: DEFAULT_SECRET,
+    store: {
+      get(id, cb) {
+        t.pass("session was restored");
+        cb(null, sessions[id]);
+      },
+      set(id, session, cb) {
+        sessions[id] = session;
+        cb();
+      },
+      destroy(id, cb) {
+        t.pass("expired session is destroyed");
+        cb();
+      },
+    },
+    cookie: { maxAge: 1000, secure: false },
+  };
+
+  const fastify = await buildFastify((request, reply) => {
+    reply.send(200);
+  }, options);
+  t.teardown(() => {
+    fastify.close();
+  });
+
+  let response;
+  response = await fastify.inject({
+    url: "/",
+  });
+
+  const initialSession = response.headers["set-cookie"]
+    .split(" ")[0]
+    .replace(";", "");
+  t.ok(initialSession.startsWith("sessionId="));
+
+  // Wait for the cookie to expire
+  await setTimeout(2000);
+
+  response = await fastify.inject({
+    url: "/",
+    headers: {
+      Cookie: initialSession,
+    },
+  });
+
+  const endingSession = response.headers["set-cookie"]
+    .split(" ")[0]
+    .replace(";", "");
+  t.ok(endingSession.startsWith("sessionId="));
+
+  t.not(initialSession, endingSession);
+});

programming_language: TypeScript