Skip to content

Commit 486a5ac

Browse files
am0o0hmac
authored andcommitted
v1
1 parent 5bc844c commit 486a5ac

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 yaml document through the Psych (YAML) 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+
After Psych(YAML) 4.0.0, the load method is same as safe_load method.
12+
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.
13+
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.
14+
</p>
15+
</recommendation>
16+
<example>
17+
<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>
18+
<sample src="YAMLUnsafeYamlDeserialization.rb" />
19+
</example>
20+
</qhelp>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @name Deserialization of user-controlled data by YAML
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/YAML-unsafe-deserialization
10+
* @tags security
11+
* experimental
12+
* external/cwe/cwe-502
13+
*/
14+
15+
import codeql.ruby.AST
16+
import codeql.ruby.ApiGraphs
17+
import codeql.ruby.DataFlow
18+
import codeql.ruby.dataflow.RemoteFlowSources
19+
import codeql.ruby.TaintTracking
20+
import DataFlow::PathGraph
21+
22+
abstract class YAMLSink extends DataFlow::Node { }
23+
24+
class YamlunsafeLoadArgument extends YAMLSink {
25+
YamlunsafeLoadArgument() {
26+
this =
27+
API::getTopLevelMember(["YAML", "Psych"])
28+
.getAMethodCall(["unsafe_load_file", "unsafe_load", "load_stream"])
29+
.getArgument(0)
30+
or
31+
this =
32+
API::getTopLevelMember(["YAML", "Psych"])
33+
.getAMethodCall(["unsafe_load", "load_stream"])
34+
.getKeywordArgument("yaml")
35+
or
36+
this =
37+
API::getTopLevelMember(["YAML", "Psych"])
38+
.getAMethodCall("unsafe_load_file")
39+
.getKeywordArgument("filename")
40+
}
41+
}
42+
43+
class Configuration extends TaintTracking::Configuration {
44+
Configuration() { this = "UnsafeDeserialization" }
45+
46+
override predicate isSource(DataFlow::Node source) {
47+
// for detecting The CVE we should uncomment following line instead of current RemoteFlowSource
48+
source instanceof DataFlow::LocalSourceNode
49+
// source instanceof RemoteFlowSource
50+
}
51+
52+
override predicate isSink(DataFlow::Node sink) {
53+
// for detecting The CVE we should uncomment following line
54+
// sink.getLocation().getFile().toString().matches("%yaml_column%") and
55+
sink instanceof YAMLSink or
56+
sink =
57+
API::getTopLevelMember(["YAML", "Psych"])
58+
.getAMethodCall(["parse", "parse_stream", "parse_file"])
59+
.getAMethodCall("to_ruby")
60+
}
61+
62+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
63+
exists(DataFlow::CallNode yaml_parser_methods |
64+
yaml_parser_methods =
65+
API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall(["parse", "parse_stream"]) and
66+
(
67+
nodeFrom = yaml_parser_methods.getArgument(0) or
68+
nodeFrom = yaml_parser_methods.getKeywordArgument("yaml")
69+
) and
70+
nodeTo = yaml_parser_methods.getAMethodCall("to_ruby")
71+
)
72+
or
73+
exists(DataFlow::CallNode yaml_parser_methods |
74+
yaml_parser_methods = API::getTopLevelMember(["YAML", "Psych"]).getAMethodCall("parse_file") and
75+
(
76+
nodeFrom = yaml_parser_methods.getArgument(0) or
77+
nodeFrom = yaml_parser_methods.getKeywordArgument("filename")
78+
) and
79+
nodeTo = yaml_parser_methods.getAMethodCall("to_ruby")
80+
)
81+
}
82+
}
83+
84+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
85+
where config.hasFlowPath(source, sink)
86+
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
87+
"potentially untrusted source"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'yaml'
2+
class UsersController < ActionController::Base
3+
def example
4+
# safe
5+
Psych.load(params[:yaml_string])
6+
Psych.load_file(params[:yaml_file])
7+
Psych.parse_stream(params[:yaml_string])
8+
Psych.parse(params[:yaml_string])
9+
Psych.parse_file(params[:yaml_file])
10+
# unsafe
11+
Psych.unsafe_load(params[:yaml_string])
12+
Psych.unsafe_load_file(params[:yaml_file])
13+
Psych.load_stream(params[:yaml_string])
14+
Psych.parse_stream(params[:yaml_string]).to_ruby
15+
Psych.parse(params[:yaml_string]).to_ruby
16+
Psych.parse_file(params[:yaml_file]).to_ruby
17+
18+
end
19+
end
20+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
edges
2+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:28 | call to params : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] |
3+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:33 | call to params : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] |
4+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:28 | call to params : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] |
5+
| YAMLUnsafeYamlDeserialization.rb:14:24:14:29 | call to params : | YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : |
6+
| YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby |
7+
| YAMLUnsafeYamlDeserialization.rb:15:17:15:22 | call to params : | YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : |
8+
| YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby |
9+
| YAMLUnsafeYamlDeserialization.rb:16:22:16:27 | call to params : | YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : |
10+
| YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby |
11+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] |
12+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] |
13+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] |
14+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : |
15+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : |
16+
| file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : |
17+
| file://:0:0:0:0 | parameter self of [](:yaml_file) : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] |
18+
| file://:0:0:0:0 | parameter self of [](:yaml_file) : | YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : |
19+
| file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] |
20+
| file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] |
21+
| file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : |
22+
| file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : |
23+
nodes
24+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:28 | call to params : | semmle.label | call to params : |
25+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | semmle.label | ...[...] |
26+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:33 | call to params : | semmle.label | call to params : |
27+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | semmle.label | ...[...] |
28+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:28 | call to params : | semmle.label | call to params : |
29+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | semmle.label | ...[...] |
30+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | semmle.label | call to to_ruby |
31+
| YAMLUnsafeYamlDeserialization.rb:14:24:14:29 | call to params : | semmle.label | call to params : |
32+
| YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : | semmle.label | ...[...] : |
33+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | semmle.label | call to to_ruby |
34+
| YAMLUnsafeYamlDeserialization.rb:15:17:15:22 | call to params : | semmle.label | call to params : |
35+
| YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : | semmle.label | ...[...] : |
36+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | semmle.label | call to to_ruby |
37+
| YAMLUnsafeYamlDeserialization.rb:16:22:16:27 | call to params : | semmle.label | call to params : |
38+
| YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : | semmle.label | ...[...] : |
39+
| file://:0:0:0:0 | parameter self of [] : | semmle.label | parameter self of [] : |
40+
| file://:0:0:0:0 | parameter self of [](:yaml_file) : | semmle.label | parameter self of [](:yaml_file) : |
41+
| file://:0:0:0:0 | parameter self of [](:yaml_string) : | semmle.label | parameter self of [](:yaml_string) : |
42+
subpaths
43+
#select
44+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:11:23:11:28 | call to params : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:11:23:11:28 | call to params | potentially untrusted source |
45+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | potentially untrusted source |
46+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
47+
| YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:11:23:11:42 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_string) | potentially untrusted source |
48+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | YAMLUnsafeYamlDeserialization.rb:12:28:12:33 | call to params : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:12:28:12:33 | call to params | potentially untrusted source |
49+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | potentially untrusted source |
50+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
51+
| YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | file://:0:0:0:0 | parameter self of [](:yaml_file) : | YAMLUnsafeYamlDeserialization.rb:12:28:12:45 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_file) | potentially untrusted source |
52+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:13:23:13:28 | call to params : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:13:23:13:28 | call to params | potentially untrusted source |
53+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | potentially untrusted source |
54+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
55+
| YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:13:23:13:42 | ...[...] | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_string) | potentially untrusted source |
56+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | potentially untrusted source |
57+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:14:24:14:29 | call to params : | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:14:24:14:29 | call to params | potentially untrusted source |
58+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:14:24:14:43 | ...[...] | potentially untrusted source |
59+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
60+
| YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:14:5:14:52 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_string) | potentially untrusted source |
61+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | potentially untrusted source |
62+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:15:17:15:22 | call to params : | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:15:17:15:22 | call to params | potentially untrusted source |
63+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:15:17:15:36 | ...[...] | potentially untrusted source |
64+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
65+
| YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | file://:0:0:0:0 | parameter self of [](:yaml_string) : | YAMLUnsafeYamlDeserialization.rb:15:5:15:45 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_string) | potentially untrusted source |
66+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | potentially untrusted source |
67+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:16:22:16:27 | call to params : | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:16:22:16:27 | call to params | potentially untrusted source |
68+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] : | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | This file extraction depends on a $@. | YAMLUnsafeYamlDeserialization.rb:16:22:16:39 | ...[...] | potentially untrusted source |
69+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | file://:0:0:0:0 | parameter self of [] : | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [] | potentially untrusted source |
70+
| YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | file://:0:0:0:0 | parameter self of [](:yaml_file) : | YAMLUnsafeYamlDeserialization.rb:16:5:16:48 | call to to_ruby | This file extraction depends on a $@. | file://:0:0:0:0 | parameter self of [](:yaml_file) | potentially untrusted source |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/CWE-502/YAMLUnsafeYamlDeserialization.ql
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'yaml'
2+
class UsersController < ActionController::Base
3+
def example
4+
# safe
5+
Psych.load(params[:yaml_string])
6+
Psych.load_file(params[:yaml_file])
7+
Psych.parse_stream(params[:yaml_string])
8+
Psych.parse(params[:yaml_string])
9+
Psych.parse_file(params[:yaml_file])
10+
# unsafe
11+
Psych.unsafe_load(params[:yaml_string])
12+
Psych.unsafe_load_file(params[:yaml_file])
13+
Psych.load_stream(params[:yaml_string])
14+
Psych.parse_stream(params[:yaml_string]).to_ruby
15+
Psych.parse(params[:yaml_string]).to_ruby
16+
Psych.parse_file(params[:yaml_file]).to_ruby
17+
18+
end
19+
end
20+

0 commit comments

Comments
 (0)