apos.util.set() traverses dot-notation paths without sanitizing __proto__, allowing an authenticated editor to write arbitrary values to Object.prototype via the $pullAll patch operator.
A confirmed gadget in publicApiCheck() causes this to bypass authorization on all piece-type REST API endpoints for every subsequent unauthenticated request, for the lifetime of the Node.js process.
apos.util.set() (modules/@apostrophecms/util/index.js ~line 800)The function splits a dot-notation path and traverses properties without rejecting __proto__, constructor, or prototype:
set(o, path, v) {
path = path.split('.');
for (i = 0; i < path.length - 1; i++) {
o = o[path[i]]; // when path[i] === '__proto__', o becomes Object.prototype
}
o[path[i]] = v; // mutates Object.prototype
}
implementPatchOperators() (modules/@apostrophecms/schema/index.js ~line 1737)User-controlled keys from the $pullAll operator are passed directly to apos.util.set():
_.each(patch.$pullAll, function(val, key) {
cloneOriginalBase(key); // uses _.has (hasOwnProperty)
self.apos.util.set(patch, key, ...); // key is fully attacker-controlled
});
cloneOriginalBase() does not sanitize __proto__ because _.has() performs an own-property check. Since __proto__ is inherited rather than an own property, the clone step is skipped and execution falls through to apos.util.set().
The same unsanitized call also appears for direct dot-notation keys in the PATCH body (~line 1811), providing a second independent entry point.
publicApiCheck() (modules/@apostrophecms/piece-type/index.js ~line 1148)publicApiCheck(req) {
if (!self.options.publicApiProjection) {
if (!self.canAccessApi(req)) {...
4.31.0Exploitability
AV:NAC:LPR:LUI:NScope
S:CImpact
C:HI:LA:L9.1/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:LInput Validation