mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge pull request #13066 from maikypedia/maikypedia/sqli-sink-2
Ruby: Add Sequel as SQL Injection Sink
This commit is contained in:
4
ruby/ql/lib/change-notes/2023-05-07-sequel.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-07-sequel.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.
|
||||
@@ -33,3 +33,4 @@ private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
private import codeql.ruby.frameworks.Pg
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
|
||||
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
module Sequel {
|
||||
/** Flow Summary for `Sequel`. */
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Sequel.connect" }
|
||||
|
||||
override MethodCall getACall() { result = any(SequelConnection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to establish a connection to a database */
|
||||
private class SequelConnection extends DataFlow::CallNode {
|
||||
SequelConnection() {
|
||||
this =
|
||||
API::getTopLevelMember("Sequel").getAMethodCall(["connect", "sqlite", "mysql2", "jdbc"])
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that constructs SQL statements */
|
||||
private class SequelConstruction extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
DataFlow::Node query;
|
||||
|
||||
SequelConstruction() {
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("cast") and query = this.getArgument(1)
|
||||
or
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("function") and
|
||||
query = this.getArgument(0)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a database */
|
||||
private class SequelExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
SequelExecution() {
|
||||
exists(SequelConnection sequelConnection |
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall([
|
||||
"execute", "execute_ddl", "execute_dui", "execute_insert", "run", "<<", "fetch",
|
||||
"fetch_rows", "[]", "log_connection_yield"
|
||||
]) or
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall("dataset")
|
||||
.getAMethodCall([
|
||||
"with_sql", "with_sql_all", "with_sql_delete", "with_sql_each", "with_sql_first",
|
||||
"with_sql_insert", "with_sql_single_value", "with_sql_update"
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -2815,6 +2815,7 @@
|
||||
| file://:0:0:0:0 | parameter position 0 of File.realpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realpath |
|
||||
| file://:0:0:0:0 | parameter position 0 of Hash[] | file://:0:0:0:0 | [summary] read: argument position 0.any element in Hash[] |
|
||||
| file://:0:0:0:0 | parameter position 0 of PG.new() | file://:0:0:0:0 | [summary] to write: return (return) in PG.new() |
|
||||
| file://:0:0:0:0 | parameter position 0 of Sequel.connect | file://:0:0:0:0 | [summary] to write: return (return) in Sequel.connect |
|
||||
| file://:0:0:0:0 | parameter position 0 of String.try_convert | file://:0:0:0:0 | [summary] to write: return (return) in String.try_convert |
|
||||
| file://:0:0:0:0 | parameter position 0 of \| | file://:0:0:0:0 | [summary] read: argument position 0.any element in \| |
|
||||
| file://:0:0:0:0 | parameter position 1.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
|
||||
|
||||
23
ruby/ql/test/library-tests/frameworks/sequel/Sequel.expected
Normal file
23
ruby/ql/test/library-tests/frameworks/sequel/Sequel.expected
Normal file
@@ -0,0 +1,23 @@
|
||||
sequelSqlConstruction
|
||||
| sequel.rb:62:29:62:49 | call to cast | sequel.rb:62:45:62:48 | name |
|
||||
| sequel.rb:65:29:65:49 | call to function | sequel.rb:65:45:65:48 | name |
|
||||
sequelSqlExecution
|
||||
| sequel.rb:9:9:9:60 | ...[...] | sequel.rb:9:14:9:59 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:12:9:12:64 | call to run | sequel.rb:12:18:12:63 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:15:9:17:11 | call to fetch | sequel.rb:15:20:15:65 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:20:9:20:65 | ...[...] | sequel.rb:20:14:20:64 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:23:9:23:65 | call to execute | sequel.rb:23:22:23:65 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:26:9:26:71 | call to execute_ddl | sequel.rb:26:26:26:71 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:29:9:29:71 | call to execute_dui | sequel.rb:29:26:29:71 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:32:9:32:74 | call to execute_insert | sequel.rb:32:29:32:74 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:35:9:35:62 | ... << ... | sequel.rb:35:17:35:62 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:38:9:38:79 | call to fetch_rows | sequel.rb:38:25:38:70 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:41:9:41:81 | call to with_sql_all | sequel.rb:41:35:41:80 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:44:9:44:84 | call to with_sql_delete | sequel.rb:44:38:44:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:47:9:47:90 | call to with_sql_each | sequel.rb:47:36:47:81 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:50:9:50:83 | call to with_sql_first | sequel.rb:50:37:50:82 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:53:9:53:84 | call to with_sql_insert | sequel.rb:53:38:53:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:56:9:56:90 | call to with_sql_single_value | sequel.rb:56:44:56:89 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:59:9:59:84 | call to with_sql_update | sequel.rb:59:38:59:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:62:9:62:20 | ...[...] | sequel.rb:62:14:62:19 | :table |
|
||||
| sequel.rb:65:9:65:20 | ...[...] | sequel.rb:65:14:65:19 | :table |
|
||||
7
ruby/ql/test/library-tests/frameworks/sequel/Sequel.ql
Normal file
7
ruby/ql/test/library-tests/frameworks/sequel/Sequel.ql
Normal file
@@ -0,0 +1,7 @@
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
|
||||
query predicate sequelSqlConstruction(SqlConstruction c, DataFlow::Node sql) { sql = c.getSql() }
|
||||
|
||||
query predicate sequelSqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
|
||||
67
ruby/ql/test/library-tests/frameworks/sequel/sequel.rb
Normal file
67
ruby/ql/test/library-tests/frameworks/sequel/sequel.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
require 'sequel'
|
||||
|
||||
class UsersController < ActionController::Base
|
||||
def sequel_handler(event:, context:)
|
||||
name = params[:name]
|
||||
conn = Sequel.sqlite("sqlite://example.db")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn["SELECT * FROM users WHERE username='#{name}'"]
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.run("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.fetch("SELECT * FROM users WHERE username='#{name}'") do |row|
|
||||
puts row[:name]
|
||||
end
|
||||
|
||||
# GOOD: SQL statement is not constructed from user input
|
||||
conn["SELECT * FROM users WHERE username='im_not_input'"]
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute "SELECT * FROM users WHERE username=#{name}"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_ddl "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_dui "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_insert "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn << "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.fetch_rows("SELECT * FROM users WHERE username='#{name}'"){|row| }
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_all("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_delete("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_each("SELECT * FROM users WHERE username='#{name}'"){|row| }
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_first("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_insert("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_single_value("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_update("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn[:table].select(Sequel.cast(:a, name))
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn[:table].select(Sequel.function(name))
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user