Skip to content

Commit ad7e107

Browse files
am0o0hmac
authored andcommitted
add the new YAML/PLIST sinks into the existing rb/unsafe-deserialization query
1 parent b9296d3 commit ad7e107

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,41 @@ module UnsafeDeserialization {
7575
}
7676

7777
/**
78-
* An argument in a call to `YAML.load`, considered a sink
78+
* An argument in a call to `YAML.unsafe_*` and `YAML.load_stream` , considered sinks
7979
* for unsafe deserialization. The `YAML` module is an alias of `Psych` in
8080
* recent versions of Ruby.
8181
*/
8282
class YamlLoadArgument extends Sink {
8383
YamlLoadArgument() {
84-
this = API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall("load").getArgument(0)
84+
this =
85+
API::getTopLevelMember(["YAML", "Psych"])
86+
.getAMethodCall(["unsafe_load_file", "unsafe_load", "load_stream"])
87+
.getArgument(0)
88+
or
89+
this =
90+
API::getTopLevelMember(["YAML", "Psych"])
91+
.getAMethodCall(["unsafe_load", "load_stream"])
92+
.getKeywordArgument("yaml")
93+
or
94+
this =
95+
API::getTopLevelMember(["YAML", "Psych"])
96+
.getAMethodCall("unsafe_load_file")
97+
.getKeywordArgument("filename")
98+
}
99+
}
100+
101+
/**
102+
* An argument in a call to `YAML.parse*`, considered sinks
103+
* for unsafe deserialization if there is a call to `to_ruby` on returned value of them,
104+
* so this need some additional taint steps. The `YAML` module is an alias of `Psych` in
105+
* recent versions of Ruby.
106+
*/
107+
class YamlParseArgument extends Sink {
108+
YamlParseArgument() {
109+
this =
110+
API::getTopLevelMember(["YAML", "Psych"])
111+
.getAMethodCall(["parse", "parse_stream", "parse_file"])
112+
.getAMethodCall("to_ruby")
85113
}
86114
}
87115

@@ -208,4 +236,31 @@ module UnsafeDeserialization {
208236
)
209237
}
210238
}
239+
240+
/**
241+
* check whether an input argument has desired "key: value" input or not.
242+
*/
243+
predicate checkkeyValue(CfgNodes::ExprNodes::PairCfgNode p, string key, string value) {
244+
p.getKey().getConstantValue().isStringlikeValue(key) and
245+
DataFlow::exprNode(p.getValue()).getALocalSource().getConstantValue().toString() = value
246+
}
247+
248+
/**
249+
* An argument in a call to `Plist.parse_xml` where the marshal is `true` (which is
250+
* the default), considered a sink for unsafe deserialization.
251+
*/
252+
class UnsafePlistParsexmlArgument extends Sink {
253+
UnsafePlistParsexmlArgument() {
254+
exists(DataFlow::CallNode plistParsexml |
255+
plistParsexml = API::getTopLevelMember("Plist").getAMethodCall("parse_xml")
256+
|
257+
this = [plistParsexml.getArgument(0), plistParsexml.getKeywordArgument("filename_or_xml")] and
258+
// Exclude calls that explicitly pass a safe mode option.
259+
checkkeyValue(plistParsexml.getArgument(1).asExpr(), "marshal", "true")
260+
or
261+
this = [plistParsexml.getArgument(0), plistParsexml.getKeywordArgument("filename_or_xml")] and
262+
plistParsexml.getNumberOfArguments() = 1
263+
)
264+
}
265+
}
211266
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
private import codeql.ruby.AST
1010
private import codeql.ruby.DataFlow
1111
private import codeql.ruby.TaintTracking
12+
private import codeql.ruby.ApiGraphs
1213
import UnsafeDeserializationCustomizations
1314

1415
/**
@@ -23,6 +24,27 @@ class Configuration extends TaintTracking::Configuration {
2324

2425
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserialization::Sink }
2526

27+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
28+
exists(DataFlow::CallNode yaml_parser_methods |
29+
yaml_parser_methods =
30+
API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall(["parse", "parse_stream"]) and
31+
(
32+
nodeFrom = yaml_parser_methods.getArgument(0) or
33+
nodeFrom = yaml_parser_methods.getKeywordArgument("yaml")
34+
) and
35+
nodeTo = yaml_parser_methods.getAMethodCall("to_ruby")
36+
)
37+
or
38+
exists(DataFlow::CallNode yaml_parser_methods |
39+
yaml_parser_methods = API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall("parse_file") and
40+
(
41+
nodeFrom = yaml_parser_methods.getArgument(0) or
42+
nodeFrom = yaml_parser_methods.getKeywordArgument("filename")
43+
) and
44+
nodeTo = yaml_parser_methods.getAMethodCall("to_ruby")
45+
)
46+
}
47+
2648
override predicate isSanitizer(DataFlow::Node node) {
2749
super.isSanitizer(node) or
2850
node instanceof UnsafeDeserialization::Sanitizer

0 commit comments

Comments
 (0)