Vulnerability write-up - "Dangerous assumptions"

We discussed this vulnerability during Episode 191 on 27 February 2023

There are a few issues in this post, the first is SQL injection with nothing very special going on. The later issues though are more of a bypass of application logic which I think is fairly cool.

First, the simple SQL injection, the Feathers web framework assumes Sequelize would only accept valid column names in an argument, whereas Sequelize actually treats that argument as trusted and does no escaping. Allowing for a trivial SQL injection.

The Feathers framework is largely intended for creating APIs, you can define a service and corresponding models. And it’ll create an API you can use to query/update/delete the data. It depends heavily on teh Sequelize ORM to actually craft the database queries, for the most part passing in the URL request object directly to Sequelize to turn into a query.

Which is where things start to go wrong, Feathers will pass in the request query object to Sequelize’s getWhereConditions function. As the name implies it generates the WHERE clause of a query. Sequelize as an ORM will fallback in some cases to simply generating a 1=1 as the where clause. Makes sense in teh context of an ORM that an empty clause is probably just a query for everything.

In the context of a Feathers application though, this isn’t a safe assumption. The application may have earlier logic to enforce certain conditions on the query like only querying for data owned by the logged in user. These would typically be enforced with before-hook functions that will modify the request object to add in the constraints.

If an attacker could somehow get the getWhereConditions function to fall into one of the 1=1 conditions it would be possible to bypass the application layer logic. Which is exactly what the authors were able to find.

Under normal circumstances Feathers would pass in a plain object to Sequelize, simply containing key-value pairs, and in this case it would act as you’d expect. The authors found that if one could pass an [] (empty array) instead of an object to the function it would fall back to using 1=1 even if a before-hook did something like query.userId = <some value>. The assignment would be allowed by JavaScript, but accessing the variable it would still look like an empty array. (Cool trick!).

Of course, they still actually need to be able to provide an empty array as the query object, using the standard web interface this couldn’t done, but Feathers also exposes a interface. This interface allowed sending an empty array as the request object. They could also abuse Socket.IO’s attachment functionality to provide other values that would fall through to the 1=1 case.