Signed In as Somebody Else — JavaScript Bug Hunt

Modelled on GitHub, March 2021. GitHub disclosed that a rare race condition in its request handling could, under specific conditions, send one user's…

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Hard
  • Concepts: Concurrency, Auth, State
  • Modelled on: GitHub · 2021
  • Visible tests: one request at a time works; interleaved requests keep their own sessions
  • Reward: 50 XP for a complete fix

Briefing

Modelled on GitHub, March 2021. GitHub disclosed that a rare race condition in its request handling could, under specific conditions, send one user's session cookie back in the response to another user's request — leaving that browser signed in as someone else. GitHub fixed the bug and invalidated every signed-in session on github.com, signing everyone out.

This reconstruction's worker splits a request into begin (authenticate) and finish (render the response) so other requests can run in between, and keeps the authenticated session in a variable shared by the whole worker. The tests drive the interleaving explicitly.

Fix the worker so each response carries its own request's session.

Bug report

BUG-GH-2103 · Priority: Critical (session leak) · Reported by: security

createWorker(sessions) returns { begin(req), finish(id), pending() }:

  • begin({ id, cookie }) authenticates the request with sessions.lookup(cookie)
  • finish(id) returns { id, user, setCookie } for THAT request: user is the session's name and setCookie its token, or both null for a request with no valid session — regardless of what began or finished in between
  • finish(id) for an id that was never begun, or already finished, throws
  • pending() is the number of begun but unfinished requests; a finished request leaves nothing behind

Observed: with two requests in flight, the first to finish was answered with the second one's session cookie.

Logs

[web] begin req=a cookie=tok-alice
[web] begin req=b cookie=tok-bob
[web] finish req=a user=bob set-cookie=tok-bob

The code as shipped

src/web/worker.js (editable)

exports.createWorker = function (sessions) {
  var current = null;
  var inFlight = 0;
  return {
    begin: function (req) {
      current = sessions.lookup(req.cookie);
      inFlight++;
    },
    finish: function (id) {
      inFlight--;
      return {
        id: id,
        user: current ? current.name : null,
        setCookie: current ? current.token : null
      };
    },
    pending: function () {
      return inFlight;
    }
  };
};

Read-only context: src/web/sessions.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.