基于Rails中的用户输入安全动态地调用方法

时间:2012-03-19 22:21:44

标签: ruby-on-rails ruby security

我有一个我在Rails中构建的API。它运行我在模块中定义的一些方法,并将它们的返回值呈现为JSON。虽然我一直在开发,但API的整个代码一直是模块本身(内容不相关),单一路线:

  controller :cool do
    get "cool/query/*args" => :query
  end

和此:

class CoolController < ApplicationController
  include CoolModule
  def query
    args = params[:args].split("/")

    # convert the API URL to the method name
    method_symbol = args[0].tr("-","_").to_sym

    if !CoolModule.method_defined?(method_symbol)
      return nil
    end

    # is calling self.method a good idea here, or is there a better way?
    render json: self.method(method_symbol).call(args[1], args[2])

  end
end

我的API(即模块)包含~30个函数,每个函数接受可变数量的参数,我希望将其保存在模块中的路由逻辑(就像现在一样)。

它将被用作我的酷ajax前端和另一个 API之间的“中端”(可能会说),我无法控制它并且实际上是后端适当的。因此,需要特别关注,因为它既接收用户输入又向第三方发送查询(我对此负责)。

我的具体问题是:

  1. 这种一般策略(直接来自查询的方法名称)是否对生产安全/稳定?
  2. 如果策略可以接受,但我的实施不是,那么需要进行哪些更改?
  3. 如果策略存在根本缺陷,我应该采取哪些替代方案?
  4. 我的悲观主义者说'英里case - when,但我会感谢你的投入。

1 个答案:

答案 0 :(得分:1)

Module#method_defined?的问题是它可能在间接方法定义(其他包含的模块,如果模块是类的继承方法)以及私有方法上返回true。这意味着你(以及接触代码的其他任何人)必须非常小心你对该模块的处理。

因此,您可以使用此方法,但您需要对未来的维护者非常明确,模块中的任何方法都自动为外部接口。就个人而言,我会选择更明确的内容,例如允许api方法名称的简单白名单,例如:

require 'set'

module CoolModule
  ALLOWED_API_METHODS = Set[
    :foo,
    :bar,
    ...
  ]

  def self.api_allowed? meth
    ALLOWED_API_METHODS.include? meth.to_sym
  end
end

是的,你必须维护这个列表,但这并不难看,它是一个显式接口的文档;并且意味着你不会被后来的编码人员判断为了方便他们需要在模块中添加一些实用工具方法,从而意外地将它们导出到你的外部api。

或者对于单个列表,您可以使用define_for_api方法并使用它来代替def来声明api接口方法

module CoolModule
  @registered_api_methods = Set.new
  def self.define_for_api meth, &block
    define method meth, &block
    @registered_api_methods << meth
  end

  def self.api_allowed? meth
    @registered_api_methods.include? meth.to_sym
  end

  def api_dispatch meth, *args
    raise ArgumentError unless self.class.api_allowed? meth
    send(meth *args)
  end

  define_for_api :foo do |*args|
    do_something_common
    ...
  end

  define_for_api :bar do
    do_something_common
    ...
  end

  # this one is just ordinary method internal to module
  private
  def do_something_common
  end
end