Unsafe code constructed from library input¶
ID: rb/unsafe-code-constructionKind: path-problemSecurity severity: 6.1Severity: warningPrecision: mediumTags: - security - external/cwe/cwe-094 - external/cwe/cwe-079 - external/cwe/cwe-116Query suites: - ruby-security-extended.qls - ruby-security-and-quality.qls
Click to see the query in the CodeQL repository
When a library function dynamically constructs code in a potentially unsafe way, it’s important to document to clients of the library that the function should only be used with trusted inputs. If the function is not documented as being potentially unsafe, then a client may incorrectly use inputs containing unsafe code fragments, and thereby leave the client vulnerable to code-injection attacks.
Recommendation¶
Properly document library functions that construct code from unsanitized inputs, or avoid constructing code in the first place.
Example¶
The following example shows two methods implemented usingeval: a simple deserialization routine and a getter method. If untrusted inputs are used with these methods, then an attacker might be able to execute arbitrary code on the system.
moduleMyLibdefunsafeDeserialize(value)eval("foo =#{value}")fooenddefunsafeGetter(obj,path)eval("obj.#{path}")endend
To avoid this problem, either properly document that the function is potentially unsafe, or use an alternative solution such asJSON.parse or another library that does not allow arbitrary code to be executed.
require'json'moduleMyLibdefsafeDeserialize(value)JSON.parse(value)enddefsafeGetter(obj,path)obj.dig(*path.split("."))endend
Example¶
As another example, consider the below code which dynamically constructs a class that has a getter method with a custom name.
require'json'moduleBadMakeGetter# Makes a class with a method named `getter_name` that returns `val`defself.define_getter_class(getter_name,val)new_class=Class.newnew_class.module_eval<<-END def #{getter_name} #{JSON.dump(val)} end ENDnew_classendendone=BadMakeGetter.define_getter_class(:one,"foo")puts"One is#{one.new.one}"
The example dynamically constructs a string which is then executed usingmodule_eval. This code will break if the specified name is not a valid Ruby identifier, and if the value is controlled by an attacker, then this could lead to code-injection.
A more robust implementation, that is also immune to code-injection, can be made by usingmodule_eval with a block and usingdefine_method to define the getter method.
# Uses `define_method` instead of constructing a stringmoduleGoodMakeGetterdefself.define_getter_class(getter_name,val)new_class=Class.newnew_class.module_evaldodefine_method(getter_name){val}endnew_classendendtwo=GoodMakeGetter.define_getter_class(:two,"bar")puts"Two is#{two.new.two}"
Example¶
This example dynamically registers a method on another class which forwards its arguments to a target object. This approach usesmodule_eval and string interpolation to construct class variables and methods.
moduleInvokerdefattach(klass,name,target)klass.module_eval<<-CODE @@#{name} = target def #{name}(*args) @@#{name}.#{name}(*args) end CODEendend
A safer approach is to useclass_variable_set andclass_variable_get along withdefine_method. String interpolation is still used to construct the class variable name, but this is safe becauseclass_variable_set is not susceptible to code-injection.
send is used to dynamically call the method specified byname. This is a more robust alternative than the previous example, because it does not allow arbitrary code to be executed, but it does still allow for any method to be called on the target object.
moduleInvokerdefattach(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)endendend
References¶
OWASP:Code Injection.
Wikipedia:Code Injection.
Ruby documentation:define_method.
Ruby documentation:class_variable_set.
Common Weakness Enumeration:CWE-94.
Common Weakness Enumeration:CWE-79.
Common Weakness Enumeration:CWE-116.