4 ms·
> reasonably be expected to create a new native DB role for every new end user and manage their DB access credentials securely, such that the overall risk of a
by everforward 2y ago
> reasonably be expected to create a new native DB role for every new end user and manage their DB access credentials securely, such that the overall risk of a SQL injection attack is lessened compared to the single-role-per-app model.
They don't have to be native DB roles. Row security policies extend far beyond an "owner".
A naive version would use SQL to set a local variable to the user's ID, with row level security policies on each row that check that local variable. This is still very vulnerable to SQL injection, though, because if attackers can execute arbitrary SQL then they can also set that variable.
A less naive version would involve setting a local variable to a JWT or other client-side secret, and having RLS validate access against a stored hash of that JWT or other secret. The app's DB account has no access to anything other than SELECTs on the JWT hash -> user ID mapping table, with RLS on all the other tables. JWT's would need to be generated by another service using it's own DB account that only has access to check passwords and write into the JWT table so that they can't use the compromised connection to just generate a new JWT they can hash themselves.
That login service can be written to be virtually immune to SQL attacks because it doesn't have to handle generalized queries. Ban all the SQL control characters in usernames/passwords and 400 any request that contains them. Hash and base64 both the username and password so they get converted to something where SQL injections are impossible because of the character set used. There's a bunch of options; this doesn't have to handle general-purpose SQL queries, so it can afford to do stuff that would break normal queries like b64'ing the inputs.
You end up with a system where you need a valid JWT to hit the API, but you also need to include a valid JWT in any SQL injection attacks for them to work. There's no point in SQL injection attacks; one of the prerequisites is "a valid authentication token for the user", at which point the attacker could just connect to the API as them.
- Mister_Snuggles 2y ago> Ban all the SQL control characters in usernames/passwords and 400 any request that contains them. Hash and base64 both the username and password so they get converted to something where SQL injections are impossible because of the character set used. The problem that this tries to solve has been solved by every SQL database for a long time. Bind-parameters in queries are your friend, never build a query using string concatenation.
- everforward 2y agoYeah, and I use them, but I still get paranoid. Maybe that's due to my lack of understanding of them, my mental model is that it still resembles string concatenation on the backend. Now that I type it out, that sounds wrong, so I probably need to take a look at that.
- bavell 2y agoYou definitely need to spend a little time with them, they are safe and don't require all the crazy workarounds you've detailed to solve a non-issue.
- dventimihasura 2y ago> That login service can be written to be virtually immune to SQL attacks because it doesn't have to handle generalized queries. Ban all the SQL control characters in usernames/passwords Why? If that login service is a third-party provider, what does this accomplish?
- kbolino 2y agoThis feels like a custom solution, which will require some code in the database. It might work, and it might even be a good idea if the database is shared between many apps, but if the DB serves one app, I'd rather write code in a coding language than in (PL/)SQL.
- binary132 2y agoI tend to think of the database less as a filesystem and more as a block store. The filesystem layer in a real system is in fact implemented by the driver (and OS), and it might be implemented in terms of primitives that are persisted in the block device, but the block device doesn’t have a magical internal scripting language or query engine that the driver delegates to. The driver is in charge, and the persistence layer stores and loads what the driver tells it to.