CVE-2023-29019 (TypeScript, published 2023-04-21T23:15Z)
Description: value: @fastify/passport is a port of passport authentication library for the Fastify ecosystem. Applications using `@fastify/passport` in affected versions for user authentication, in combination with `@fastify/session` as the underlying session management mechanism, are vulnerable to session fixation attacks from network and same-site attackers. fastify applications rely on the `@fastify/passport` library for user authentication. The login and user validation are performed by the `authenticate` function. When executing this function, the `sessionId` is preserved between the pre-login and the authenticated session. Network and same-site attackers can hijack the victim's session by tossing a valid `sessionId` cookie in the victim's browser and waiting for the victim to log in on the website. As a solution, newer versions of `@fastify/passport` regenerate `sessionId` upon login, preventing the attacker-controlled pre-session cookie from being upgraded to an authenticated session. Users are advised to upgrade. There are no known workarounds for this vulnerability.

Severity: nan (CVSS v3 base score: 8.1)
Weakness: CWE-384 \u2014 Session Fixation
Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
Repository: https://github.com/fastify/fastify-passport
Files changed:
src/session-managers/SecureSessionManager.ts
test/session-isolation.test.ts
Commit message: Merge pull request from GHSA-4m3m-ppvx-xgw9

Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>

Vulnerable code:
const { server, fastifyPassport } = getConfiguredTestServer()

server.get(
  '/protected',
  { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
  async () => 'hello!'
)
server.get('/my-id', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) =>
  String((request.user as any).id)
)
server.post(
  '/login',
  { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
  async () => 'success'
)

server.post('/force-login', async (request, reply) => {
  await request.logIn(generateTestUser())
  void reply.send('logged in')
})

server.post(
  '/logout',
  { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
  async (request, reply) => {
    await request.logout()
    void reply.send('logged out')
  }
)

Fixed code:
    // Handle sessions using @fastify/session
    if (request.session.regenerate) {
      // regenerate session to guard against session fixation
      await request.session.regenerate()
    }
    if (request.session.regenerate) {
      await request.session.regenerate()
    }
function createServer() {
  const { server, fastifyPassport } = getConfiguredTestServer()

  server.get(
    '/protected',
    { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
    async () => 'hello!'
  )
  server.get('/my-id', { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) }, async (request) =>
    String((request.user as any).id)
  )
  server.post(
    '/login',
    { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
    async () => 'success'
  )

  server.post('/force-login', async (request, reply) => {
    await request.logIn(generateTestUser())
    void reply.send('logged in')
  })

  server.post(
    '/logout',
    { preValidation: fastifyPassport.authenticate('test', { authInfo: false }) },
    async (request, reply) => {
      await request.logout()
      void reply.send('logged out')
    }
  )
  return server
}
  process.env.SESSION_PLUGIN = sessionPluginName
  const server = createServer()
    const sessionOnlyTest = sessionPluginName === '@fastify/session' ? test : test.skip

      sessionOnlyTest('should regenerate session on login', async () => {
        expect(userA.cookies['sessionId']).toBeUndefined()
        await userA.inject({ method: 'GET', url: '/protected' })
        expect(userA.cookies['sessionId']).not.toBeUndefined()
        const prevSessionId = userA.cookies.sessionId
        await userA.inject({
          method: 'POST',
          url: '/login',
          payload: { login: 'test', password: 'test' },
        })
        expect(userA.cookies.sessionId).not.toBe(prevSessionId)
      })
  delete process.env.SESSION_PLUGIN