fix: resolve sql injection while building dynamic query for filters

This commit is contained in:
2026-02-27 16:55:45 +01:00
parent 32cf95a837
commit 150c2263d2
2 changed files with 28 additions and 28 deletions

View File

@@ -4,7 +4,8 @@ use rusqlite::*;
pub fn run<'a>(filter: &'a Filter) -> Result<Vec<Box<dyn ToSql + 'a>>, FailedTo> { pub fn run<'a>(filter: &'a Filter) -> Result<Vec<Box<dyn ToSql + 'a>>, FailedTo> {
let mut params: Vec<Box<dyn ToSql>> = Vec::new(); let mut params: Vec<Box<dyn ToSql>> = Vec::new();
for condition in filter.conditions() { for condition in filter.conditions() {
let (_, comparison, condition) = condition; let (name, comparison, condition) = condition;
params.push(Box::new(name));
match (comparison, condition) { match (comparison, condition) {
// BOOL // BOOL
(Comparison::Equal, Condition::Bool(value)) => params.push(Box::new(value)), (Comparison::Equal, Condition::Bool(value)) => params.push(Box::new(value)),

View File

@@ -1,83 +1,82 @@
use crate::*; use crate::*;
// TODO: possible sql injection for attribute_id!!!
const BASE_SELECT: &str = r#"SELECT * FROM entity"#; const BASE_SELECT: &str = r#"SELECT * FROM entity"#;
const INNER_JOIN_FRAGMENT: &str = r#" const INNER_JOIN_FRAGMENT: &str = r#"
INNER JOIN attribute as attribute_{index} INNER JOIN attribute as attribute_{index}
ON entity.id = attribute_{index}.entity_id ON entity.id = attribute_{index}.entity_id
AND attribute_{index}.id = '{attribute_id}' AND attribute_{index}.id = ?{attribute_id_index}
AND attribute_{index}.{field} {op} ?{index} AND attribute_{index}.{field} {op} ?{index}
"#; "#;
const WHERE: &str = r#" WHERE 1=1"#; const WHERE: &str = r#" WHERE 1=1"#;
fn compose_fragment(name: &str, field: &str, op: &str, index: usize) -> String { fn compose_fragment(field: &str, op: &str, index: usize) -> String {
let attribute_index = index * 2 - 1;
let field_index = index * 2;
INNER_JOIN_FRAGMENT INNER_JOIN_FRAGMENT
.replace("{attribute_id}", name)
.replace("{field}", field) .replace("{field}", field)
.replace("{op}", op) .replace("{op}", op)
.replace("{index}", &index.to_string()) .replace("{attribute_id_index}", &attribute_index.to_string())
.replace("{index}", &field_index.to_string())
} }
fn from_condition( fn from_condition(
i: usize, i: usize,
name: &str,
comparison: &Comparison, comparison: &Comparison,
condition: &Condition, condition: &Condition,
) -> Result<String, FailedTo> { ) -> Result<String, FailedTo> {
let fragment = match (comparison, condition) { let fragment = match (comparison, condition) {
// BOOL // BOOL
(Comparison::Equal, Condition::Bool(_)) => compose_fragment(name, "value_bool", "=", i), (Comparison::Equal, Condition::Bool(_)) => compose_fragment("value_bool", "=", i),
(_, Condition::Bool(_)) => return Err(FailedTo::ComposeFilter), (_, Condition::Bool(_)) => return Err(FailedTo::ComposeFilter),
// SIGNED INT // SIGNED INT
(Comparison::Equal, Condition::SignedInt(_)) => compose_fragment(name, "value_int", "=", i), (Comparison::Equal, Condition::SignedInt(_)) => compose_fragment("value_int", "=", i),
(Comparison::Greater, Condition::SignedInt(_)) => { (Comparison::Greater, Condition::SignedInt(_)) => {
compose_fragment(name, "value_int", ">", i) compose_fragment("value_int", ">", i)
} }
(Comparison::Lesser, Condition::SignedInt(_)) => { (Comparison::Lesser, Condition::SignedInt(_)) => {
compose_fragment(name, "value_int", "<", i) compose_fragment("value_int", "<", i)
} }
(Comparison::GreaterOrEqual, Condition::SignedInt(_)) => { (Comparison::GreaterOrEqual, Condition::SignedInt(_)) => {
compose_fragment(name, "value_int", ">=", i) compose_fragment("value_int", ">=", i)
} }
(Comparison::LesserOrEqual, Condition::SignedInt(_)) => { (Comparison::LesserOrEqual, Condition::SignedInt(_)) => {
compose_fragment(name, "value_int", "<=", i) compose_fragment("value_int", "<=", i)
} }
(_, Condition::SignedInt(_)) => return Err(FailedTo::ComposeFilter), (_, Condition::SignedInt(_)) => return Err(FailedTo::ComposeFilter),
// UNSIGNED INT // UNSIGNED INT
(Comparison::Equal, Condition::UnsignedInt(_)) => { (Comparison::Equal, Condition::UnsignedInt(_)) => {
compose_fragment(name, "value_uint", "=", i) compose_fragment("value_uint", "=", i)
} }
(Comparison::Greater, Condition::UnsignedInt(_)) => { (Comparison::Greater, Condition::UnsignedInt(_)) => {
compose_fragment(name, "value_uint", ">", i) compose_fragment("value_uint", ">", i)
} }
(Comparison::Lesser, Condition::UnsignedInt(_)) => { (Comparison::Lesser, Condition::UnsignedInt(_)) => {
compose_fragment(name, "value_uint", "<", i) compose_fragment("value_uint", "<", i)
} }
(Comparison::GreaterOrEqual, Condition::UnsignedInt(_)) => { (Comparison::GreaterOrEqual, Condition::UnsignedInt(_)) => {
compose_fragment(name, "value_uint", ">=", i) compose_fragment("value_uint", ">=", i)
} }
(Comparison::LesserOrEqual, Condition::UnsignedInt(_)) => { (Comparison::LesserOrEqual, Condition::UnsignedInt(_)) => {
compose_fragment(name, "value_uint", "<=", i) compose_fragment("value_uint", "<=", i)
} }
(_, Condition::UnsignedInt(_)) => return Err(FailedTo::ComposeFilter), (_, Condition::UnsignedInt(_)) => return Err(FailedTo::ComposeFilter),
// REAL // REAL
(Comparison::Equal, Condition::Real(_)) => compose_fragment(name, "value_real", "=", i), (Comparison::Equal, Condition::Real(_)) => compose_fragment("value_real", "=", i),
(Comparison::Greater, Condition::Real(_)) => compose_fragment(name, "value_real", ">", i), (Comparison::Greater, Condition::Real(_)) => compose_fragment("value_real", ">", i),
(Comparison::Lesser, Condition::Real(_)) => compose_fragment(name, "value_real", "<", i), (Comparison::Lesser, Condition::Real(_)) => compose_fragment("value_real", "<", i),
(Comparison::GreaterOrEqual, Condition::Real(_)) => { (Comparison::GreaterOrEqual, Condition::Real(_)) => {
compose_fragment(name, "value_real", ">=", i) compose_fragment("value_real", ">=", i)
} }
(Comparison::LesserOrEqual, Condition::Real(_)) => { (Comparison::LesserOrEqual, Condition::Real(_)) => {
compose_fragment(name, "value_real", "<=", i) compose_fragment("value_real", "<=", i)
} }
(_, Condition::Real(_)) => return Err(FailedTo::ComposeFilter), (_, Condition::Real(_)) => return Err(FailedTo::ComposeFilter),
// TEXT // TEXT
(Comparison::IsExactly, Condition::Text(_)) => compose_fragment(name, "value_text", "=", i), (Comparison::IsExactly, Condition::Text(_)) => compose_fragment("value_text", "=", i),
( (
Comparison::StartsWith | Comparison::EndsWith | Comparison::Contains, Comparison::StartsWith | Comparison::EndsWith | Comparison::Contains,
Condition::Text(_), Condition::Text(_),
) => compose_fragment(name, "value_text", "LIKE", i), ) => compose_fragment("value_text", "LIKE", i),
(_, Condition::Text(_)) => return Err(FailedTo::ComposeFilter), (_, Condition::Text(_)) => return Err(FailedTo::ComposeFilter),
}; };
Ok(fragment) Ok(fragment)
@@ -88,9 +87,9 @@ pub fn run(filter: &Filter) -> Result<String, FailedTo> {
let mut statement = String::from(BASE_SELECT); let mut statement = String::from(BASE_SELECT);
let mut idx = 0; let mut idx = 0;
// for each condition add an inner join fragment // for each condition add an inner join fragment
for (i, (name, comparison, condition)) in filter.conditions().enumerate() { for (i, (_, comparison, condition)) in filter.conditions().enumerate() {
idx = i + 1; idx = i + 1;
let fragment = from_condition(idx, name, comparison, condition)?; let fragment = from_condition(idx, comparison, condition)?;
statement.push_str(&fragment); statement.push_str(&fragment);
} }
// add a neutral where condition // add a neutral where condition