从库输入构建的不安全代码¶
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
当库函数以潜在的不安全方式动态构建代码时,重要的是向库的客户端记录该函数应该只用于受信任的输入。如果该函数没有被记录为潜在的不安全函数,那么客户端可能会错误地使用包含不安全代码片段的输入,从而使客户端容易受到代码注入攻击。
建议¶
正确记录从未经清理的输入构建代码的库函数,或避免首先构建代码。
示例¶
以下示例展示了使用 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_set
和 class_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
参考资料¶
OWASP: 代码注入.
维基百科: 代码注入.
Ruby 文档: define_method.
Ruby 文档: class_variable_set.
常见弱点枚举: CWE-94.
常见弱点枚举: CWE-79.
常见弱点枚举: CWE-116.