You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Split out of the static review of #286. Filed as hardening rather than an incident — see "Scope" below, this is long standing and API wide, not something #286 introduced.
Summary
Endpoints that accept a JSON object as a MongoDB filter pass $ prefixed keys straight through to the driver. A $ prefixed key is a query operator, not a property name, so $where reaches the server and is evaluated as server side JavaScript. No authentication is required to reach either endpoint.
Affected:
POST /v1/api/query — long standing, accepts the whole request body as the filter
POST /v1/id/:_id/expanded — added in /v1/id/_:id/expanded endpoint #286; sanitizeExpansionFilters() removes four reserved property names but does not reject operator keys
Impact
Availability.$where predicates are evaluated per candidate document, so request cost scales with the candidate set rather than with the response. A single anonymous request can be made to occupy a connection for a long time, and a small number of concurrent requests would saturate the cluster.
A limited read oracle. A $where predicate has access to the document it is evaluating, and the response reveals whether it matched (result count on /query, the Annotations-Merged header on /expanded). That makes field contents recoverable a character at a time. Slow and noisy, but it means "read only" is not the same as "harmless."
What this is not
Worth stating plainly so this gets weighted correctly:
No bypass of the expansion guardrails. Every supplied filter is ANDed into the query, so on /expanded the leaf version, Annotation type, and target constraints hold regardless of what is sent. There is no way to widen the result set past the entity in the request URI.
No writes, no authentication or authorization bypass, no access to anything the endpoint would not already return.
Scope
/v1/api/query has accepted operator keys for as long as it has existed, so this is not a regression and /expanded grants an attacker nothing new. It is worth fixing because the fix is cheap and because sanitizeExpansionFilters() currently does not do what its own docblock says it does — it promises to reduce the body to "the literal MongoDB filter keys the expand job will honor," and $where is not a literal filter key.
Suggested fix
1. Disable server side JavaScript on the cluster. This closes $where, $function, $accumulator, and mapReduce everywhere at once, with no code change:
Atlas: Configuration → Additional Settings → disable server side JavaScript
self hosted: security.javascriptEnabled: false
I grepped the repository for $where, $function, $accumulator, and mapReduce across all .js, .json, .yaml, and .html sources, including the legacy db-controller.js.backup. Zero matches. Nothing in RERUM uses server side JavaScript, so disabling it should be behavior neutral.
2. Reject operator keys at the boundary, as defense in depth. In sanitizeExpansionFilters() (controllers/crud.js) and in query() (controllers/crud.js):
if(key.startsWith("$")){throwObject.assign(newError(`MongoDB operator '${key}' is not an allowed filter. Supply literal property names only.`),{status: 400})}
Rejecting with a 400 rather than dropping silently means a client that sends one learns why it did not work.
Values need the same treatment, since {"creator": {"$where": "..."}} reaches the same place through a nested object. Either walk values recursively with the same check, or constrain filter values to scalars and arrays of scalars.
Decide deliberately whether /query should keep accepting operators at all. Some operators there are genuinely useful to clients ($exists, $in, $regex) in a way they are not on /expanded, so a blanket $ rejection may be too blunt for /query specifically. An allowlist of safe operators would fit that endpoint better than a denylist. /expanded has no such need and can take the blanket rule.
Notes
Reproduction details are deliberately omitted here since this repository is public and the production instance is live. Available on request through research.computing@slu.edu.
Consider enabling private vulnerability reporting on this repository (Settings → Security) so future findings of this kind have somewhere non public to go.
Split out of the static review of #286. Filed as hardening rather than an incident — see "Scope" below, this is long standing and API wide, not something #286 introduced.
Summary
Endpoints that accept a JSON object as a MongoDB filter pass
$prefixed keys straight through to the driver. A$prefixed key is a query operator, not a property name, so$wherereaches the server and is evaluated as server side JavaScript. No authentication is required to reach either endpoint.Affected:
POST /v1/api/query— long standing, accepts the whole request body as the filterPOST /v1/id/:_id/expanded— added in/v1/id/_:id/expandedendpoint #286;sanitizeExpansionFilters()removes four reserved property names but does not reject operator keysImpact
Availability.
$wherepredicates are evaluated per candidate document, so request cost scales with the candidate set rather than with the response. A single anonymous request can be made to occupy a connection for a long time, and a small number of concurrent requests would saturate the cluster.A limited read oracle. A
$wherepredicate has access to the document it is evaluating, and the response reveals whether it matched (result count on/query, theAnnotations-Mergedheader on/expanded). That makes field contents recoverable a character at a time. Slow and noisy, but it means "read only" is not the same as "harmless."What this is not
Worth stating plainly so this gets weighted correctly:
ANDed into the query, so on/expandedthe leaf version, Annotation type, and target constraints hold regardless of what is sent. There is no way to widen the result set past the entity in the request URI.Scope
/v1/api/queryhas accepted operator keys for as long as it has existed, so this is not a regression and/expandedgrants an attacker nothing new. It is worth fixing because the fix is cheap and becausesanitizeExpansionFilters()currently does not do what its own docblock says it does — it promises to reduce the body to "the literal MongoDB filter keys the expand job will honor," and$whereis not a literal filter key.Suggested fix
1. Disable server side JavaScript on the cluster. This closes
$where,$function,$accumulator, andmapReduceeverywhere at once, with no code change:security.javascriptEnabled: falseI grepped the repository for
$where,$function,$accumulator, andmapReduceacross all.js,.json,.yaml, and.htmlsources, including the legacydb-controller.js.backup. Zero matches. Nothing in RERUM uses server side JavaScript, so disabling it should be behavior neutral.2. Reject operator keys at the boundary, as defense in depth. In
sanitizeExpansionFilters()(controllers/crud.js) and inquery()(controllers/crud.js):Rejecting with a 400 rather than dropping silently means a client that sends one learns why it did not work.
Values need the same treatment, since
{"creator": {"$where": "..."}}reaches the same place through a nested object. Either walk values recursively with the same check, or constrain filter values to scalars and arrays of scalars.Decide deliberately whether
/queryshould keep accepting operators at all. Some operators there are genuinely useful to clients ($exists,$in,$regex) in a way they are not on/expanded, so a blanket$rejection may be too blunt for/queryspecifically. An allowlist of safe operators would fit that endpoint better than a denylist./expandedhas no such need and can take the blanket rule.Notes
Reproduction details are deliberately omitted here since this repository is public and the production instance is live. Available on request through research.computing@slu.edu.
Consider enabling private vulnerability reporting on this repository (Settings → Security) so future findings of this kind have somewhere non public to go.