You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The ActiveRecord::Base.connection_handler.while_preventing_writes method does not prevent all writes as the name suggests.
This should be mentioned in the method documentation, particularly because in the original Changelog the author @eileencodes does mention this.
Description
While looking for a way to safely expose a sql query parameter I stumbled upon the while_preventing_writes method. I was surprised that I wouldn't need a another readonly DB user. Not quite sure if this would really work, I did some digging and found that, at least in Postgresql, this method is not sufficient to block all writes.
Steps to reproduce
All of the following queries do write to a Postgresql database in a while_preventing_writes block:
Explain analyze
Postgresql let's you not only explain a query, you can also explain and analyze a query which actually runs it and also analyzes it:
ActiveRecord::Base.connection_handler.while_preventing_writesdoActiveRecord::Base.connection.execute"EXPLAIN ANALYZE INSERT INTO roles_users (role_id, user_id) VALUES (1, 1234)"ActiveRecord::Base.connection.execute"EXPLAIN ANALYZE UPDATE users SET role = 'admin' WHERE id = 10"ActiveRecord::Base.connection.execute"EXPLAIN ANALYZE DELETE FROM users WHERE id = 26"end
That is why these comments look like SELECT queries to the regular expression READ_QUERY which is supposed to guard against writes, but to the SQL parser they are comments:
ActiveRecord::Base.connection_handler.while_preventing_writesdoActiveRecord::Base.connection.execute"/*/**/SELECT*/ INSERT INTO roles_users (role_id, user_id) VALUES (1, 1234)"ActiveRecord::Base.connection.execute"/*/**/SELECT*/ UPDATE users SET role = 'admin' WHERE id = 10"ActiveRecord::Base.connection.execute"/*/**/SELECT*/ DELETE FROM users WHERE id = 26"end
Select into
Finally postgres let's you create tables using the SELECT INTO statement, which starts the query with a SELECT but then goes on to write the result into a new table. While it cannot write into an existing table, technically it's still a write query:
ActiveRecord::Base.connection_handler.while_preventing_writesdoActiveRecord::Base.connection.execute"SELECT * INTO users_new FROM users"end
Expected behavior
The writing queries are not applied to the database.
Actual behavior
The queries are written to the database. The regularexpression which is supposed to guard against write queries does not catch the write statements.
Discussion
I suspect that parsing SQL with regexes is like parsing HTML with regexes: it cannot work. I suspect that @eileencodes was aware of the limitations of this regular expression approach and mentioned them in the Changelog at the time.
I did spend a fair amount of time trying to fix these issues and I believe that a somewhat more complex Regex will be able to catch some of these issues, but not all.
Considering that the nested comments can have any level of depth, I don't think that they can be solved with Regexes. You will need to parse the SQL and strip out the comments entirely to safeguard against those.
The SELECT INTO issue is complex as well, as there can be a lot of statements between the SELECT and the INTO some of which may be strings or comments, so the regular expression approach is probably not going to work here as well.
I have tests and a fix for the EXPLAIN ANALYZE queries, if you want me to I can open a pull request for those. I will add a pull request for the documentation change.
System configuration
Rails master
ruby 2.7.0p0
vagrant@rails-dev-box
The text was updated successfully, but these errors were encountered:
This issue has been automatically marked as stale because it has not been commented on for at least three months.
The resources of the Rails team are limited, and so we are asking for your help.
If you can still reproduce this error on the 6-0-stable branch or on master, please reply with all of the information you have about it in order to keep the issue open.
Thank you for all your contributions.
I find it somewhat frustrating, that I put considerable effort into pointing out this potentially dangerous misnomer and it does not get addressed. The proposed change in the PR is a minuscule addition to the documentation which may save someone from relying on this method to prevent SQL injections.
Adds a note to clarify that `while_preventing_writes` is not meant to be
a replacement for a readonly replica user.
Closes#39132 and #39133
Co-authored-by: Eike Send <eike.send@gmail.com>
The
ActiveRecord::Base.connection_handler.while_preventing_writes
method does not prevent all writes as the name suggests.This should be mentioned in the method documentation, particularly because in the original Changelog the author @eileencodes does mention this.
Description
While looking for a way to safely expose a sql query parameter I stumbled upon the
while_preventing_writes
method. I was surprised that I wouldn't need a another readonly DB user. Not quite sure if this would really work, I did some digging and found that, at least in Postgresql, this method is not sufficient to block all writes.Steps to reproduce
All of the following queries do write to a Postgresql database in a
while_preventing_writes
block:Explain analyze
Postgresql let's you not only explain a query, you can also explain and analyze a query which actually runs it and also analyzes it:
Nested comments
Postgres has a peculiar comment syntax which lets you nest comments within other comments:
That is why these comments look like SELECT queries to the regular expression READ_QUERY which is supposed to guard against writes, but to the SQL parser they are comments:
Select into
Finally postgres let's you create tables using the
SELECT INTO
statement, which starts the query with aSELECT
but then goes on to write the result into a new table. While it cannot write into an existing table, technically it's still a write query:Expected behavior
The writing queries are not applied to the database.
Actual behavior
The queries are written to the database. The regular expression which is supposed to guard against write queries does not catch the write statements.
Discussion
I suspect that parsing SQL with regexes is like parsing HTML with regexes: it cannot work. I suspect that @eileencodes was aware of the limitations of this regular expression approach and mentioned them in the Changelog at the time.
I did spend a fair amount of time trying to fix these issues and I believe that a somewhat more complex Regex will be able to catch some of these issues, but not all.
Considering that the nested comments can have any level of depth, I don't think that they can be solved with Regexes. You will need to parse the SQL and strip out the comments entirely to safeguard against those.
The
SELECT INTO
issue is complex as well, as there can be a lot of statements between theSELECT
and theINTO
some of which may be strings or comments, so the regular expression approach is probably not going to work here as well.I have tests and a fix for the
EXPLAIN ANALYZE
queries, if you want me to I can open a pull request for those. I will add a pull request for the documentation change.System configuration
The text was updated successfully, but these errors were encountered: