内容简介:Rails 中 ActiveRecord 的不当使用产生 SQLI 风险
Ruby on Rails 是一个经典的 MVC 框架。其中,ActiveRecord 是 MVC 中的 M(模型),负责处理数据和业务逻辑。每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录,通常表的每个字段在类中都有相应的 Field。ActiveRecord 同时负责把自己持久化,在 ActiveRecord 中封装了对数据库的访问,即 CURD。
使用 ActiveRecord 执行 SQL 语句的大概顺序是:
- 把提供的查询方法转换为等价的 SQL 查询。
- 触发 SQL 查询并从数据库中检索对应的结果。
- 为每个查询结果实例化对应的模型对象。
- 当存在回调时,先调用 after_find 回调再调用 after_initialize 回调。
只要能够理解第一步中的转换规则,那么针对 ActiveRecord 的 SQL 注入和普通的 SQL 注入并没有什么区别。
然而,Rails 为 ActiveRecord 方法提供了内置的 SQL 过滤器,用于转义 '、"、NULL 和换行符,使得大部分 SQL 注入都失去作用。但这并不代表开发者写出来的代码就是绝对安全的。如果没有对用户输入手动进行过滤,并且错误运用了某些方法,那么仍然存在 SQL 注入的风险。具体来讲,在使用某些方法时,需要手动触发这个过滤器。
所谓“错误使用“指的是,向某些方法传入 String 而不是 Array 或 Hash。因为在这种情况下,过滤器不会被触发。
where
以常用的 where 方法作例子,它能够接受的参数类型有 string,array 和 hash。然而,过滤器只有当传入的参数是 array 或 hash 时才是生效。如果开发者直接传入一个 string 类型的参数,那么会带来严重的安全隐患,看下面这个例子:
其中,params[:name]和params[:password] 是表单提交的用户名和密码。转换后的 SQL 语句为:
如果输入的 name为“') OR 1=1--“,那么 SQL 语句就会变成:
“OR 1”后面的内容全部被注释掉了,这是很常见的一句话密码,它和不使用 ORM 的情况下SQL 注入的 PAYLOAD 并无二异。
安全的写法如下:
- 传入 Array:
- 或者传入 Hash:
这两种情况下过滤器会被触发,输入的引号会被转义引起报错:
find_by
同理,find_by 方法也有相同的问题,因为它等价于 where(*args).take:
params[:id] = "admin = '’ OR 1=1 )#" User.find_by params[:id]
SQL 语句:
最安全的方式是传入 Hash:
Select/pluck
select 方法用于指定查询字段,如果传入的是 String,那么输入完全不会被转义:
SQL:
需要注意 select 方法返回的是 Model 类。
pluck 方法和 select 方法作用的地方一致,唯一的不同是 pluck 方法返回一个数组。等价于:
因为 select 和 pluck 方法作用于 SQL 语句的最开始,因此 SQL 注入的方式非常灵活。
delete_all/destroy_all/update_all
这三个批量操作方法都可以进行 SQL 注入,只要传入的参数是 String。
delete_all 和 destroy_all 的区别是 destroy_all 会触发 ActiveRecord 的回调,而 delete_all 会直接把 SQL 语句传给数据库,所以理论上 destroy_all 比 delete_all 更安全一点。总之,正确的使用方法是传入 Hash。
- delete_all/destroy_all:
- SQL 语句:
- update_all:
SQL 语句:
- average
- calculate
- count
- maximum
- minimum
- sum
- 在 error-based 或 bool 类型的 SQL 注入中,分组和排序子句可以用于猜测字段数:
select * from user order by 1,2,3; (假设 user 表只有两个字段)
如果开启了错误提醒,那么字段名会直接显示在错误中。
其它情况下,可以根据返回的页面是否正确来判断。 - 当然也可以直接爆数据:
params[:sortby] = "(CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END)" User.order("#{params[:sortby]} ASC")
From
from方法指定查询的表明,也就是注入点在from之后:
params[:from] = "users WHERE admin = 't' OR 1=1 #" User.from(params[:from]).where(admin: false)
SQL语句:
SELECT "users".* FROM users WHERE admin = 't' OR 1=1 #WHERE "users"."admin" = ?
后面的where被截断了。
Calculate
指的是 ActiveRecord::Calculations#calculate 中的方法,对应于 SQL 语法中的聚集函数
包含有:
注入点是 select 和 from 之间:
SQL 语句:
Group/Order/Having/Reorder
这几个方法对应 SQL 中的 排序 和分组子句,group 方法对应于 group by 子句;having 对应于having 子句;oreder 和 reorder 对应于 order by 子句(reorder 是用于覆盖作用域的默认排序,所以它们的注入方法是完全一样的)。
SQL 语句:
SELECT "users".* FROM "users" ORDER BY (CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END) ASC
(猜解 password 的第一位是否为’s’,根据返回的是 True 还是 False ,排序的结果会有所不同)
还有一些方法如 lock,joins 也存在注入点,但因为在实际情况下难以利用或者跟前文的利用方法有重复,故不展开介绍。
小结
总的来说对于 ActiveRecord 的 SQL 注入和普通的 SQL 注入其实差别并不是很大,关键在于掌握 query methods 到 SQL 语句的转换规则以及了解 SQL 过滤器的触发条件。从以上的那些例子可以很明显得看出,当传入的参数是 String 时,许多方法是不会触发 SQL 过滤器的,这时的 SQL 注入就和普通的 SQL 注入基本没有区别。
攻击者应该了解哪些方法可能存在 SQL 注入漏洞。然而实战时的难点主要在于判断 SQL 查询使用的方法,因为链式方法的灵活性,同样的 SQL 查询可以有很多种写法,这对于 SQL 注入带来了很大的困难。
而对于 Rails 开发者来说,应当切记不要直接向查询方法直接传入 String,而应使用 Array 和 Hash,只要能做到这一点,那么被 SQL 注入的可能性将会大幅降低。
--------
微信公众号:TwoSecurity (二向箔安全)
以上所述就是小编给大家介绍的《Rails 中 ActiveRecord 的不当使用产生 SQLI 风险》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。