baseUnset: __proto__ guard checks ownership on the root object, not the current node
Hi!
Whilst trying to backport the patches for some security issues in Debian, we discovered an interesting problem.
The prototype-pollution guard added to baseUnset in 4.18.0 (commit fe8d32e) can be bypassed for __proto__ when the root object owns a __proto__ key.
The guard walks the path but never advances object:
while (++index < length) {
var key = toKey(path[index]);
if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) {
return false;
}
...
}
object is the original root the whole time, so hasOwnProperty.call(object, '__proto__') only ever inspects the root — not the node at the current position in the path. If the root owns a __proto__ key, the __proto__ check is effectively disabled for every segment, and a deeper __proto__ reaches a nested object's real prototype.
An own __proto__ key is easy to get from untrusted input: JSON.parse('{"__proto__":1}') creates a genuine own data property (it doesn't trigger the setter). Parsing attacker JSON and then running _.unset / _.omit on it with a path is a normal pattern.
Source: lodash/lodash