Skip to content

Commit b09772e

Browse files
authored
Merge pull request github#12893 from alexrford/rb/sqlite3
Ruby: model sqlite3
2 parents 0a5647d + e7213e9 commit b09772e

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ private import codeql.ruby.frameworks.Erb
3131
private import codeql.ruby.frameworks.Slim
3232
private import codeql.ruby.frameworks.Sinatra
3333
private import codeql.ruby.frameworks.Twirp
34+
private import codeql.ruby.frameworks.Sqlite3
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Provides modeling for `sqlite3`, a library that allows Ruby programs to use the SQLite3 database engine.
3+
* Version: 1.6.2
4+
* https://github.com/sparklemotion/sqlite3-ruby
5+
*/
6+
7+
private import ruby
8+
private import codeql.ruby.ApiGraphs
9+
private import codeql.ruby.dataflow.FlowSummary
10+
private import codeql.ruby.Concepts
11+
12+
/**
13+
* Provides modeling for `sqlite3`, a library that allows Ruby programs to use the SQLite3 database engine.
14+
* Version: 1.6.2
15+
* https://github.com/sparklemotion/sqlite3-ruby
16+
*/
17+
module Sqlite3 {
18+
/** Gets a method call with a receiver that is a database instance. */
19+
private DataFlow::CallNode getADatabaseMethodCall(string methodName) {
20+
exists(API::Node dbInstance |
21+
dbInstance = API::getTopLevelMember("SQLite3").getMember("Database").getInstance() and
22+
(
23+
result = dbInstance.getAMethodCall(methodName)
24+
or
25+
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
26+
exists(DataFlow::BlockNode block |
27+
result.getMethodName() = methodName and
28+
block = dbInstance.getAValueReachableFromSource().(DataFlow::CallNode).getBlock() and
29+
block.getParameter(0).flowsTo(result.getReceiver())
30+
)
31+
)
32+
)
33+
}
34+
35+
/** A prepared but unexecuted SQL statement. */
36+
private class PreparedStatement extends SqlConstruction::Range, DataFlow::CallNode {
37+
PreparedStatement() { this = getADatabaseMethodCall("prepare") }
38+
39+
override DataFlow::Node getSql() { result = this.getArgument(0) }
40+
}
41+
42+
/** Execution of a prepared SQL statement. */
43+
private class PreparedStatementExecution extends SqlExecution::Range, DataFlow::CallNode {
44+
private PreparedStatement stmt;
45+
46+
PreparedStatementExecution() {
47+
stmt.flowsTo(this.getReceiver()) and
48+
this.getMethodName() = ["columns", "execute", "execute!", "get_metadata", "types"]
49+
}
50+
51+
override DataFlow::Node getSql() { result = stmt.getReceiver() }
52+
}
53+
54+
/** Gets the name of a method called against a database that executes an SQL statement. */
55+
private string getAnExecutionMethodName() {
56+
result =
57+
[
58+
"execute", "execute2", "execute_batch", "execute_batch2", "get_first_row",
59+
"get_first_value", "query"
60+
]
61+
}
62+
63+
/** A method call against a database that constructs an SQL query. */
64+
private class DatabaseMethodCallSqlConstruction extends SqlConstruction::Range, DataFlow::CallNode
65+
{
66+
// Database query execution methods also construct an SQL query
67+
DatabaseMethodCallSqlConstruction() {
68+
this = getADatabaseMethodCall(getAnExecutionMethodName())
69+
}
70+
71+
override DataFlow::Node getSql() { result = this.getArgument(0) }
72+
}
73+
74+
/** A method call against a database that executes an SQL query. */
75+
private class DatabaseMethodCallSqlExecution extends SqlExecution::Range, DataFlow::CallNode {
76+
DatabaseMethodCallSqlExecution() { this = getADatabaseMethodCall(getAnExecutionMethodName()) }
77+
78+
override DataFlow::Node getSql() { result = this.getArgument(0) }
79+
}
80+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sqlite3SqlConstruction
2+
| sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL |
3+
| sqlite3.rb:12:8:12:41 | call to prepare | sqlite3.rb:12:19:12:41 | "select * from numbers" |
4+
| sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" |
5+
| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" |
6+
sqlite3SqlExecution
7+
| sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL |
8+
| sqlite3.rb:14:1:14:12 | call to execute | sqlite3.rb:12:8:12:9 | db |
9+
| sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" |
10+
| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
private import codeql.ruby.DataFlow
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.frameworks.Sqlite3
4+
5+
query predicate sqlite3SqlConstruction(SqlConstruction c, DataFlow::Node sql) { sql = c.getSql() }
6+
7+
query predicate sqlite3SqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'sqlite3'
2+
3+
db = SQLite3::Database.new "test.db"
4+
5+
db.execute <<-SQL
6+
create table numbers (
7+
name varchar(30),
8+
val int
9+
);
10+
SQL
11+
12+
stmt = db.prepare "select * from numbers"
13+
14+
stmt.execute
15+
16+
SQLite3::Database.new( "data.db" ) do |db|
17+
db.execute( "select * from table" ) do |row|
18+
p row
19+
end
20+
end
21+
22+
23+
class MyDatabaseWrapper
24+
def initialize(filename)
25+
@db = SQLite3::Database.new(filename, results_as_hash: true)
26+
end
27+
28+
def select_rows(category)
29+
@db.execute("select * from table")
30+
end
31+
end

0 commit comments

Comments
 (0)