Thanks to visit codestin.com
Credit goes to github.com

Skip to content

hgetall mishandles malicious keys (__proto__) #1267

@davidje13

Description

@davidje13

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__: hello

It'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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions