Skip to content

Commit b8c3cba

Browse files
committed
Ruby: Consolidate unsafe deserialization queries
Merge the experimental YAMLUnsafeDeserialization and PlistUnsafeDeserialization queries into the generate UnsafeDeserialization query in the default suite. These queries look for some specific sinks that we now find in the general query. Also apply some small code and comment refactors.
1 parent d727d57 commit b8c3cba

16 files changed

+75
-330
lines changed

ruby/ql/lib/codeql/ruby/frameworks/Yaml.qll

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,28 @@ private import codeql.ruby.ApiGraphs
88

99
/**
1010
* A taint step related to the result of `YAML.parse` calls, or similar.
11-
*In the following example, this step will propagate taint from
12-
*`source` to `sink`:
11+
* In the following example, this step will propagate taint from
12+
* `source` to `sink`:
1313
*
14-
*```rb
15-
*x = source
16-
*result = YAML.parse(x)
17-
*sink result.to_ruby # Unsafe call
14+
* ```rb
15+
* x = source
16+
* result = YAML.parse(x)
17+
* sink result.to_ruby # Unsafe call
1818
* ```
1919
*/
2020
private class YamlParseStep extends AdditionalTaintStep {
2121
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
2222
exists(DataFlow::CallNode yamlParserMethod |
23-
yamlParserMethod = yamlNode().getAMethodCall(["parse", "parse_stream"]) and
23+
succ = yamlParserMethod.getAMethodCall("to_ruby") and
2424
(
25-
pred = yamlParserMethod.getArgument(0) or
26-
pred = yamlParserMethod.getKeywordArgument("yaml")
27-
) and
28-
succ = yamlParserMethod.getAMethodCall("to_ruby")
29-
or
30-
yamlParserMethod = yamlNode().getAMethodCall("parse_file") and
31-
(
32-
pred = yamlParserMethod.getArgument(0) or
33-
pred = yamlParserMethod.getKeywordArgument("filename")
34-
) and
35-
succ = yamlParserMethod.getAMethodCall("to_ruby")
25+
yamlParserMethod = yamlNode().getAMethodCall(["parse", "parse_stream"]) and
26+
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("yaml")]
27+
or
28+
yamlParserMethod = yamlNode().getAMethodCall("parse_file") and
29+
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("filename")]
30+
)
3631
)
3732
}
3833
}
3934

40-
/**
41-
* YAML/Psych Top level Class member
42-
*/
4335
private API::Node yamlNode() { result = API::getTopLevelMember(["YAML", "Psych"]) }

ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,13 @@ module UnsafeDeserialization {
7575
}
7676

