-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
This isn't a global prototype pollution attack, but does mean it's possible to get inconsistent behaviour:
await client.hset('test_key', ['__proto__', 'hello']);
console.log('hget:', await client.hget('test_key', '__proto__'))); // "hello"
console.log('hgetall:', await client.hgetall('test_key'))); // does not include __proto__: helloIt's because the reply transformer which is applied does not check for special field names: https://github.com/luin/ioredis/blob/master/lib/command.ts#L404
Since this isn't doing a recursive set, it can't cause global prototype pollution, but it can cause unexpected inconsistent behaviour as shown above. Obviously this is mainly a concern if allowing user-provided field names (e.g. a database explorer UI). Since the database itself can handle this name, I think the client library should be able to handle it too.
The easiest fix would be something like:
const obj = {};
for (let i = 0; i < result.length; i += 2) {
const key = result[i];
const value = result[i + 1];
if (obj[key]) { // can only be truthy if the property is special somehow, like '__proto__' or 'constructor'
Object.defineProperty(obj, key, {
value,
configurable: true,
enumerable: true,
writable: true,
});
} else {
obj[key] = value;
}
}
return obj;But a better fix would be to change the API to return a Map instead.
None of the other commands seem to have the same issue; hgetall is the only command which uses setReplyTransformer, and the ArgumentTransformers are fine (e.g. hset('foo', JSON.parse('{"__proto__":"yay"}')) works)
Relatedly, it would be nice to be able to call hgetall without the reply transformer (returning a raw key/value alternating list) for situations where the intent is to iterate through all fields anyway. It's interesting that the transformer has a condition checking for arrays; is it possible for the server to send a different data type? The main documentation says it will always be a list.