The Preview That Minted Someone Else's Token — JavaScript Bug Hunt

Modelled on Facebook's "View As" breach (disclosed September 2018): "View As" shows you your own profile as another person sees it.

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Easy
  • Concepts: Security, Auth
  • Modelled on: Facebook · View As 2018
  • Visible tests: your own page gets your own token; View As renders as the subject but mints for the viewer
  • Reward: 50 XP for a complete fix

Briefing

Modelled on Facebook's "View As" breach (disclosed September 2018): "View As" shows you your own profile as another person sees it. A combination of bugs meant that, in that mode, the video uploader generated an access token for the person being viewed as rather than for the viewer. Attackers chained this to collect tokens; Facebook first estimated almost 50 million affected accounts and later said about 29 million had tokens taken.

This project is a reconstruction. viewAs.js builds a render context in which user is the person the page is rendered as — and the composer mints its uploader token for ctx.user.

Fix the composer so a token is only ever minted for the authenticated viewer.

Bug report

BUG-VIEWAS · Priority: Critical · Reported by: security

renderPage(session, subjectId, tokens) returns { renderedAs, uploaderToken }:

  • renderedAs is subjectId when it is given and differs from session.userId (View As mode), otherwise session.userId
  • the uploader token is minted with tokens.mint(...) for session.userId — ALWAYS the authenticated viewer, never the subject — exactly once per render

buildContext(session, subjectId) returns { viewer, user, viewAs } in that key order.

Observed: in View As mode the page carries a working token for the person being previewed.

Logs

[viewas] viewer=u_1001 subject=u_2002 uploader token owner=u_2002

The code as shipped

src/profile/viewAs.js (editable)

exports.buildContext = function (session, subjectId) {
  var viewAs = !!subjectId && subjectId !== session.userId;
  return {
    viewer: session.userId,
    user: viewAs ? subjectId : session.userId,
    viewAs: viewAs
  };
};

exports.renderComposer = function (ctx, tokens) {
  var token = tokens.mint(ctx.user);
  return { renderedAs: ctx.user, uploaderToken: token.token };
};

exports.renderPage = function (session, subjectId, tokens) {
  return exports.renderComposer(exports.buildContext(session, subjectId), tokens);
};

Read-only context: src/profile/tokens.js.

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