Skip to content

Commit 3673c7c

Browse files
committed
Rust: Add SQL injection test cases (complete and functioning).
1 parent ef9f383 commit 3673c7c

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# sqlite database
2+
*.db*
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[workspace]
2+
3+
[package]
4+
name = "CWE-089-Test"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
[dependencies]
9+
reqwest = { version = "0.12.9", features = ["blocking"] }
10+
sqlx = { version = "0.8", features = ["mysql", "sqlite", "postgres", "runtime-async-std", "tls-native-tls"] }
11+
futures = { version = "0.3" }
12+
13+
[[bin]]
14+
name = "sqlx"
15+
path = "./sqlx.rs"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CREATE TABLE IF NOT EXISTS people
2+
(
3+
id INTEGER PRIMARY KEY NOT NULL,
4+
firstname TEXT NOT NULL,
5+
lastname TEXT NOT NULL
6+
);
7+
8+
INSERT INTO people
9+
VALUES (1, "Alice", "Adams");
10+
11+
INSERT INTO people
12+
VALUES (2, "Bob", "Becket");
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use sqlx::Connection;
2+
use sqlx::Executor;
3+
4+
/**
5+
* This test is designed to be "run" in two ways:
6+
* - you can extract and analyze the code here using the CodeQL test runner in the usual way,
7+
* verifying the that various vulnerabilities are detected.
8+
* - you can compile and run the code using `cargo`, verifying that it really is a complete
9+
* program that compiles, runs and executes SQL commands (the sqlite ones, at least).
10+
*
11+
* To do the latter:
12+
*
13+
* Install `sqlx`:
14+
* ```
15+
* cargo install sqlx-cli
16+
* ```
17+
*
18+
* Create the database:
19+
* ```
20+
* export DATABASE_URL="sqlite:sqlite_database.db"
21+
* sqlx db create
22+
* sqlx migrate run
23+
* ```
24+
*
25+
* Build and run:
26+
* ```
27+
* cargo run
28+
* ```
29+
*
30+
* You can also rebuild the sqlx 'query cache' in the `.sqlx` subdirectory
31+
* with:
32+
* ```
33+
* cargo sqlx prepare
34+
* ```
35+
* This allows the code (in particular the `prepare!` macro) to be built
36+
* in the test without setting `DATABASE_URL` first.
37+
*/
38+
39+
async fn test_sqlx_mysql(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
40+
// connect through a MySql connection pool
41+
let pool = sqlx::mysql::MySqlPool::connect(url).await?;
42+
let mut conn = pool.acquire().await?;
43+
44+
// construct queries (with extra variants)
45+
let const_string = String::from("Alice");
46+
let arg_string = std::env::args().nth(1).unwrap_or(String::from("Alice")); // $ MISSING Source=args1
47+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote1
48+
let remote_number = remote_string.parse::<i32>().unwrap_or(0);
49+
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='Alice'");
50+
let safe_query_2 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
51+
let safe_query_3 = format!("SELECT * FROM people WHERE firstname='{remote_number}'");
52+
let unsafe_query_1 = &arg_string;
53+
let unsafe_query_2 = &remote_string;
54+
let unsafe_query_3 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
55+
let unsafe_query_4 = format!("SELECT * FROM people WHERE firstname='{remote_string}'");
56+
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
57+
58+
// direct execution
59+
let _ = conn.execute(safe_query_1.as_str()).await?;
60+
let _ = conn.execute(safe_query_2.as_str()).await?;
61+
let _ = conn.execute(safe_query_3.as_str()).await?;
62+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=args1
63+
if enable_remote {
64+
let _ = conn.execute(unsafe_query_2.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
65+
let _ = conn.execute(unsafe_query_3.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
66+
let _ = conn.execute(unsafe_query_4.as_str()).await?; // $ MISSING Alert[sql-injection]=remote1
67+
}
68+
69+
// prepared queries
70+
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
71+
let _ = sqlx::query(safe_query_2.as_str()).execute(&pool).await?;
72+
let _ = sqlx::query(safe_query_3.as_str()).execute(&pool).await?;
73+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=args1
74+
if enable_remote {
75+
let _ = sqlx::query(unsafe_query_2.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
76+
let _ = sqlx::query(unsafe_query_3.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
77+
let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote1
78+
}
79+
let _ = sqlx::query(prepared_query_1.as_str()).bind(const_string).execute(&pool).await?;
80+
let _ = sqlx::query(prepared_query_1.as_str()).bind(arg_string).execute(&pool).await?;
81+
if enable_remote {
82+
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_string).execute(&pool).await?;
83+
let _ = sqlx::query(prepared_query_1.as_str()).bind(remote_number).execute(&pool).await?;
84+
}
85+
86+
Ok(())
87+
}
88+
89+
async fn test_sqlx_sqlite(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
90+
// connect through Sqlite, no connection pool
91+
let mut conn = sqlx::sqlite::SqliteConnection::connect(url).await?;
92+
93+
// construct queries
94+
let const_string = String::from("Alice");
95+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote2
96+
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
97+
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
98+
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=?"); // (prepared arguments are safe)
99+
100+
// direct execution (with extra variants)
101+
let _ = conn.execute(safe_query_1.as_str()).await?;
102+
if enable_remote {
103+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote2
104+
}
105+
// ...
106+
let _ = sqlx::raw_sql(safe_query_1.as_str()).execute(&mut conn).await?;
107+
if enable_remote {
108+
let _ = sqlx::raw_sql(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
109+
}
110+
111+
// prepared queries (with extra variants)
112+
let _ = sqlx::query(safe_query_1.as_str()).execute(&mut conn).await?;
113+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&mut conn).await?;
114+
if enable_remote {
115+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
116+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&mut conn).await?;
117+
}
118+
// ...
119+
let _ = sqlx::query(safe_query_1.as_str()).fetch(&mut conn);
120+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch(&mut conn);
121+
if enable_remote {
122+
let _ = sqlx::query(unsafe_query_1.as_str()).fetch(&mut conn); // $ MISSING Alert[sql-injection]=remote2
123+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch(&mut conn);
124+
}
125+
// ...
126+
let row1: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_one(&mut conn).await?;
127+
println!(" row1 = {:?}", row1);
128+
let row2: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_one(&mut conn).await?;
129+
println!(" row2 = {:?}", row2);
130+
if enable_remote {
131+
let _: (i64, String, String) = sqlx::query_as(unsafe_query_1.as_str()).fetch_one(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
132+
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_one(&mut conn).await?;
133+
}
134+
// ...
135+
let row3: (i64, String, String) = sqlx::query_as(safe_query_1.as_str()).fetch_optional(&mut conn).await?.expect("no data");
136+
println!(" row3 = {:?}", row3);
137+
let row4: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&const_string).fetch_optional(&mut conn).await?.expect("no data");
138+
println!(" row4 = {:?}", row4);
139+
if enable_remote {
140+
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
141+
let _: (i64, String, String) = sqlx::query_as(prepared_query_1.as_str()).bind(&remote_string).fetch_optional(&mut conn).await?.expect("no data");
142+
}
143+
// ...
144+
let _ = sqlx::query(safe_query_1.as_str()).fetch_all(&mut conn).await?;
145+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).fetch_all(&mut conn).await?;
146+
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&const_string).fetch_all(&mut conn).await?;
147+
if enable_remote {
148+
let _ = sqlx::query(unsafe_query_1.as_str()).fetch_all(&mut conn).await?; // $ MISSING Alert[sql-injection]=remote2
149+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).fetch_all(&mut conn).await?;
150+
let _ = sqlx::query("SELECT * FROM people WHERE firstname=?").bind(&remote_string).fetch_all(&mut conn).await?;
151+
}
152+
// ...
153+
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)
154+
if enable_remote {
155+
let _ = sqlx::query!("SELECT * FROM people WHERE firstname=$1", remote_string).fetch_all(&mut conn).await?;
156+
}
157+
158+
Ok(())
159+
}
160+
161+
async fn test_sqlx_postgres(url: &str, enable_remote: bool) -> Result<(), sqlx::Error> {
162+
// connect through a PostGres connection pool
163+
let pool = sqlx::postgres::PgPool::connect(url).await?;
164+
let mut conn = pool.acquire().await?;
165+
166+
// construct queries
167+
let const_string = String::from("Alice");
168+
let remote_string = reqwest::blocking::get("http://example.com/").unwrap().text().unwrap_or(String::from("Alice")); // $ MISSING Source=remote3
169+
let safe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &const_string + "'";
170+
let unsafe_query_1 = String::from("SELECT * FROM people WHERE firstname='") + &remote_string + "'";
171+
let prepared_query_1 = String::from("SELECT * FROM people WHERE firstname=$1"); // (prepared arguments are safe)
172+
173+
// direct execution
174+
let _ = conn.execute(safe_query_1.as_str()).await?;
175+
if enable_remote {
176+
let _ = conn.execute(unsafe_query_1.as_str()).await?; // $ MISSING Alert[sql-injection]=remote3
177+
}
178+
179+
// prepared queries
180+
let _ = sqlx::query(safe_query_1.as_str()).execute(&pool).await?;
181+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&const_string).execute(&pool).await?;
182+
if enable_remote {
183+
let _ = sqlx::query(unsafe_query_1.as_str()).execute(&pool).await?; // $ MISSING Alert[sql-injection]=remote3
184+
let _ = sqlx::query(prepared_query_1.as_str()).bind(&remote_string).execute(&pool).await?;
185+
}
186+
187+
Ok(())
188+
}
189+
190+
fn main() {
191+
println!("--- CWE-089 sqlx.rs test ---");
192+
193+
// we don't *actually* use data from a remote source unless we're explicitly told to at the
194+
// command line; that's because this test is designed to be runnable, and we don't really
195+
// want to expose the test database to potential SQL injection from http://example.com/ -
196+
// no matter how unlikely, local and compartmentalized that may seem.
197+
let enable_remote = std::env::args().nth(1) == Some(String::from("ENABLE_REMOTE"));
198+
println!("enable_remote = {enable_remote}");
199+
200+
println!("test_sqlx_mysql...");
201+
match futures::executor::block_on(test_sqlx_mysql("", enable_remote)) {
202+
Ok(_) => println!(" successful!"),
203+
Err(e) => println!(" error: {}", e),
204+
}
205+
206+
println!("test_sqlx_sqlite...");
207+
match futures::executor::block_on(test_sqlx_sqlite("sqlite:sqlite_database.db", enable_remote)) {
208+
Ok(_) => println!(" successful!"),
209+
Err(e) => println!(" error: {}", e),
210+
}
211+
212+
println!("test_sqlx_postgres...");
213+
match futures::executor::block_on(test_sqlx_postgres("", enable_remote)) {
214+
Ok(_) => println!(" successful!"),
215+
Err(e) => println!(" error: {}", e),
216+
}
217+
}

0 commit comments

Comments
 (0)