mirror of
https://github.com/github/codeql.git
synced 2025-12-27 22:26:31 +01:00
Merge pull request #12893 from alexrford/rb/sqlite3
Ruby: model sqlite3
This commit is contained in:
4
ruby/ql/lib/change-notes/2023-05-03-sqlite3.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-03-sqlite3.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
@@ -31,3 +31,4 @@ private import codeql.ruby.frameworks.Erb
|
||||
private import codeql.ruby.frameworks.Slim
|
||||
private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
|
||||
80
ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll
Normal file
80
ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Provides modeling for `sqlite3`, a library that allows Ruby programs to use the SQLite3 database engine.
|
||||
* Version: 1.6.2
|
||||
* https://github.com/sparklemotion/sqlite3-ruby
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for `sqlite3`, a library that allows Ruby programs to use the SQLite3 database engine.
|
||||
* Version: 1.6.2
|
||||
* https://github.com/sparklemotion/sqlite3-ruby
|
||||
*/
|
||||
module Sqlite3 {
|
||||
/** Gets a method call with a receiver that is a database instance. */
|
||||
private DataFlow::CallNode getADatabaseMethodCall(string methodName) {
|
||||
exists(API::Node dbInstance |
|
||||
dbInstance = API::getTopLevelMember("SQLite3").getMember("Database").getInstance() and
|
||||
(
|
||||
result = dbInstance.getAMethodCall(methodName)
|
||||
or
|
||||
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
|
||||
exists(DataFlow::BlockNode block |
|
||||
result.getMethodName() = methodName and
|
||||
block = dbInstance.getAValueReachableFromSource().(DataFlow::CallNode).getBlock() and
|
||||
block.getParameter(0).flowsTo(result.getReceiver())
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A prepared but unexecuted SQL statement. */
|
||||
private class PreparedStatement extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
PreparedStatement() { this = getADatabaseMethodCall("prepare") }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** Execution of a prepared SQL statement. */
|
||||
private class PreparedStatementExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private PreparedStatement stmt;
|
||||
|
||||
PreparedStatementExecution() {
|
||||
stmt.flowsTo(this.getReceiver()) and
|
||||
this.getMethodName() = ["columns", "execute", "execute!", "get_metadata", "types"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = stmt.getReceiver() }
|
||||
}
|
||||
|
||||
/** Gets the name of a method called against a database that executes an SQL statement. */
|
||||
private string getAnExecutionMethodName() {
|
||||
result =
|
||||
[
|
||||
"execute", "execute2", "execute_batch", "execute_batch2", "get_first_row",
|
||||
"get_first_value", "query"
|
||||
]
|
||||
}
|
||||
|
||||
/** A method call against a database that constructs an SQL query. */
|
||||
private class DatabaseMethodCallSqlConstruction extends SqlConstruction::Range, DataFlow::CallNode
|
||||
{
|
||||
// Database query execution methods also construct an SQL query
|
||||
DatabaseMethodCallSqlConstruction() {
|
||||
this = getADatabaseMethodCall(getAnExecutionMethodName())
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A method call against a database that executes an SQL query. */
|
||||
private class DatabaseMethodCallSqlExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
DatabaseMethodCallSqlExecution() { this = getADatabaseMethodCall(getAnExecutionMethodName()) }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
sqlite3SqlConstruction
|
||||
| sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL |
|
||||
| sqlite3.rb:12:8:12:41 | call to prepare | sqlite3.rb:12:19:12:41 | "select * from numbers" |
|
||||
| sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" |
|
||||
| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" |
|
||||
sqlite3SqlExecution
|
||||
| sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL |
|
||||
| sqlite3.rb:14:1:14:12 | call to execute | sqlite3.rb:12:8:12:9 | db |
|
||||
| sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" |
|
||||
| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" |
|
||||
7
ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.ql
Normal file
7
ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.ql
Normal file
@@ -0,0 +1,7 @@
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
|
||||
query predicate sqlite3SqlConstruction(SqlConstruction c, DataFlow::Node sql) { sql = c.getSql() }
|
||||
|
||||
query predicate sqlite3SqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
|
||||
31
ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb
Normal file
31
ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require 'sqlite3'
|
||||
|
||||
db = SQLite3::Database.new "test.db"
|
||||
|
||||
db.execute <<-SQL
|
||||
create table numbers (
|
||||
name varchar(30),
|
||||
val int
|
||||
);
|
||||
SQL
|
||||
|
||||
stmt = db.prepare "select * from numbers"
|
||||
|
||||
stmt.execute
|
||||
|
||||
SQLite3::Database.new( "data.db" ) do |db|
|
||||
db.execute( "select * from table" ) do |row|
|
||||
p row
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class MyDatabaseWrapper
|
||||
def initialize(filename)
|
||||
@db = SQLite3::Database.new(filename, results_as_hash: true)
|
||||
end
|
||||
|
||||
def select_rows(category)
|
||||
@db.execute("select * from table")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user