CodeQL 文档

从库输入构建的不安全代码

ID: rb/unsafe-code-construction
Kind: path-problem
Security severity: 6.1
Severity: warning
Precision: medium
Tags:
   - security
   - external/cwe/cwe-094
   - external/cwe/cwe-079
   - external/cwe/cwe-116
Query suites:
   - ruby-security-extended.qls
   - ruby-security-and-quality.qls

点击查看 CodeQL 代码库中的查询

当库函数以潜在的不安全方式动态构建代码时,重要的是向库的客户端记录该函数应该只用于受信任的输入。如果该函数没有被记录为潜在的不安全函数,那么客户端可能会错误地使用包含不安全代码片段的输入,从而使客户端容易受到代码注入攻击。

建议

正确记录从未经清理的输入构建代码的库函数,或避免首先构建代码。

示例

以下示例展示了使用 eval 实现的两种方法:一个简单的反序列化例程和一个 getter 方法。如果使用不受信任的输入与这些方法一起使用,那么攻击者可能会在系统上执行任意代码。

module MyLib
    def unsafeDeserialize(value)
        eval("foo = #{value}")
        foo
    end

    def unsafeGetter(obj, path)
        eval("obj.#{path}")
    end
end

为了避免这个问题,要么正确记录该函数是潜在的不安全的,要么使用其他解决方案,例如 JSON.parse 或其他不允许执行任意代码的库。

require 'json'

module MyLib
    def safeDeserialize(value)
        JSON.parse(value)
    end

    def safeGetter(obj, path)
        obj.dig(*path.split("."))
    end
end

示例

再举一个例子,考虑以下代码,它动态构建一个具有自定义名称的 getter 方法的类。

require 'json'

module BadMakeGetter
  # Makes a class with a method named `getter_name` that returns `val`
  def self.define_getter_class(getter_name, val)
    new_class = Class.new
    new_class.module_eval <<-END
      def #{getter_name}
        #{JSON.dump(val)}
      end
    END
    new_class
  end
end

one = BadMakeGetter.define_getter_class(:one, "foo")
puts "One is #{one.new.one}"

该示例动态构建一个字符串,然后使用 module_eval 执行。如果指定的名称不是有效的 Ruby 标识符,则此代码将中断;如果该值由攻击者控制,则这可能导致代码注入。

更健壮的实现(也免受代码注入攻击)可以通过使用带有块的 module_eval 和使用 define_method 定义 getter 方法来完成。

# Uses `define_method` instead of constructing a string
module GoodMakeGetter
  def self.define_getter_class(getter_name, val)
    new_class = Class.new
    new_class.module_eval do
      define_method(getter_name) { val }
    end
    new_class
  end
end

two = GoodMakeGetter.define_getter_class(:two, "bar")
puts "Two is #{two.new.two}"

示例

此示例在另一个类上动态注册一个方法,该方法将其参数转发到目标对象。这种方法使用 module_eval 和字符串插值来构建类变量和方法。

module Invoker
  def attach(klass, name, target)
    klass.module_eval <<-CODE
      @@#{name} = target

      def #{name}(*args)
        @@#{name}.#{name}(*args)
      end
    CODE
  end
end

更安全的方法是使用 class_variable_setclass_variable_get 以及 define_method。字符串插值仍然用于构建类变量名称,但这很安全,因为 class_variable_set 不容易受到代码注入的影响。

send 用于动态调用由 name 指定的方法。这比前面的示例更健壮,因为它不允许执行任意代码,但它确实允许在目标对象上调用任何方法。

module Invoker
  def attach(klass, name, target)
    var = :"@@#{name}"
    klass.class_variable_set(var, target)
    klass.define_method(name) do |*args|
      self.class.class_variable_get(var).send(name, *args)
    end
  end
end

参考资料

  • ©GitHub, Inc.
  • 条款
  • 隐私