Skip to content

Commit 4360a56

Browse files
am0o0hmac
authored andcommitted
v2 add plist.parse_xml as a dangerous sink and enhancements on documents
1 parent 0521ffe commit 4360a56

16 files changed

+152
-201
lines changed

ruby/ql/src/experimental/CWE-502/YAMLUnsafeYamlDeserialization.qhelp

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

ruby/ql/src/experimental/CWE-502/YAMLUnsafeYamlDeserialization.ql

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

ruby/ql/src/experimental/CWE-502/YAMLUnsafeYamlDeserialization.rb

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Processing an unvalidated user input can allow an attacker to execute arbitrary code in your application.
6+
Unsafe deserializing the malicious serialized xml document through the Plist library, making it possible to execute some code or execute arbitrary code with the help of a complete gadget chain.
7+
</p>
8+
</overview>
9+
<recommendation>
10+
<p>
11+
This vulnerability can be prevented by using <code>Plist.parse_xml</code>.
12+
</p>
13+
</recommendation>
14+
<example>
15+
<p>In the example below, you can see safe and unsafe call of this dangerous method 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.
16+
</p>
17+
<sample src="PlistUnsafeYamlDeserialization.rb" />
18+
</example>
19+
<references>
20+
<li>
21+
Security considerations from library documentation
22+
<a href="https://github.com/patsplat/plist#label-Security+considerations">patsplat/plist</a>.
23+
</li>
24+
</references>
25+
</qhelp>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @name Unsafe Deserialization of user-controlled data by Plist
3+
* @description Deserializing user-controlled data may allow attackers to
4+
* execute arbitrary code.
5+
* @kind path-problem
6+
* @problem.severity warning
7+
* @security-severity 9.8
8+
* @precision high
9+
* @id rb/Plist-unsafe-deserialization
10+
* @tags security
11+
* experimental
12+
* external/cwe/cwe-502
13+
*/
14+
15+
import codeql.ruby.ApiGraphs
16+
import codeql.ruby.DataFlow
17+
import codeql.ruby.TaintTracking
18+
import codeql.ruby.CFG
19+
import DataFlow::PathGraph
20+
import codeql.ruby.security.UnsafeDeserializationCustomizations
21+
22+
abstract class PlistUnsafeSinks extends DataFlow::Node { }
23+
24+
/**
25+
* check whether an input argument has desired "key: value" input or not.
26+
* borrowed from UnsafeDeserialization module with some changes
27+
*/
28+
predicate checkkeyBalue(CfgNodes::ExprNodes::PairCfgNode p, string key, string value) {
29+
p.getKey().getConstantValue().isStringlikeValue(key) and
30+
DataFlow::exprNode(p.getValue()).getALocalSource().getConstantValue().toString() = value
31+
}
32+
33+
/**
34+
* An argument in a call to `Plist.parse_xml` where the marshal is `true` (which is
35+
* the default), considered a sink for unsafe deserialization.
36+
* borrowed from UnsafeDeserialization module with some changes
37+
*/
38+
class UnsafePlistParsexmlArgument extends PlistUnsafeSinks {
39+
UnsafePlistParsexmlArgument() {
40+
exists(DataFlow::CallNode plistParsexml |
41+
plistParsexml = API::getTopLevelMember("Plist").getAMethodCall("parse_xml")
42+
|
43+
this = [plistParsexml.getArgument(0), plistParsexml.getKeywordArgument("filename_or_xml")] and
44+
// Exclude calls that explicitly pass a safe mode option.
45+
checkkeyBalue(plistParsexml.getArgument(1).asExpr(), "marshal", "true")
46+
or
47+
this = [plistParsexml.getArgument(0), plistParsexml.getKeywordArgument("filename_or_xml")] and
48+
plistParsexml.getNumberOfArguments() = 1
49+
)
50+
}
51+
}
52+
53+
class Configuration extends TaintTracking::Configuration {
54+
Configuration() { this = "PlistUnsafeDeserialization" }
55+
56+
override predicate isSource(DataFlow::Node source) {
57+
// to detect CVE-2021-33575, we should uncomment following line instead of current UnsafeDeserialization::Source
58+
// source instanceof DataFlow::LocalSourceNode
59+
source instanceof UnsafeDeserialization::Source
60+
}
61+
62+
override predicate isSink(DataFlow::Node sink) { sink instanceof PlistUnsafeSinks }
63+
}
64+
65+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
66+
where config.hasFlowPath(source, sink)
67+
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
68+
"potentially untrusted source"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require 'yaml'
2+
class UsersController < ActionController::Base
3+
def example
4+
# not safe
5+
result = Plist.parse_xml(params[:yaml_string])
6+
result = Plist.parse_xml(params[:yaml_string], marshal: true)
7+
8+
# safe
9+
result = Plist.parse_xml(params[:yaml_string], marshal: false)
10+
end
11+
end
12+
13+

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,10 @@
1717
<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>
1818
<sample src="YAMLUnsafeYamlDeserialization.rb" />
1919
</example>
20+
<references>
21+
<li>
22+
You can read that how unsafe yaml load methods can lead to code executions.
23+
<a href="https://devcraft.io/2021/01/07/universal-deserialisation-gadget-for-ruby-2-x-3-x.html">Universal Deserialisation Gadget for Ruby 2.x-3.x </a>.
24+
</li>
25+
</references>
2026
</qhelp>

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @name Deserialization of user-controlled data by YAML
2+
* @name Unsafe Deserialization of user-controlled data by YAML
33
* @description Deserializing user-controlled data may allow attackers to
44
* execute arbitrary code.
55
* @kind path-problem
@@ -18,10 +18,10 @@ import codeql.ruby.TaintTracking
1818
import DataFlow::PathGraph
1919
import codeql.ruby.security.UnsafeDeserializationCustomizations
2020

