mirror of
https://github.com/github/codeql.git
synced 2026-04-26 01:05:15 +02:00
Merge branch 'main' into amammad-ruby-YAMLunsafeLoad
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* 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.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
4
ruby/ql/lib/change-notes/2023-05-06-mysql2.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-06-mysql2.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
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.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* 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.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -78,6 +78,19 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
class SqlSanitization extends DataFlow::Node instanceof SqlSanitization::Range { }
|
||||
|
||||
/** Provides a class for modeling new SQL sanitization APIs. */
|
||||
module SqlSanitization {
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes a regular expression.
|
||||
*
|
||||
|
||||
@@ -32,5 +32,7 @@ private import codeql.ruby.frameworks.Slim
|
||||
private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
private import codeql.ruby.frameworks.Mysql2
|
||||
private import codeql.ruby.frameworks.Pg
|
||||
private import codeql.ruby.frameworks.Yaml
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
|
||||
@@ -166,28 +166,21 @@ module Public {
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
private predicate noComponentSpecific(SummaryComponent sc) {
|
||||
not exists(getComponentSpecific(sc))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this component used for flow summaries. */
|
||||
private string getComponent(SummaryComponent sc) {
|
||||
result = getComponentSpecific(sc)
|
||||
or
|
||||
noComponentSpecific(sc) and
|
||||
(
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack used for flow summaries. */
|
||||
|
||||
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
module Mysql2 {
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.new()`.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Mysql2::Client.new()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2Connection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to Mysql2::Client.new() is used to establish a connection to a MySql database. */
|
||||
private class Mysql2Connection extends DataFlow::CallNode {
|
||||
Mysql2Connection() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a MySQL database. */
|
||||
private class Mysql2Execution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
|
||||
Mysql2Execution() {
|
||||
exists(Mysql2Connection mysql2Connection |
|
||||
this = mysql2Connection.getAMethodCall("query") and query = this.getArgument(0)
|
||||
or
|
||||
exists(DataFlow::CallNode prepareCall |
|
||||
prepareCall = mysql2Connection.getAMethodCall("prepare") and
|
||||
query = prepareCall.getArgument(0) and
|
||||
this = prepareCall.getAMethodCall("execute")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Mysql2::Client.escape`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class Mysql2EscapeSanitization extends SqlSanitization::Range {
|
||||
Mysql2EscapeSanitization() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAMethodCall("escape")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.escape()`.
|
||||
*/
|
||||
private class EscapeSummary extends SummarizedCallable {
|
||||
EscapeSummary() { this = "Mysql2::Client.escape()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
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) }
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,26 @@ module Sqlite3 {
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class SQLite3QuoteSanitization extends SqlSanitization {
|
||||
SQLite3QuoteSanitization() {
|
||||
this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `SQLite3::Database.quote()`.
|
||||
*/
|
||||
private class QuoteSummary extends SummarizedCallable {
|
||||
QuoteSummary() { this = "SQLite3::Database.quote()" }
|
||||
|
||||
override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting SQL injection
|
||||
@@ -53,4 +54,6 @@ module SqlInjection {
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier
|
||||
{ }
|
||||
|
||||
private class SqlSanitizationAsSanitizer extends Sanitizer, SqlSanitization { }
|
||||
}
|
||||
|
||||
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id rb/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::TestOutput
|
||||
private import codeql.IDEContextual
|
||||
|
||||
/**
|
||||
* Gets the source file to generate a CFG from.
|
||||
*/
|
||||
external string selectedSourceFile();
|
||||
|
||||
class MyRelevantNode extends RelevantNode {
|
||||
MyRelevantNode() {
|
||||
this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
ruby/ql/src/change-notes/released/0.6.2.md
Normal file
3
ruby/ql/src/change-notes/released/0.6.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-queries
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups:
|
||||
- ruby
|
||||
- queries
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
<example>
|
||||
<p>Consider this regular expression:</p>
|
||||
<sample language="ruby">
|
||||
/^_(__|.)+_$/
|
||||
</sample>
|
||||
/^_(__|.)+_$/</sample>
|
||||
<p>
|
||||
Its sub-expression <code>"(__|.)+?"</code> can match the string
|
||||
<code>"__"</code> either by the first alternative <code>"__"</code> to the
|
||||
@@ -21,8 +20,7 @@
|
||||
repetition:
|
||||
</p>
|
||||
<sample language="ruby">
|
||||
/^_(__|[^_])+_$/
|
||||
</sample>
|
||||
/^_(__|[^_])+_$/</sample>
|
||||
</example>
|
||||
<include src="ReDoSReferences.inc.qhelp"/>
|
||||
</qhelp>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* select sink, source, sink, "$@", source, source.toString()
|
||||
* ```
|
||||
*
|
||||
* To declare expecations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
|
||||
* To declare expectations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
|
||||
* Example of the corresponding test file, e.g. test.rb
|
||||
* ```rb
|
||||
* s = source(1)
|
||||
|
||||
@@ -2814,7 +2814,11 @@
|
||||
| file://:0:0:0:0 | parameter position 0 of File.realdirpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realdirpath |
|
||||
| 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 Mysql2::Client.escape() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.escape() |
|
||||
| file://:0:0:0:0 | parameter position 0 of Mysql2::Client.new() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.new() |
|
||||
| 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 SQLite3::Database.quote() | file://:0:0:0:0 | [summary] to write: return (return) in SQLite3::Database.quote() |
|
||||
| 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 |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
| Mysql2.rb:10:16:10:48 | call to query | Mysql2.rb:10:27:10:47 | "SELECT * FROM users" |
|
||||
| Mysql2.rb:13:16:13:73 | call to query | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." |
|
||||
| Mysql2.rb:17:16:17:76 | call to query | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." |
|
||||
| Mysql2.rb:21:16:21:57 | call to execute | Mysql2.rb:20:31:20:82 | "SELECT * FROM users WHERE id ..." |
|
||||
| Mysql2.rb:25:16:25:60 | call to execute | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." |
|
||||
5
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.ql
Normal file
5
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.Mysql2
|
||||
|
||||
query predicate mysql2SqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
|
||||
30
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb
Normal file
30
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
class UsersController < ActionController::Base
|
||||
def mysql2_handler(event:, context:)
|
||||
name = params[:user_name]
|
||||
|
||||
conn = Mysql2::Client.new(
|
||||
host: "127.0.0.1",
|
||||
username: "root"
|
||||
)
|
||||
# GOOD: SQL statement is not constructed from user input
|
||||
results1 = conn.query("SELECT * FROM users")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
results2 = conn.query("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# GOOD: user input is escaped
|
||||
escaped = Mysql2::Client.escape(name)
|
||||
results3 = conn.query("SELECT * FROM users WHERE username='#{escaped}'")
|
||||
|
||||
# GOOD: user input is escaped
|
||||
statement1 = conn.prepare("SELECT * FROM users WHERE id >= ? AND username = ?")
|
||||
results4 = statement1.execute(1, name, :as => :array)
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
statement2 = conn.prepare("SELECT * FROM users WHERE username='#{name}' AND password = ?")
|
||||
results4 = statement2.execute("password", :as => :array)
|
||||
|
||||
# NOT EXECUTED
|
||||
statement3 = conn.prepare("SELECT * FROM users WHERE username = ?")
|
||||
end
|
||||
end
|
||||
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
|
||||
@@ -40,6 +40,9 @@ edges
|
||||
| PolynomialReDoS.rb:70:12:70:24 | ...[...] | PolynomialReDoS.rb:70:5:70:8 | name |
|
||||
| PolynomialReDoS.rb:73:32:73:35 | name | PolynomialReDoS.rb:76:35:76:39 | input |
|
||||
| PolynomialReDoS.rb:76:35:76:39 | input | PolynomialReDoS.rb:77:5:77:9 | input |
|
||||
| PolynomialReDoS.rb:103:5:103:8 | name | PolynomialReDoS.rb:105:5:105:8 | name |
|
||||
| PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:103:12:103:24 | ...[...] |
|
||||
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | PolynomialReDoS.rb:103:5:103:8 | name |
|
||||
| lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x |
|
||||
| lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x |
|
||||
| lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x |
|
||||
@@ -91,6 +94,10 @@ nodes
|
||||
| PolynomialReDoS.rb:73:32:73:35 | name | semmle.label | name |
|
||||
| PolynomialReDoS.rb:76:35:76:39 | input | semmle.label | input |
|
||||
| PolynomialReDoS.rb:77:5:77:9 | input | semmle.label | input |
|
||||
| PolynomialReDoS.rb:103:5:103:8 | name | semmle.label | name |
|
||||
| PolynomialReDoS.rb:103:12:103:17 | call to params | semmle.label | call to params |
|
||||
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | semmle.label | ...[...] |
|
||||
| PolynomialReDoS.rb:105:5:105:8 | name | semmle.label | name |
|
||||
| lib/index.rb:2:11:2:11 | x | semmle.label | x |
|
||||
| lib/index.rb:4:13:4:13 | x | semmle.label | x |
|
||||
| lib/index.rb:8:13:8:13 | x | semmle.label | x |
|
||||
@@ -121,6 +128,8 @@ subpaths
|
||||
| PolynomialReDoS.rb:62:5:62:22 | call to gsub | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:62:5:62:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:56:31:56:33 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:66:5:66:34 | call to match? | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:66:5:66:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:58:30:58:32 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:77:5:77:22 | call to gsub | PolynomialReDoS.rb:70:12:70:17 | call to params | PolynomialReDoS.rb:77:5:77:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:72:28:72:30 | \\s+ | regular expression | PolynomialReDoS.rb:70:12:70:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:397:100:399 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:405:100:407 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
|
||||
| lib/index.rb:4:13:4:26 | call to match | lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:4:22:4:23 | a+ | regular expression | lib/index.rb:2:11:2:11 | x | library input |
|
||||
| lib/index.rb:9:15:9:28 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:9:24:9:25 | a+ | regular expression | lib/index.rb:8:13:8:13 | x | library input |
|
||||
| lib/index.rb:11:16:11:276 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x | This $@ that depends on a $@ may run slow on strings starting with 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'. | lib/index.rb:11:271:11:272 | .* | regular expression | lib/index.rb:8:13:8:13 | x | library input |
|
||||
|
||||
@@ -76,4 +76,32 @@ class FooController < ActionController::Base
|
||||
def re_compile_indirect_2 (reg, input)
|
||||
input.gsub reg, '' # NOT GOOD
|
||||
end
|
||||
|
||||
# See https://github.com/dependabot/dependabot-core/blob/37dc1767fde9b7184020763f4d0c1434f93d11d6/python/lib/dependabot/python/requirement_parser.rb#L6-L25
|
||||
NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
|
||||
EXTRA = /[a-zA-Z0-9\-_\.]+/
|
||||
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
|
||||
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
|
||||
|
||||
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
|
||||
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
|
||||
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
|
||||
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
|
||||
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
|
||||
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
|
||||
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
|
||||
ENV_VAR =
|
||||
/python_version|python_full_version|os_name|sys_platform|
|
||||
platform_release|platform_system|platform_version|platform_machine|
|
||||
platform_python_implementation|implementation_name|
|
||||
implementation_version/
|
||||
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
|
||||
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
|
||||
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
|
||||
|
||||
def use_marker_expr
|
||||
name = params[:name] # source
|
||||
|
||||
name =~ MARKER_EXPR
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user