Files
bal-server/tests/sql_injection_tests.rs
svatantrya 8f764f06b2 fix: resolve PostgreSQL type mismatches and add willexecutor debug logging
- Fix get_next_address_index: use try_get::<i32> for PG SERIAL/INTEGER columns
- Fix search_tx: use try_get::<i32> for PG status column
- Fix execute_insert: parse locktime (String→i64) and in_vout (String→i32)
  before binding to PG INTEGER columns
- Fix execute_insert: bind tbl_out vout as i32, amount as String for PG
- Fix save_new_address: cast xpub i64 to i32 for PG INTEGER column
- Fix get_pending_txs: cast i64 bind params to i32, use try_get::<i32> for reads
- Fix get_stats: use try_get::<i32> for all numeric PG INTEGER columns
- Add trace logging in parse_request_transactions for xpub address matching
- Add trace logging in get_all_addresses_by_xpub for query debugging
2026-08-18 03:14:03 -04:00

137 lines
4.1 KiB
Rust

use bal_server::db::{DatabasePool, open_database};
use sqlx::Row;
async fn setup_db() -> DatabasePool {
open_database("sqlite", "sqlite::memory:").await.unwrap()
}
#[tokio::test]
async fn test_sql_injection_via_push_err_update() {
let pool = setup_db().await;
if let DatabasePool::SQLite(p) = &pool {
sqlx::query("CREATE TABLE tbl_tx (txid TEXT PRIMARY KEY, status INTEGER, push_err TEXT);")
.execute(p)
.await
.unwrap();
sqlx::query("INSERT INTO tbl_tx (txid, status, push_err) VALUES (?, ?, ?)")
.bind("dummy_txid")
.bind(0_i64)
.bind("")
.execute(p)
.await
.unwrap();
let malicious_error = "'; DROP TABLE tbl_tx; --";
let txid = "dummy_txid";
sqlx::query("UPDATE tbl_tx SET status = 2, push_err = ? WHERE txid = ?")
.bind(malicious_error)
.bind(txid)
.execute(p)
.await
.unwrap();
let row = sqlx::query("SELECT status, push_err FROM tbl_tx WHERE txid = ?")
.bind("dummy_txid")
.fetch_one(p)
.await
.unwrap();
let status: i64 = row.try_get("status").unwrap();
let push_err: String = row.try_get("push_err").unwrap();
assert_eq!(status, 2);
assert_eq!(push_err, malicious_error);
let row = sqlx::query("SELECT COUNT(*) as cnt FROM tbl_tx")
.fetch_one(p)
.await
.unwrap();
let count: i64 = row.try_get("cnt").unwrap();
assert_eq!(count, 1);
}
}
#[tokio::test]
async fn test_sql_injection_via_txid_update() {
let pool = setup_db().await;
if let DatabasePool::SQLite(p) = &pool {
sqlx::query("CREATE TABLE tbl_tx (txid TEXT PRIMARY KEY, status INTEGER);")
.execute(p)
.await
.unwrap();
for i in 0..3 {
sqlx::query("INSERT INTO tbl_tx (txid, status) VALUES (?, ?)")
.bind(format!("txid_{}", i))
.bind(0_i64)
.execute(p)
.await
.unwrap();
}
let malicious_txid = "' OR '1'='1";
sqlx::query("UPDATE tbl_tx SET status = 1 WHERE txid = ?")
.bind(malicious_txid)
.execute(p)
.await
.unwrap();
for i in 0..3 {
let row = sqlx::query("SELECT status FROM tbl_tx WHERE txid = ?")
.bind(format!("txid_{}", i))
.fetch_one(p)
.await
.unwrap();
let status: i64 = row.try_get("status").unwrap();
assert_eq!(
status, 0,
"Row txid_{} should not be updated by malicious txid",
i
);
}
}
}
#[tokio::test]
async fn test_sql_injection_via_txid_with_comment() {
let pool = setup_db().await;
if let DatabasePool::SQLite(p) = &pool {
sqlx::query("CREATE TABLE tbl_tx (txid TEXT PRIMARY KEY, status INTEGER);")
.execute(p)
.await
.unwrap();
sqlx::query("INSERT INTO tbl_tx (txid, status) VALUES (?, ?)")
.bind("safe_txid")
.bind(0_i64)
.execute(p)
.await
.unwrap();
let malicious_txid = "safe_txid'; UPDATE tbl_tx SET status = 99; --";
sqlx::query("UPDATE tbl_tx SET status = 1 WHERE txid = ?")
.bind(malicious_txid)
.execute(p)
.await
.unwrap();
let row = sqlx::query("SELECT status FROM tbl_tx WHERE txid = ?")
.bind("safe_txid")
.fetch_one(p)
.await
.unwrap();
let status: i64 = row.try_get("status").unwrap();
assert_eq!(status, 0, "Original row should not be updated");
let row = sqlx::query("SELECT COUNT(*) as cnt FROM tbl_tx WHERE status = 99")
.fetch_one(p)
.await
.unwrap();
let count: i64 = row.try_get("cnt").unwrap();
assert_eq!(count, 0, "No rows should have status 99");
}
}