【问题标题】:Avoid sql injection with query by ActiveRecord::Base.connection.execute避免使用 ActiveRecord::Base.connection.execute 查询的 sql 注入
【发布时间】:2014-12-27 08:54:19
【问题描述】:

我需要通过ActiveRecord::Base.connection.execute(USER_INPUT)的方法查询

查询字符串由 USER INPUT 给出(他们可以通过 URL 查询任何time periodproduct_type

如何防止 SQL 注入?为了避免对我的系统造成任何损害?

是否可以限制用户只能查询SELECT 除了DELETE, TRUNCATE, INSERT, UPDATE ...

【问题讨论】:

  • 想要拥有它似乎很奇怪。是否有可能拥有一组更细粒度的可能命令?此外,您所说的方式是 SQL 注入,它只是用户输入。您需要提供一些规则,您将根据这些规则制定接受用户输入的策略。
  • 你不能。您正在接受来自用户的 SQL。当您打开门并说“进来,运行您喜欢的任何 SQL”时,阻止 SQL“注入”是没有意义的。您是否尝试过滤用户输入以限制可能运行的种类的SQL命令

标签: ruby-on-rails ruby activerecord postgresql-9.3


【解决方案1】:

如果我正确理解您想要什么,那么您实际上根本不想阻止 SQL 注入。事实上,您允许您的应用在设计上运行任意 SQL。

您似乎想要限制 什么 SQL 可以运行。

这注定要失败。至少在 PostgreSQL 上,SELECT 可以运行各种你不希望普通用户在使用特权用户调用时运行的功能。他们还可以访问包含您不一定希望他们知道的信息的系统目录。

按命令类型过滤将不起作用,即使您成功编写了一个可以防止在单个 SQL 字符串中包含多个命令的解析器。这并不像您希望的那么简单。对于文字、$$ 引用、标识符引用、--/* */ cmets 等,您必须打开和关闭 standard_conforming_strings。您弄错并在您的过滤器和/ 或禁止完全合法的命令。

一种更安全的方法是在运行来自用户的命令时使用权限较低的数据库角色。这个角色应该只有权阅读你明确GRANTed 它的内容。它应该没有INSERTUPDATEDELETE 权限,没有表或其他对象所有权,当然也不是超级用户。你让用户发送他们想要的任何 SQL,或者粗略地尝试过滤它,但是你依靠数据库的访问权限来真正阻止他们做任何顽皮的事情。您也可以SET TRANSACTION READ ONLY ... 但请注意,这样的设置主要是建议性的,而不是硬性限制。

据我所知,与升级相比,Rails 无法轻松获得非特权连接,或者让您的应用每天使用特权较低的连接工作。这是因为 Rails 喜欢以单一数据库用户的身份做所有事情,通常是拥有所有对象的高特权用户,因此它可以随时进行迁移。 Rails/ActiveRecord 哲学的全部内容是,数据库只不过是一个愚蠢的行存储。

即使使用更灵活的框架,获得不同用户权限的连接仍然很痛苦。 SET ROLESET SESSION AUTHORIZATION 工作,但两者都可以重置以获得特权连接,目前在 PostgreSQL 中没有放弃特权的概念。用户可能会通过多种方式偷偷 RESET ROLERESET SESSION AUTHORIZATION 通过任意 SQL 使用它们。因此,您实际上必须使用不同的凭据建立新连接。

这就是我要做的。使用不同的凭据建立新连接,并将 SQL 作为非常受限的用户帐户运行。您可以执行REVOKE 之类的操作,从该帐户查看pg_procpg_class,限制它可以看到的系统目录数量,并且您可以只授予它对您真正需要的表的最低访问权限它有。 (记住REVOKE CREATE ON SCHEMA public FROM public 然后GRANT 回复给您希望能够制作表格的用户)。

【讨论】:

  • Rails ActiveRecord(就像所有的 ORM 一样,包括我写的那些)是穿紧身衣的垃圾。如果您绕过所有“友好”和“便携”的东西并与底层pg interface 交谈,您可以获得更大的灵活性。整个“将用户提供的 SQL 发送到数据库中”这件事仍然让我感到沮丧。
  • @muistooshort 是的。在大多数情况下我不会这样做——如果可能的话,我会使用受限查询 DSL。我可以想象有必要的情况......但对于大多数人来说,我宁愿让用户直接通过 psql 以受限角色进入。
【解决方案2】:

我想你要找的是quote

sql = ActiveRecord::Base.connection.quote( potentially harmful string )
res = ActiveRecord::Base.connection.execute(sql)

你也可以使用sanitize_sql_array:

sql = ActiveRecord::Base.send(:sanitize_sql_array, sql_array)
res = ActiveRecord::Base.connection.execute(sql)

【讨论】:

  • OP 不只是想要清理 - 如果不是SELECT,他想拒绝查询
【解决方案3】:

我在自己的应用程序中尝试创建一种“数据剪辑”(Heroku 风格)时遇到了类似的问题。虽然我将我的问题描述为“如何让我的应用程序的用户以只读模式运行任意 SQL”。

不需要特定数据库用户的最优雅的解决方案是:

ActiveRecord::Base.transaction do
  results = ActiveRecord::Base.connection.select_all sql
  do_your_stuff_here_with results
  raise ActiveRecord::Rollback
end

引发 ActiveRecord::Rollback 将回滚事务,防止任何东西被持久化,异常不会在事务块之外重新引发。

select_all 防止使用多个语句,因为它处理 libpq 中的准备好的语句接口。

【讨论】:

    【解决方案4】:

    我将在该数据库上创建另一个用户,该用户仅授予运行SELECT 查询的权限。而不是使用此受限用户帐户来运行从用户输入生成的查询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-21
      • 2012-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-13
      • 2017-12-02
      相关资源
      最近更新 更多