7777
/**
78-
* An argument in a call to `YAML.unsafe_*` and `YAML.load_stream` , considered sinks
78+
* An argument in a call to `YAML.unsafe_*` and `YAML.load_stream` , considered a sink
7979
* for unsafe deserialization. The `YAML` module is an alias of `Psych` in
8080
* recent versions of Ruby.
81-
* the `this = yamlNode().getAMethodCall("load").getArgument(0)` is safe
82-
* in psych/yaml library after [v4.0.0](https://github.com/ruby/psych/releases/tag/v4.0.0), so it will be removed in future.
8381
*/
8482
class YamlLoadArgument extends Sink {
8583
YamlLoadArgument() {
84+
// Note: this is safe in psych/yaml >= 4.0.0.
8685
this = yamlNode().getAMethodCall("load").getArgument(0)
8786
or
8887
this =
@@ -94,16 +93,11 @@ module UnsafeDeserialization {
9493
}
9594
}
9695

97-
/**
98-
* YAML/Psych Top level Class member
99-
*/
10096
private API::Node yamlNode() { result = API::getTopLevelMember(["YAML", "Psych"]) }
10197

10298
/**
103-
* An argument in a call to `YAML.parse*`, considered sinks
104-
* for unsafe deserialization if there is a call to `to_ruby` on returned value of them,
105-
* so this need some additional taint steps. The `YAML` module is an alias of `Psych` in
106-
* recent versions of Ruby.
99+
* An argument in a call to `YAML.parse*`, considered a sink for unsafe deserialization
100+
* if there is a call to `to_ruby` on the returned value.
107101
*/
108102
class YamlParseArgument extends Sink {
109103
YamlParseArgument() {
@@ -237,7 +231,7 @@ module UnsafeDeserialization {
237231
}
238232

239233
/**
240-
* An argument in a call to `Plist.parse_xml` where the marshal is `true` (which is
234+
* An argument in a call to `Plist.parse_xml` where `marshal` is `true` (which is
241235
* the default), considered a sink for unsafe deserialization.
242236
*/
243237
class UnsafePlistParsexmlArgument extends Sink {
@@ -246,10 +240,11 @@ module UnsafeDeserialization {
246240
plistParseXml = API::getTopLevelMember("Plist").getAMethodCall("parse_xml")
247241
|
248242
this = [plistParseXml.getArgument(0), plistParseXml.getKeywordArgument("filename_or_xml")] and
249-
plistParseXml.getKeywordArgument("marshal").getConstantValue().isBoolean(true)
250-
or
251-
this = [plistParseXml.getArgument(0), plistParseXml.getKeywordArgument("filename_or_xml")] and
252-
plistParseXml.getNumberOfArguments() = 1
243+
(
244+
plistParseXml.getKeywordArgument("marshal").getConstantValue().isBoolean(true)
245+
or
246+
plistParseXml.getNumberOfArguments() = 1
247+
)
253248
)
254249
}
255250
}

ruby/ql/src/experimental/cwe-502/PlistUnsafeDeserialization.qhelp

Lines changed: 0 additions & 24 deletions
This file was deleted.

ruby/ql/src/experimental/cwe-502/PlistUnsafeDeserialization.ql

Lines changed: 0 additions & 57 deletions
This file was deleted.

ruby/ql/src/experimental/cwe-502/PlistUnsafeDeserialization.rb

Lines changed: 0 additions & 17 deletions
This file was deleted.

ruby/ql/src/experimental/cwe-502/YAMLUnsafeDeserialization.qhelp

Lines changed: 0 additions & 26 deletions
This file was deleted.

ruby/ql/src/experimental/cwe-502/YAMLUnsafeDeserialization.ql

Lines changed: 0 additions & 88 deletions
This file was deleted.

ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ deserialization of arbitrary objects.
1919
</p>
2020

2121
<p>
22-
YAML/Psych recommendation:
23-
After Psych(YAML) 4.0.0, the load method is same as safe_load method.
24-
This vulnerability can be prevented by using YAML.load (same as <code>YAML.safe_load</code>), <code>YAML.load_file</code> (same as <code>YAML.safe_load_file</code>) instead of <code>YAML.unsafe_*</code> methods.
25-
Be careful that <code>YAML.load_stream</code> don't use safe_load method, Also Be careful the <code>to_ruby</code> method of Psych get called on a trusted parsed (<code>YAML.parse*</code>) yaml document.
22+
If deserializing an untrusted YAML document using the <code>psych</code> gem
23+
prior to version 4.0.0, the <code>load</code> method is vulnerable. Use
24+
<code>safe_load</code> instead. With <code>psych</code> version 4.0.0 and later,
25+
the <code>load</code> is safe. The same applies to <code>load_file</code>.
26+
<code>load_stream</code> is vulnerable in all versions. The safe versions of these
27+
methods (<code>safe_load</code> and <code>safe_load_file</code>) are not vulnerable
28+
in any known version.
2629
</p>
2730

2831
<p>
29-
This vulnerability in Plist can be prevented by calling <code>Plist.parse_xml FileOrXmlString, marshal: false</code>.
32+
To safely deserialize <a href="https://en.wikipedia.org/wiki/Property_list">Property List</a>
33+
files using the <code>plist</code> gem, ensure that you pass <code>marshal: false</code>
34+
when calling <code>Plist.parse_xml</code>.
3035
</p>
3136
</recommendation>
3237

@@ -39,13 +44,6 @@ to arbitrary objects, this is inherently unsafe.
3944
</p>
4045
<sample src="examples/UnsafeDeserializationBad.rb"/>
4146

42-
<p>In the example below, you can see safe and unsafe methods get called by a remote user input. You can give correct authorization to users, or you can use safe methods for loading yaml documents.</p>
43-
<sample src="examples/YAMLUnsafeDeserialization.rb"/>
44-
45-
<p>In the example below, you can see safe and unsafe Plist dangerous method calls that can be abused by a remote user input. You can use "marshal: false" as an arugument for <code>Plist.parse_xml</code> to use it safe.
46-
</p>
47-
<sample src="examples/PlistUnsafeDeserialization.rb"/>
48-
4947
<p>
5048
Using <code>JSON.parse</code> and <code>YAML.safe_load</code> instead, as in the
5149
following example, removes the vulnerability. Similarly, calling

ruby/ql/test/query-tests/experimental/Security/cwe-502/PlistUnsafeDeserialization.expected

Lines changed: 0 additions & 12 deletions
This file was deleted.

ruby/ql/test/query-tests/experimental/Security/cwe-502/PlistUnsafeDeserialization.qlref

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)