Broken Object Level Authorization

Broken Object Level Authorization: Node.js (Express) [CVE-2026-33421]

[Updated Mar 2026] Updated CVE-2026-33421

Overview

CVE-2026-33421 describes a Broken Object Level Authorization issue where Parse Server's LiveQuery WebSocket interface did not enforce Class-Level Permissions (CLP) pointer permissions for readUserFields or pointerFields. Prior to versions 8.6.53 and 9.6.0-alpha.42, any authenticated client could subscribe to LiveQuery events and receive real-time updates for all objects in a class protected by pointer permissions, bypassing the intended read access controlled via the REST API. This could expose sensitive data that should be restricted, especially for objects with private or owner-restricted pointers. The vulnerability is categorized under CWE-863: Incorrect Authorization, and it manifests in real-world Node.js (Express) deployments that rely on Parse Server LiveQuery for real-time data streams without robust server-side access checks. In practice, an attacker who authenticates to the system and subscribes to a LiveQuery channel for a class could receive all subsequent updates for objects in that class, regardless of whether their pointers reference the subscribing user. Because LiveQuery updates were broadcast to all subscribers without validating per-subscriber permissions, sensitive fields behind pointer relationships could leak to unauthorized clients. The issue specifically affected Parse Server builds before the patched releases: 8.6.53 and 9.6.0-alpha.42, and it is important for Node.js/Express apps integrating LiveQuery to apply the fix or implement equivalent server-side permission checks in their real-time layer. Mitigation requires not only upgrading to the patched Parse Server versions but also aligning your real-time layer with strict access controls. In Node.js/Express ecosystems, you should ensure that any WebSocket or long-running subscription channel enforces CLP/ACL checks on every published event, and that you do not rely solely on REST API permissions for real-time data. Implement server-side filtering by the authenticated user’s permissions before broadcasting events, and validate permissions consistently across REST and real-time paths. Consider disabling or tightly restricting LiveQuery for sensitive classes until you can guarantee full CLP enforcement.

Affected Versions

Parse Server < 8.6.53 and < 9.6.0-alpha.42

Code Fix Example

Node.js (Express) API Security Remediation
/* Demonstration: Vulnerable vs Fixed LiveQuery-like pattern in Node.js (Express) with WebSocket (ws) */

// Prereqs: npm install express ws

const express = require('express');
const http = require('http');
const WebSocket = require('ws');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// In-memory data model (simplified)
const objects = [
  { id: 'o1', className: 'Secret', ownerId: 'u1', pointerFields: ['u1'], data: 'top-secret' },
  { id: 'o2', className: 'Public', ownerId: 'u2', pointerFields: [], data: 'public-info' }
];

// Subscriptions (WebSocket clients)
const subs = [];

// Toggle this to demonstrate vulnerable vs fixed behavior
const VULNERABLE = true;

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    try {
      const msg = JSON.parse(message);
      if (msg.action === 'subscribe') {
        // Client indicates their userId and class they want updates for
        ws.userId = msg.userId;
        ws.className = msg.className;
        subs.push(ws);
        ws.send(JSON.stringify({ type: 'subscribed', className: ws.className }));
      }
    } catch (e) {
      ws.send(JSON.stringify({ type: 'error', error: 'invalid-message' }));
    }
  });
});

// Simulate updates to objects over time
setInterval(() => {
  const updatedObj = { id: 'o1', className: 'Secret', ownerId: 'u1', pointerFields: ['u1'], data: 'updated' };

  if (VULNERABLE) {
    // Vulnerable path: broadcast to all subscribers regardless of per-user CLP/permissions
    subs.forEach((s) => {
      if (s.readyState === WebSocket.OPEN) {
        s.send(JSON.stringify({ type: 'update', object: updatedObj }));
      }
    });
  } else {
    // Fixed path: enforce per-subscriber permission checks before broadcasting
    subs.forEach((s) => {
      if (s.readyState !== WebSocket.OPEN) return;
      if (canRead(s.userId, updatedObj)) {
        s.send(JSON.stringify({ type: 'update', object: updatedObj }));
      }
    });
  }
}, 3000);

// Minimal permission check (server-side CLP/ACL enforcement)
function canRead(userId, obj) {
  // Simplified: allow if user is the owner; in real apps use CLP/ACL rules
  if (!userId) return false;
  return obj.ownerId === userId;
}

server.listen(3000, () => {
  console.log('LiveQuery-like server listening on port 3000');
});

/*
Vulnerable pattern summary:
- ws broadcasts object updates to all subscribers regardless of pointer permissions.
- A client subscribed to a class with restricted pointers can receive updates for all objects, enabling BOLOA-like leakage.

Fixed pattern summary:
- Before broadcasting, for each subscriber, verify canRead(sub.userId, object) and only send if permitted.
- Replace the placeholder canRead with your CLP/ACL implementation tying to your identity/auth model.
*/

CVE References

Choose which optional cookies to allow. You can change this any time.