Broken Function Level Authorization

Broken Function Level Authorization in Node.js (Express) [CVE-2026-34784]

[Updated March 2026] Updated CVE-2026-34784

Overview

In CVE-2026-34784, Parse Server allowed file downloads via HTTP Range requests to bypass the afterFind(Parse.File) trigger and its validators on streaming storage adapters like GridFS. This is a broken function-level authorization issue: the access control logic that should run when a function is invoked can be bypassed by certain streaming paths. The vulnerability existed in versions prior to 8.6.71 and 9.7.1-alpha.1 and was fixed in those releases. Attackers could issue a Range request to a protected file and start streaming data before the authorization and in-situ validators finished executing, effectively bypassing the afterFind hooks that normally enforce requireUser or other guards. The bypass relied on how streaming paths sidestep function-level authorization in some storage adapters. In a Node.js/Express app, a similar flaw can arise if a route streams file data without performing per-resource authorization before opening a read stream. Range-based requests can compound the issue by removing the need to reach higher-level guards before delivery begins. The fix is to enforce authorization at the entry point of the streaming path, not solely in model hooks or background validators. Remediation pattern: upgrade to the patched Parse Server versions (8.6.71 and 9.7.1-alpha.1) and apply equivalent hardening in your own Express apps by validating permissions before streaming and by centralizing access checks. Use signed URLs or short-lived tokens for downloads and add tests that simulate Range requests against protected resources.

Affected Versions

Parse Server versions prior to 8.6.71 and 9.7.1-alpha.1

Code Fix Example

Node.js (Express) API Security Remediation
/* Vulnerable pattern and fix side-by-side for Node.js/Express */

const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

// Mock in-memory file metadata store (for illustration)
const files = {
  '1': { id: '1', path: path.join(__dirname, 'demo.txt'), size: 1024, ownerId: 'alice' }
};

// Helper: parse Range header
function parseRange(range, size) {
  if (!range) return null;
  const m = range.match(/bytes=(\\d+)-(\\d+)?/);
  if (!m) return null;
  const start = parseInt(m[1], 10);
  const end = m[2] ? parseInt(m[2], 10) : size - 1;
  return { start, end };
}

/* Vulnerable pattern: streaming without any authorization checks */
app.get('/files/:id', (req, res) => {
  const f = files[req.params.id];
  if (!f) return res.status(404).end();
  const range = parseRange(req.headers.range, f.size);
  const start = range ? range.start : 0;
  const end = range ? range.end : f.size - 1;
  res.status(206);
  res.set({
    'Content-Type': 'application/octet-stream',
    'Content-Range': `bytes ${start}-${end}/${f.size}`
  });
  fs.createReadStream(f.path, { start, end }).pipe(res);
});

/* Fixed version: enforce authentication and authorization before streaming */
function ensureAuthenticated(req, res, next) {
  // Simple header-based auth for illustration; replace with real auth in production
  if (!req.headers['x-user-id']) return res.status(401).send('Unauthorized');
  req.user = { id: req.headers['x-user-id'] };
  next();
}
function userHasAccess(user, file) {
  return user && file && user.id === file.ownerId;
}

app.get('/files-fixed/:id', ensureAuthenticated, (req, res) => {
  const f = files[req.params.id];
  if (!f) return res.status(404).end();
  if (!userHasAccess(req.user, f)) return res.status(403).send('Forbidden');
  const range = parseRange(req.headers.range, f.size);
  const start = range ? range.start : 0;
  const end = range ? range.end : f.size - 1;
  res.status(206);
  res.set({
    'Content-Type': 'application/octet-stream',
    'Content-Range': `bytes ${start}-${end}/${f.size}`
  });
  fs.createReadStream(f.path, { start, end }).pipe(res);
});

app.listen(3000, () => console.log('Server running on port 3000'));

CVE References

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