Skip to content

Commit 0e6028a

Browse files
committed
add stdin as source for unsafe-deserialization
1 parent 44213f0 commit 0e6028a

File tree

4 files changed

+64
-5
lines changed

4 files changed

+64
-5
lines changed

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ private import codeql.ruby.DataFlow
1010
private import codeql.ruby.dataflow.RemoteFlowSources
1111
private import codeql.ruby.frameworks.ActiveJob
1212
private import codeql.ruby.frameworks.core.Module
13+
private import codeql.ruby.frameworks.core.Kernel
1314

1415
module UnsafeDeserialization {
1516
/**
1617
* A data flow source for unsafe deserialization vulnerabilities.
1718
*/
18-
abstract class Source extends DataFlow::Node { }
19+
abstract class Source extends DataFlow::Node {
20+
/** Gets a string that describes the source. */
21+
string describe() { result = "user-provided value" }
22+
}
1923

2024
/**
2125
* A data flow sink for unsafe deserialization vulnerabilities.
@@ -37,6 +41,36 @@ module UnsafeDeserialization {
3741
/** A source of remote user input, considered as a flow source for unsafe deserialization. */
3842
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
3943

44+
/** A read of data from `STDIN`/`ARGV`, considered as a flow source for unsafe deserialization. */
45+
class StdInSource extends UnsafeDeserialization::Source {
46+
boolean stdin;
47+
48+
StdInSource() {
49+
this = API::getTopLevelMember(["STDIN", "ARGF"]).getAMethodCall(["gets", "read"]) and
50+
stdin = true
51+
or
52+
// > $stdin == STDIN
53+
// => true
54+
// but $stdin is special in that it is a global variable and not a constant. `API::getTopLevelMember` only gets constants.
55+
exists(DataFlow::Node dollarStdin |
56+
dollarStdin.asExpr().getExpr().(GlobalVariableReadAccess).getVariable().getName() = "$stdin" and
57+
this = dollarStdin.getALocalSource().getAMethodCall(["gets", "read"])
58+
) and
59+
stdin = true
60+
or
61+
// ARGV.
62+
this.asExpr().getExpr().(GlobalVariableReadAccess).getVariable().getName() = "ARGV" and
63+
stdin = false
64+
or
65+
this.(Kernel::KernelMethodCall).getMethodName() = ["gets", "readline", "readlines"] and
66+
stdin = true
67+
}
68+
69+
override string describe() {
70+
if stdin = true then result = "value from stdin" else result = "value from ARGV"
71+
}
72+
}
73+
4074
/**
4175
* An argument in a call to `Marshal.load` or `Marshal.restore`, considered a
4276
* sink for unsafe deserialization.

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@
1111
* external/cwe/cwe-502
1212
*/
1313

14-
import codeql.ruby.AST
15-
import DataFlow::PathGraph
16-
import codeql.ruby.DataFlow
14+
import ruby
1715
import codeql.ruby.security.UnsafeDeserializationQuery
16+
import DataFlow::PathGraph
1817

1918
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
2019
where cfg.hasFlowPath(source, sink)
2120
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
22-
"user-provided value"
21+
source.getNode().(UnsafeDeserialization::Source).describe()

ruby/ql/test/query-tests/security/cwe-502/unsafe-deserialization/UnsafeDeserialization.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ nodes
5353
| UnsafeDeserialization.rb:93:30:93:43 | ...[...] | semmle.label | ...[...] |
5454
| UnsafeDeserialization.rb:99:48:99:53 | call to params : | semmle.label | call to params : |
5555
| UnsafeDeserialization.rb:99:48:99:61 | ...[...] | semmle.label | ...[...] |
56+
| UnsafeDeserialization.rb:104:24:104:34 | call to read | semmle.label | call to read |
57+
| UnsafeDeserialization.rb:107:24:107:33 | call to gets | semmle.label | call to gets |
58+
| UnsafeDeserialization.rb:110:24:110:32 | call to read | semmle.label | call to read |
59+
| UnsafeDeserialization.rb:113:24:113:27 | call to gets | semmle.label | call to gets |
60+
| UnsafeDeserialization.rb:116:24:116:32 | call to readlines | semmle.label | call to readlines |
5661
subpaths
5762
#select
5863
| UnsafeDeserialization.rb:11:27:11:41 | serialized_data | UnsafeDeserialization.rb:10:39:10:44 | call to params : | UnsafeDeserialization.rb:11:27:11:41 | serialized_data | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:10:39:10:44 | call to params | user-provided value |
@@ -67,3 +72,8 @@ subpaths
6772
| UnsafeDeserialization.rb:88:25:88:33 | yaml_data | UnsafeDeserialization.rb:87:17:87:22 | call to params : | UnsafeDeserialization.rb:88:25:88:33 | yaml_data | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:87:17:87:22 | call to params | user-provided value |
6873
| UnsafeDeserialization.rb:93:30:93:43 | ...[...] | UnsafeDeserialization.rb:93:30:93:35 | call to params : | UnsafeDeserialization.rb:93:30:93:43 | ...[...] | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:93:30:93:35 | call to params | user-provided value |
6974
| UnsafeDeserialization.rb:99:48:99:61 | ...[...] | UnsafeDeserialization.rb:99:48:99:53 | call to params : | UnsafeDeserialization.rb:99:48:99:61 | ...[...] | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:99:48:99:53 | call to params | user-provided value |
75+
| UnsafeDeserialization.rb:104:24:104:34 | call to read | UnsafeDeserialization.rb:104:24:104:34 | call to read | UnsafeDeserialization.rb:104:24:104:34 | call to read | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:104:24:104:34 | call to read | value from stdin |
76+
| UnsafeDeserialization.rb:107:24:107:33 | call to gets | UnsafeDeserialization.rb:107:24:107:33 | call to gets | UnsafeDeserialization.rb:107:24:107:33 | call to gets | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:107:24:107:33 | call to gets | value from stdin |
77+
| UnsafeDeserialization.rb:110:24:110:32 | call to read | UnsafeDeserialization.rb:110:24:110:32 | call to read | UnsafeDeserialization.rb:110:24:110:32 | call to read | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:110:24:110:32 | call to read | value from stdin |
78+
| UnsafeDeserialization.rb:113:24:113:27 | call to gets | UnsafeDeserialization.rb:113:24:113:27 | call to gets | UnsafeDeserialization.rb:113:24:113:27 | call to gets | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:113:24:113:27 | call to gets | value from stdin |
79+
| UnsafeDeserialization.rb:116:24:116:32 | call to readlines | UnsafeDeserialization.rb:116:24:116:32 | call to readlines | UnsafeDeserialization.rb:116:24:116:32 | call to readlines | Unsafe deserialization depends on a $@. | UnsafeDeserialization.rb:116:24:116:32 | call to readlines | value from stdin |

ruby/ql/test/query-tests/security/cwe-502/unsafe-deserialization/UnsafeDeserialization.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,20 @@ def route13
9999
klass = ActiveJob::Serializers.deserialize(params[:class])
100100
object = klass.new
101101
end
102+
103+
def stdin
104+
object = YAML.load $stdin.read
105+
106+
# STDIN
107+
object = YAML.load STDIN.gets
108+
109+
# ARGF
110+
object = YAML.load ARGF.read
111+
112+
# Kernel.gets
113+
object = YAML.load gets
114+
115+
# Kernel.readlines
116+
object = YAML.load readlines
117+
end
102118
end

0 commit comments

Comments
 (0)