mirror of
https://github.com/github/codeql.git
synced 2025-12-23 20:26:32 +01:00
116
rust/ql/lib/codeql/rust/Concepts.qll
Normal file
116
rust/ql/lib/codeql/rust/Concepts.qll
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Provides abstract classes representing generic concepts such as file system
|
||||||
|
* access or system command execution, for which individual framework libraries
|
||||||
|
* provide concrete subclasses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import codeql.rust.dataflow.DataFlow
|
||||||
|
private import codeql.threatmodels.ThreatModels
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data flow source for a specific threat-model.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `ThreatModelSource::Range` instead.
|
||||||
|
*/
|
||||||
|
final class ThreatModelSource = ThreatModelSource::Range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a class for modeling new sources for specific threat-models.
|
||||||
|
*/
|
||||||
|
module ThreatModelSource {
|
||||||
|
/**
|
||||||
|
* A data flow source, for a specific threat-model.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/**
|
||||||
|
* Gets a string that represents the source kind with respect to threat modeling.
|
||||||
|
*
|
||||||
|
* See
|
||||||
|
* - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst
|
||||||
|
* - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml
|
||||||
|
*/
|
||||||
|
abstract string getThreatModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a string that describes the type of this threat-model source.
|
||||||
|
*/
|
||||||
|
abstract string getSourceType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data flow source that is enabled in the current threat model configuration.
|
||||||
|
*/
|
||||||
|
class ActiveThreatModelSource extends ThreatModelSource {
|
||||||
|
ActiveThreatModelSource() { currentThreatModel(this.getThreatModel()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-flow node that constructs a SQL statement.
|
||||||
|
*
|
||||||
|
* Often, it is worthy of an alert if a SQL statement is constructed such that
|
||||||
|
* executing it would be a security risk.
|
||||||
|
*
|
||||||
|
* If it is important that the SQL statement is executed, use `SqlExecution`.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `SqlConstruction::Range` instead.
|
||||||
|
*/
|
||||||
|
final class SqlConstruction = SqlConstruction::Range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a class for modeling new SQL execution APIs.
|
||||||
|
*/
|
||||||
|
module SqlConstruction {
|
||||||
|
/**
|
||||||
|
* A data-flow node that constructs a SQL statement.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/**
|
||||||
|
* Gets the argument that specifies the SQL statements to be constructed.
|
||||||
|
*/
|
||||||
|
abstract DataFlow::Node getSql();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-flow node that executes SQL statements.
|
||||||
|
*
|
||||||
|
* If the context of interest is such that merely constructing a SQL statement
|
||||||
|
* would be valuable to report, consider using `SqlConstruction`.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `SqlExecution::Range` instead.
|
||||||
|
*/
|
||||||
|
final class SqlExecution = SqlExecution::Range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a class for modeling new SQL execution APIs.
|
||||||
|
*/
|
||||||
|
module SqlExecution {
|
||||||
|
/**
|
||||||
|
* A data-flow node that executes SQL statements.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/**
|
||||||
|
* Gets the argument that specifies the SQL statements to be executed.
|
||||||
|
*/
|
||||||
|
abstract DataFlow::Node getSql();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-flow node that performs SQL sanitization.
|
||||||
|
*/
|
||||||
|
final class SqlSanitization = 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 { }
|
||||||
|
}
|
||||||
50
rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll
Normal file
50
rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Provides classes and predicates for reasoning about database
|
||||||
|
* queries built from user-controlled sources (that is, SQL injection
|
||||||
|
* vulnerabilities).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import rust
|
||||||
|
private import codeql.rust.dataflow.DataFlow
|
||||||
|
private import codeql.rust.Concepts
|
||||||
|
private import codeql.util.Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides default sources, sinks and barriers for detecting SQL injection
|
||||||
|
* vulnerabilities, as well as extension points for adding your own.
|
||||||
|
*/
|
||||||
|
module SqlInjection {
|
||||||
|
/**
|
||||||
|
* A data flow source for SQL injection vulnerabilities.
|
||||||
|
*/
|
||||||
|
abstract class Source extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data flow sink for SQL injection vulnerabilities.
|
||||||
|
*/
|
||||||
|
abstract class Sink extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A barrier for SQL injection vulnerabilities.
|
||||||
|
*/
|
||||||
|
abstract class Barrier extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An active threat-model source, considered as a flow source.
|
||||||
|
*/
|
||||||
|
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flow sink that is the statement of an SQL construction.
|
||||||
|
*/
|
||||||
|
class SqlConstructionAsSink extends Sink {
|
||||||
|
SqlConstructionAsSink() { this = any(SqlConstruction c).getSql() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flow sink that is the statement of an SQL execution.
|
||||||
|
*/
|
||||||
|
class SqlExecutionAsSink extends Sink {
|
||||||
|
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ dependencies:
|
|||||||
codeql/controlflow: ${workspace}
|
codeql/controlflow: ${workspace}
|
||||||
codeql/dataflow: ${workspace}
|
codeql/dataflow: ${workspace}
|
||||||
codeql/regex: ${workspace}
|
codeql/regex: ${workspace}
|
||||||
|
codeql/threat-models: ${workspace}
|
||||||
codeql/mad: ${workspace}
|
codeql/mad: ${workspace}
|
||||||
codeql/ssa: ${workspace}
|
codeql/ssa: ${workspace}
|
||||||
codeql/tutorial: ${workspace}
|
codeql/tutorial: ${workspace}
|
||||||
|
|||||||
39
rust/ql/src/queries/security/CWE-089/SqlInjection.qhelp
Normal file
39
rust/ql/src/queries/security/CWE-089/SqlInjection.qhelp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC
|
||||||
|
"-//Semmle//qhelp//EN"
|
||||||
|
"qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
<overview>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If a database query (such as an SQL query) is built from user-provided data without sufficient sanitization, a user may be able to run malicious database queries. An attacker can craft the part of the query they control to change the overall meaning of the query.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</overview>
|
||||||
|
<recommendation>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Most database connector libraries offer a way to safely embed untrusted data into a query using query parameters or prepared statements. You should use these features to build queries, rather than string concatenation or similar methods. You can also escape (sanitize) user-controlled strings so that they can be included directly in an SQL command. A library function should be used for escaping, because this approach is only safe if the escaping function is robust against all possible inputs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</recommendation>
|
||||||
|
<example>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In the following examples, an SQL query is prepared using string formatting to directly include a user-controlled value <code>remote_controlled_string</code>. An attacker could craft <code>remote_controlled_string</code> to change the overall meaning of the SQL query.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<sample src="SqlInjectionBad.rs" />
|
||||||
|
|
||||||
|
<p>A better way to do this is with a prepared statement, binding <code>remote_controlled_string</code> to a parameter of that statement. An attacker who controls <code>remote_controlled_string</code> now cannot change the overall meaning of the query.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<sample src="SqlInjectionGood.rs" />
|
||||||
|
|
||||||
|
</example>
|
||||||
|
<references>
|
||||||
|
|
||||||
|
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.</li>
|
||||||
|
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html">SQL Injection Prevention Cheat Sheet</a>.</li>
|
||||||
|
|
||||||
|
</references>
|
||||||
|
</qhelp>
|
||||||
35
rust/ql/src/queries/security/CWE-089/SqlInjection.ql
Normal file
35
rust/ql/src/queries/security/CWE-089/SqlInjection.ql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @name Database query built from user-controlled sources
|
||||||
|
* @description Building a database query from user-controlled sources is vulnerable to insertion of malicious code by attackers.
|
||||||
|
* @kind path-problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @security-severity 8.8
|
||||||
|
* @precision high
|
||||||
|
* @id rust/sql-injection
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-089
|
||||||
|
*/
|
||||||
|
|
||||||
|
import rust
|
||||||
|
import codeql.rust.dataflow.DataFlow
|
||||||
|
import codeql.rust.dataflow.TaintTracking
|
||||||
|
import codeql.rust.security.SqlInjectionExtensions
|
||||||
|
import SqlInjectionFlow::PathGraph
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint configuration for tainted data that reaches a SQL sink.
|
||||||
|
*/
|
||||||
|
module SqlInjectionConfig implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node node) { node instanceof SqlInjection::Source }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node node) { node instanceof SqlInjection::Sink }
|
||||||
|
|
||||||
|
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof SqlInjection::Barrier }
|
||||||
|
}
|
||||||
|
|
||||||
|
module SqlInjectionFlow = TaintTracking::Global<SqlInjectionConfig>;
|
||||||
|
|
||||||
|
from SqlInjectionFlow::PathNode sourceNode, SqlInjectionFlow::PathNode sinkNode
|
||||||
|
where SqlInjectionFlow::flowPath(sourceNode, sinkNode)
|
||||||
|
select sinkNode.getNode(), sourceNode, sinkNode, "This query depends on a $@.",
|
||||||
|
sourceNode.getNode(), "user-provided value"
|
||||||
7
rust/ql/src/queries/security/CWE-089/SqlInjectionBad.rs
Normal file
7
rust/ql/src/queries/security/CWE-089/SqlInjectionBad.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// with SQLx
|
||||||
|
|
||||||
|
let unsafe_query = format!("SELECT * FROM people WHERE firstname='{remote_controlled_string}'");
|
||||||
|
|
||||||
|
let _ = conn.execute(unsafe_query.as_str()).await?; // BAD (arbitrary SQL injection is possible)
|
||||||
|
|
||||||
|
let _ = sqlx::query(unsafe_query.as_str()).fetch_all(&mut conn).await?; // BAD (arbitrary SQL injection is possible)
|
||||||
5
rust/ql/src/queries/security/CWE-089/SqlInjectionGood.rs
Normal file
5
rust/ql/src/queries/security/CWE-089/SqlInjectionGood.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// with SQLx
|
||||||
|
|
||||||
|
let prepared_query = "SELECT * FROM people WHERE firstname=?";
|
||||||
|
|
||||||
|
let _ = sqlx::query(prepared_query_1).bind(&remote_controlled_string).fetch_all(&mut conn).await?; // GOOD (prepared statement with bound parameter)
|
||||||
2
rust/ql/test/query-tests/security/CWE-089/.gitignore
vendored
Normal file
2
rust/ql/test/query-tests/security/CWE-089/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# sqlite database
|
||||||
|
*.db*
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT * FROM people WHERE firstname=$1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firstname",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lastname",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "c996a36820ff0b98021fa553b09b6da5ed65c28f666a68c4d73a1918f0eaa6f6"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
uniqueEnclosingCallable
|
||||||
|
| sqlx.rs:52:72:52:84 | remote_number | Node should have one enclosing callable but has 0. |
|
||||||
|
| sqlx.rs:56:74:56:86 | remote_string | Node should have one enclosing callable but has 0. |
|
||||||
|
| sqlx.rs:199:32:199:44 | enable_remote | Node should have one enclosing callable but has 0. |
|
||||||
|
uniqueNodeToString
|
||||||
|
| sqlx.rs:154:13:154:81 | (no string representation) | Node should have one toString but has 0. |
|
||||||
|
| sqlx.rs:156:17:156:86 | (no string representation) | Node should have one toString but has 0. |
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#select
|
||||||
|
edges
|
||||||
|
nodes
|
||||||
|
subpaths
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
query: queries/security/CWE-089/SqlInjection.ql
|
||||||
|
postprocess: utils/InlineExpectationsTestQuery.ql
|
||||||
15
rust/ql/test/query-tests/security/CWE-089/cargo.toml.manual
Normal file
15
rust/ql/test/query-tests/security/CWE-089/cargo.toml.manual
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[workspace]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "CWE-089-Test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.12.9", features = ["blocking"] }
|
||||||
|
sqlx = { version = "0.8", features = ["mysql", "sqlite", "postgres", "runtime-async-std", "tls-native-tls"] }
|
||||||
|
futures = { version = "0.3" }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "sqlx"
|
||||||
|
path = "./sqlx.rs"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS people
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
firstname TEXT NOT NULL,
|
||||||
|
lastname TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO people
|
||||||
|
VALUES (1, "Alice", "Adams");
|
||||||
|
|
||||||
|
INSERT INTO people
|
||||||
|
VALUES (2, "Bob", "Becket");
|
||||||
5
rust/ql/test/query-tests/security/CWE-089/options.yml
Normal file
5
rust/ql/test/query-tests/security/CWE-089/options.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
qltest_cargo_check: true
|
||||||
|
qltest_dependencies:
|
||||||
|
- reqwest = { version = "0.12.9", features = ["blocking"] }
|
||||||
|
- sqlx = { version = "0.8", features = ["mysql", "sqlite", "postgres", "runtime-async-std", "tls-native-tls"] }
|
||||||
|
- futures = { version = "0.3" }
|
||||||
218
rust/ql/test/query-tests/security/CWE-089/sqlx.rs
Normal file
218
rust/ql/test/query-tests/security/CWE-089/sqlx.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
use sqlx::Connection;
|
||||||
|
use sqlx::Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test is designed to be "run" in two ways:
|
||||||
|
* - you can extract and analyze the code here using the CodeQL test runner in the usual way,
|
||||||
|
* verifying the that various vulnerabilities are detected.
|
||||||
|
* - you can compile and run the code using `cargo`, verifying that it really is a complete
|
||||||
|
* program that compiles, runs and executes SQL commands (the sqlite ones, at least).
|
||||||
|
*
|
||||||
|
* To do the latter:
|
||||||
|
*
|
||||||
|
* Install `sqlx`:
|
||||||
|
* ```
|
||||||
|
* cargo install sqlx-cli
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Create the database:
|
||||||
|
* ```
|
||||||
|
* export DATABASE_URL="sqlite:sqlite_database.db"
|
||||||
|
* sqlx db create
|
||||||
|
* sqlx migrate run
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Build and run with the provided `cargo.toml.manual`:
|
||||||
|
* ```
|
||||||
|
* cp cargo.toml.manual cargo.toml
|
||||||
|
* cargo run
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* You can also rebuild the sqlx 'query cache' in the `.sqlx` subdirectory
|
||||||
|
* with:
|
||||||
|
* ```
|
||||||
|
* cargo sqlx prepare
|
||||||
|
* ```
|
||||||
|
* This allows the code (in particular the `prepare!` macro) to be built
|
||||||
|
* in the test without setting `DATABASE_URL` first.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async fn test_sqlx_mysql(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
|
||||||
|
// connect through a MySql connection pool
|
||||||
|
let pool = sqlx::mysql::MySqlPool::connect(url).await?;
|
||||||
|
let mut conn = pool.acquire().await?;
|
||||||
|
|
||||||
|
// construct queries (with extra variants)
|
||||||
|
let const_string = String::from("Alice");
|
||||||
|
let arg_string = std::env::args().nth(1).unwrap_or(String::from("Alice")); // $ MISSING Source=args1
|
||||||
|
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote1
|
||||||
|
let remote_number = remote_string.parse::<i32>().unwrap_or(0);
|
||||||
|
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='Alice'");
|
||||||
|
let safe_query_2 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
|
||||||
|
let safe_query_3 = format!("SELECT * FROM people WHERE firstname='{remote_number}'");
|
||||||
|
let unsafe_query_1 = &arg_string;
|
||||||
|
let unsafe_query_2 = &remote_string;
|
||||||
|
let unsafe_query_3 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
|
||||||
|
let unsafe_query_4 = format!("SELECT * FROM people WHERE firstname='{remote_string}'");
|
||||||
|
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
|
||||||
|
|
||||||
|
// direct execution
|
||||||
|
let _ = conn.execute(safe_query_1.as_str()).await?;
|
||||||
|
let _ = conn.execute(safe_query_2.as_str()).await?;
|
||||||
|
let _ = conn.execute(safe_query_3.as_str()).await?;
|
||||||
|
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=args1
|
||||||
|
if enable_remote {
|
||||||
|
let _ = conn.execute(unsafe_query_2.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
let _ = conn.execute(unsafe_query_3.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
let _ = conn.execute(unsafe_query_4.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepared queries
|
||||||
|
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(safe_query_2.as_str()).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(safe_query_3.as_str()).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=args1
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(unsafe_query_2.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
let _ = sqlx::query(unsafe_query_3.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
|
||||||
|
}
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(const_string).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(arg_string).execute(&pool).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_string).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_number).execute(&pool).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_sqlx_sqlite(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
|
||||||
|
// connect through Sqlite, no connection pool
|
||||||
|
let mut conn = sqlx::sqlite::SqliteConnection::connect(url).await?;
|
||||||
|
|
||||||
|
// construct queries
|
||||||
|
let const_string = String::from("Alice");
|
||||||
|
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote2
|
||||||
|
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
|
||||||
|
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
|
||||||
|
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
|
||||||
|
|
||||||
|
// direct execution (with extra variants)
|
||||||
|
let _ = conn.execute(safe_query_1.as_str()).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let _ = sqlx::raw_sql(safe_query_1.as_str()).execute(&mut conn).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::raw_sql(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepared queries (with extra variants)
|
||||||
|
let _ = sqlx::query(safe_query_1.as_str()).execute(&mut conn).await?;
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&mut conn).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&mut conn).await?;
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let _ = sqlx::query(safe_query_1.as_str()).fetch(&mut conn);
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch(&mut conn);
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(unsafe_query_1.as_str()).fetch(&mut conn); // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch(&mut conn);
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let row1: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_one(&mut conn).await?;
|
||||||
|
println!(" row1 = {:?}", row1);
|
||||||
|
let row2: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_one(&mut conn).await?;
|
||||||
|
println!(" row2 = {:?}", row2);
|
||||||
|
if enable_remote {
|
||||||
|
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_one(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_one(&mut conn).await?;
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let row3: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data");
|
||||||
|
println!(" row3 = {:?}", row3);
|
||||||
|
let row4: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_optional(&mut conn).await?.expect("no data");
|
||||||
|
println!(" row4 = {:?}", row4);
|
||||||
|
if enable_remote {
|
||||||
|
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data"); // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_optional(&mut conn).await?.expect("no data");
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let _ = sqlx::query(safe_query_1.as_str()).fetch_all(&mut conn).await?;
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch_all(&mut conn).await?;
|
||||||
|
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&const_string).fetch_all(&mut conn).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(unsafe_query_1.as_str()).fetch_all(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch_all(&mut conn).await?;
|
||||||
|
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&remote_string).fetch_all(&mut conn).await?;
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", const_string).fetch_all(&mut conn).await?; // (only takes string literals, so can't be vulnerable)
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", remote_string).fetch_all(&mut conn).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_sqlx_postgres(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
|
||||||
|
// connect through a PostGres connection pool
|
||||||
|
let pool = sqlx::postgres::PgPool::connect(url).await?;
|
||||||
|
let mut conn = pool.acquire().await?;
|
||||||
|
|
||||||
|
// construct queries
|
||||||
|
let const_string = String::from("Alice");
|
||||||
|
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote3
|
||||||
|
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
|
||||||
|
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
|
||||||
|
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=$1"); // (prepared arguments are safe)
|
||||||
|
|
||||||
|
// direct execution
|
||||||
|
let _ = conn.execute(safe_query_1.as_str()).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote3
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepared queries
|
||||||
|
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&pool).await?;
|
||||||
|
if enable_remote {
|
||||||
|
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote3
|
||||||
|
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&pool).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("--- CWE-089 sqlx.rs test ---");
|
||||||
|
|
||||||
|
// we don't *actually* use data from a remote source unless we're explicitly told to at the
|
||||||
|
// command line; that's because this test is designed to be runnable, and we don't really
|
||||||
|
// want to expose the test database to potential SQL injection from http://example.com/ -
|
||||||
|
// no matter how unlikely, local and compartmentalized that may seem.
|
||||||
|
let enable_remote = std::env::args().nth(1) == Some(String::from("ENABLE_REMOTE"));
|
||||||
|
println!("enable_remote = {enable_remote}");
|
||||||
|
|
||||||
|
println!("test_sqlx_mysql...");
|
||||||
|
match futures::executor::block_on(test_sqlx_mysql("", enable_remote)) {
|
||||||
|
Ok(_) => println!(" successful!"),
|
||||||
|
Err(e) => println!(" error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("test_sqlx_sqlite...");
|
||||||
|
match futures::executor::block_on(test_sqlx_sqlite("sqlite:sqlite_database.db", enable_remote)) {
|
||||||
|
Ok(_) => println!(" successful!"),
|
||||||
|
Err(e) => println!(" error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("test_sqlx_postgres...");
|
||||||
|
match futures::executor::block_on(test_sqlx_postgres("", enable_remote)) {
|
||||||
|
Ok(_) => println!(" successful!"),
|
||||||
|
Err(e) => println!(" error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user