21-
abstract class YamlSink extends DataFlow::Node { }
21+
abstract class YamlUnsafeSinks extends DataFlow::Node { }
2222

23-
class YamlUnsafeLoadArgument extends YamlSink {
24-
YamlUnsafeLoadArgument() {
23+
class YamlUnsafeArgument extends YamlUnsafeSinks {
24+
YamlUnsafeArgument() {
2525
this =
2626
API::getTopLevelMember(["YAML", "Psych"])
2727
.getAMethodCall(["unsafe_load_file", "unsafe_load", "load_stream"])
@@ -45,7 +45,7 @@ class YamlUnsafeLoadArgument extends YamlSink {
4545
}
4646

4747
class Configuration extends TaintTracking::Configuration {
48-
Configuration() { this = "UnsafeYAMLDeserialization" }
48+
Configuration() { this = "YamlUnsafeDeserialization" }
4949

5050
override predicate isSource(DataFlow::Node source) {
5151
// to detect CVE-2022-32224, we should uncomment following line instead of current UnsafeDeserialization::Source
@@ -57,7 +57,7 @@ class Configuration extends TaintTracking::Configuration {
5757
// after changing the isSource for detecting CVE-2022-32224
5858
// uncomment the following line only see the CVE sink not other files similar sinks
5959
// sink.getLocation().getFile().toString().matches("%yaml_column%") and
60-
sink instanceof YamlSink
60+
sink instanceof YamlUnsafeSinks
6161
}
6262

6363
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
@@ -84,5 +84,5 @@ class Configuration extends TaintTracking::Configuration {
8484

8585
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
8686
where config.hasFlowPath(source, sink)
87-
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
87+
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
8888
"potentially untrusted source"

ruby/ql/test/query-tests/experimental/Security/CWE-502/YAMLUnsafeYamlDeserialization.expected

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

ruby/ql/test/query-tests/experimental/Security/CWE-502/YAMLUnsafeYamlDeserialization.qlref

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

0 commit comments

Comments
 (0)