Merge pull request #12893 from alexrford/rb/sqlite3

Ruby: model sqlite3
This commit is contained in:
Alex Ford
2023-05-04 14:24:45 +01:00
committed by GitHub
6 changed files with 133 additions and 0 deletions

View 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.

View File

@@ -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

View 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) }
}
}

View File

@@ -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" |

View 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() }

View 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