Add query rb/unsafe-deserialization

This commit is contained in:
Nick Rolfe
2021-09-07 18:05:52 +01:00
parent a62aa2b1b2
commit adceb0a2a1
6 changed files with 197 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about unsafe
* deserialization, as well as extension points for adding your own.
*/
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
module UnsafeDeserialization {
/**
* A data flow source for unsafe deserialization vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for unsafe deserialization vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for unsafe deserialization vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A source of remote user input, considered as a flow source for unsafe deserialization. */
class RemoteFlowSourceAsSource extends Source {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
}
/**
* An argument in a call to `Marshal.load` or `Marshal.restore`, considered a
* sink for unsafe deserialization.
*/
class MarshalLoadOrRestoreArgument extends Sink {
MarshalLoadOrRestoreArgument() {
this = API::getTopLevelMember("Marshal").getAMethodCall(["load", "restore"]).getArgument(0)
}
}
/**
* An argument in a call to `YAML.load`, considered a sink for unsafe
* deserialization.
*/
class YamlLoadArgument extends Sink {
YamlLoadArgument() {
this = API::getTopLevelMember("YAML").getAMethodCall("load").getArgument(0)
}
}
/**
* An argument in a call to `JSON.load` or `JSON.restore`, considered a sink
* for unsafe deserialization.
*/
class JsonLoadArgument extends Sink {
JsonLoadArgument() {
this = API::getTopLevelMember("JSON").getAMethodCall(["load", "restore"]).getArgument(0)
}
}
}

View File

@@ -0,0 +1,30 @@
/**
* Provides a taint-tracking configuration for reasoning about unsafe deserialization.
*
* Note, for performance reasons: only import this file if
* `UnsafeDeserialization::Configuration` is needed, otherwise
* `UnsafeDeserializationCustomizations` should be imported instead.
*/
private import ruby
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
import UnsafeDeserializationCustomizations
/**
* A taint-tracking configuration for reasoning about unsafe deserialization.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "UnsafeDeserialization" }
override predicate isSource(DataFlow::Node source) {
source instanceof UnsafeDeserialization::Source
}
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserialization::Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof UnsafeDeserialization::Sanitizer
}
}

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Deserializing untrusted data using any method that allows the construction of
arbitrary objects is easily exploitable and, in many cases, allows an attacker
to execute arbitrary code.
</p>
</overview>
<recommendation>
<p>
Avoid deserialization of untrusted data if possible. If the architecture permits
it, use serialization formats that cannot represent arbitarary objects. For
libraries that support it, such as the Ruby standard library's <code>JSON</code>
module, ensure that the parser is configured to disable
deserialization of arbitrary objects.
</p>
</recommendation>
<example>
<p>
The following example calls the <code>Marshal.load</code>, <code>JSON.load</code>, and
<code>YAML.load</code> methods on data from an HTTP request. Since these methods
are capable of deserializing to arbitrary objects, this is inherently unsafe.
</p>
<sample src="examples/UnsafeDeserializationBad.rb"/>
<p>
Using <code>YAML.parse</code> instead, with the default options, removes the
vulnerability.
</p>
<sample src="examples/UnsafeDeserializationGood.rb"/>
</example>
<references>
<li>
OWASP vulnerability description:
<a href="https://www.owasp.org/index.php/Deserialization_of_untrusted_data">deserialization of untrusted data</a>.
</li>
<li>
Ruby documentation: <a href="https://docs.ruby-lang.org/en/3.0.0/doc/security_rdoc.html">guidance on deserializing objects safely</a>.
</li>
<li>
Ruby documentation: <a href="https://ruby-doc.org/core-3.0.2/Marshal.html#module-Marshal-label-Security+considerations">security guidance on the Marshal library</a>.
</li>
<li>
Ruby documentation: <a href="https://ruby-doc.org/stdlib-3.0.2/libdoc/json/rdoc/JSON.html#method-i-load">security guidance on JSON.load</a>.
</li>
<li>
Ruby documentation: <a href="https://ruby-doc.org/stdlib-3.0.2/libdoc/yaml/rdoc/YAML.html#module-YAML-label-Security">security guidance on the YAML library</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Deserialization of user-controlled data
* @description Deserializing user-controlled data may allow attackers to
* execute arbitrary code.
* @kind path-problem
* @problem.severity warning
* @security-severity 9.8
* @precision high
* @id rb/unsafe-deserialization
* @tags security
* external/cwe/cwe-502
*/
import ruby
import DataFlow::PathGraph
import codeql.ruby.DataFlow
import codeql.ruby.security.UnsafeDeserializationQuery
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe deserialization of $@.", source.getNode(), "user input"

View File

@@ -0,0 +1,20 @@
require 'json'
require 'yaml'
class UserController < ActionController::Base
def marshal_example
data = Base64.decode64 params[:data]
object = Marshal.load data
# ...
end
def json_example
object = JSON.load params[:json]
# ...
end
def yaml_example
object = YAML.load params[:yaml]
# ...
end
end

View File

@@ -0,0 +1,8 @@
require 'json'
class UserController < ActionController::Base
def safe_json_example
object = JSON.parse params[:json]
# ...
end
end