Skip to content

Commit e1dcc20

Browse files
committed
Ruby: Model methods in Rails::Generators::Actions
These methods are sinks for command injection.
1 parent 20ff4c4 commit e1dcc20

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActiveSupport
1212
private import codeql.ruby.frameworks.Archive
1313
private import codeql.ruby.frameworks.GraphQL
1414
private import codeql.ruby.frameworks.Rails
15+
private import codeql.ruby.frameworks.Railties
1516
private import codeql.ruby.frameworks.Stdlib
1617
private import codeql.ruby.frameworks.Files
1718
private import codeql.ruby.frameworks.HttpClients
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Modeling for `railties`, which is a gem containing various internals and utilities for the Rails framework.
3+
* https://rubygems.org/gems/railties
4+
*/
5+
6+
private import ruby
7+
private import codeql.ruby.Concepts
8+
private import codeql.ruby.ApiGraphs
9+
private import codeql.ruby.DataFlow
10+
private import codeql.ruby.ast.internal.Module
11+
12+
/**
13+
* Modeling for `railties`.
14+
*/
15+
module Railties {
16+
/**
17+
* A class which `include`s `Rails::Generators::Actions`.
18+
*/
19+
private class GeneratorsActionsContext extends ClassDeclaration {
20+
GeneratorsActionsContext() {
21+
exists(IncludeOrPrependCall i |
22+
i.getEnclosingModule() = this and
23+
i.getArgument(0) =
24+
API::getTopLevelMember("Rails")
25+
.getMember("Generators")
26+
.getMember("Actions")
27+
.getAUse()
28+
.asExpr()
29+
.getExpr()
30+
)
31+
}
32+
}
33+
34+
/**
35+
* A call to `Rails::Generators::Actions#execute_command`.
36+
* This method concatenates its first and second arguments and executes the result as a shell command.
37+
*/
38+
private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode {
39+
ExecuteCommandCall() {
40+
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
41+
this.getMethodName() = "execute_command"
42+
}
43+
44+
override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) }
45+
46+
override predicate isShellInterpreted(DataFlow::Node arg) { any() }
47+
}
48+
49+
/**
50+
* A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`.
51+
*/
52+
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode {
53+
ExecuteCommandWrapperCall() {
54+
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
55+
this.getMethodName() = ["rake", "rails_command", "git"]
56+
}
57+
58+
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
59+
60+
override predicate isShellInterpreted(DataFlow::Node arg) { any() }
61+
}
62+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| Railties.rb:5:5:5:34 | call to execute_command |
2+
| Railties.rb:6:5:6:37 | call to execute_command |
3+
| Railties.rb:8:5:8:16 | call to rake |
4+
| Railties.rb:10:5:10:27 | call to rails_command |
5+
| Railties.rb:12:5:12:17 | call to git |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
private import ruby
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.frameworks.Railties
4+
5+
query predicate systemCommandExecutions(SystemCommandExecution e) { any() }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Foo
2+
include Rails::Generators::Actions
3+
4+
def foo
5+
execute_command(:rake, "test")
6+
execute_command(:rails, "server")
7+
8+
rake("test")
9+
10+
rails_command("server")
11+
12+
git("status")
13+
end
14+
end

0 commit comments

Comments
 (0)