From 6eaf27cda0452c7dca824c5acdb99a221a42f25f Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:35:26 +0100 Subject: [PATCH 01/12] Fix false positive for single or double quotes that are safely escaped With a partial match --- src/helpers/find_all_matches.rs | 12 ++++ src/helpers/find_all_matches_test.rs | 67 +++++++++++++++++++ src/helpers/mod.rs | 1 + src/sql_injection/detect_sql_injection.rs | 45 +++++++++++++ .../detect_sql_injection_test.rs | 25 +++++++ 5 files changed, 150 insertions(+) create mode 100644 src/helpers/find_all_matches.rs create mode 100644 src/helpers/find_all_matches_test.rs diff --git a/src/helpers/find_all_matches.rs b/src/helpers/find_all_matches.rs new file mode 100644 index 0000000..1f1c5c7 --- /dev/null +++ b/src/helpers/find_all_matches.rs @@ -0,0 +1,12 @@ +pub fn find_all_matches(haystack: &str, needle: &str) -> Vec { + let mut positions = Vec::new(); + let mut start = 0; + + while let Some(pos) = haystack[start..].find(needle) { + let match_pos = start + pos; + positions.push(match_pos); + start = match_pos + 1; + } + + positions +} diff --git a/src/helpers/find_all_matches_test.rs b/src/helpers/find_all_matches_test.rs new file mode 100644 index 0000000..be0f0e8 --- /dev/null +++ b/src/helpers/find_all_matches_test.rs @@ -0,0 +1,67 @@ +#[cfg(test)] +mod tests { + use crate::helpers::find_all_matches::find_all_matches; + + #[test] + fn test_find_all_matches() { + let haystack = "hello world hello world hello world"; + let needle = "world"; + let expected = vec![6, 18, 30]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_no_matches() { + let haystack = "hello world hello world hello world"; + let needle = "foo"; + let expected = vec![]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_empty_haystack() { + let haystack = ""; + let needle = "world"; + let expected = vec![]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_empty_needle() { + let haystack = "hello world hello world hello world"; + let needle = ""; + let expected = vec![]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_empty_haystack_and_needle() { + let haystack = ""; + let needle = ""; + let expected = vec![]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_single_char() { + let haystack = "hello world hello world hello world"; + let needle = "w"; + let expected = vec![6, 18, 30]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } + + #[test] + fn test_overlapping_matches() { + let haystack = "aaa"; + let needle = "aa"; + let expected = vec![0, 1]; + let result = find_all_matches(haystack, needle); + assert_eq!(result, expected); + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index cd43c7c..de0958f 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1 +1,2 @@ pub mod diff_in_vec_len; +pub mod find_all_matches; diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index 8944358..d66215e 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -2,6 +2,7 @@ use super::have_comments_changed::have_comments_changed; use super::is_common_sql_string::is_common_sql_string; use super::tokenize_query::tokenize_query; use crate::diff_in_vec_len; +use crate::helpers::find_all_matches::find_all_matches; use sqlparser::tokenizer::Token; const SPACE_CHAR: char = ' '; @@ -26,6 +27,50 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b return false; } + // Special case for single or double quotes at start and/or end of user input + // Normally if the user input is properly escaped, we wouldn't find an exact match in the query + // However, if the user input is `'value` and single quote is used to escape + // `'value` becomes `'''value'` in the query so we still find an exact match + if userinput.contains("'") || userinput.contains(r#"""#) { + let mut matches = find_all_matches(query, userinput).len(); + for token in tokens.iter() { + match token { + Token::SingleQuotedString(s) => { + let single_quoted_start = "'".to_owned() + userinput; // 'userinput + let single_quoted_end = userinput.to_owned() + "'"; // userinput' + let fully_quoted = "'".to_owned() + userinput + "'"; // 'userinput' + + if *s == single_quoted_start { + matches -= 1; + } else if *s == single_quoted_end { + matches -= 1; + } else if *s == fully_quoted { + matches -= 1; + } + }, + Token::DoubleQuotedString(s) => { + let double_quoted_start = r#"""#.to_owned() + userinput; // "userinput + let double_quoted_end = userinput.to_owned() + r#"""#; // userinput" + let fully_quoted = r#"""#.to_owned() + userinput + r#"""#; // "userinput" + + if *s == double_quoted_start { + matches -= 1; + } else if *s == double_quoted_end { + matches -= 1; + } else if *s == fully_quoted { + matches -= 1; + } + }, + _ => {} + } + } + + if matches == 0 { + // All matches were found in strings, so it's not an injection. + return false; + } + } + // Remove leading and trailing spaces from userinput : let trimmed_userinput = userinput.trim_matches(SPACE_CHAR); diff --git a/src/sql_injection/detect_sql_injection_test.rs b/src/sql_injection/detect_sql_injection_test.rs index 630b643..495501d 100644 --- a/src/sql_injection/detect_sql_injection_test.rs +++ b/src/sql_injection/detect_sql_injection_test.rs @@ -691,4 +691,29 @@ mod tests { ); is_injection!("SELECT * FROM users WHERE id IN (-1,571,639)", "-1,571,639"); } + + #[test] + fn test_partial_match_single_quote() { + let starts_with = r#" + SELECT "foo" WHERE "bar" = '''; sleep 15 ;"' + "#; + + // r#"..."# is a raw string literal + not_injection!(starts_with, r#"'; sleep 15 ;""#); + } + + #[test] + fn test_multiple_partial_match_single_quote() { + not_injection!("SELECT '''abc', '''abc' FROM table", "'abc"); + not_injection!("SELECT 'abc''', 'abc''' FROM table", "abc'"); + not_injection!("SELECT '''abc''', '''abc''' FROM table", "'abc'"); + } + + #[test] + fn test_multiple_partial_match_double_quote() { + // r#"..."# is a raw string literal + not_injection!(r#"SELECT """"abc", """"abc"" FROM table"#, r#""abc"#); + not_injection!(r#"SELECT "abc"""", "abc""" FROM table"#, r#"abc""#); + not_injection!(r#"SELECT """"abc""", """"abc"" FROM table"#, r#""abc"#); + } } From 0f7c34f67495da313a0160de13b769d722c41580 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:39:53 +0100 Subject: [PATCH 02/12] Simplify code --- src/sql_injection/detect_sql_injection.rs | 37 +++++++---------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index d66215e..aa2b110 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -33,34 +33,19 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b // `'value` becomes `'''value'` in the query so we still find an exact match if userinput.contains("'") || userinput.contains(r#"""#) { let mut matches = find_all_matches(query, userinput).len(); + + fn is_quoted_match(input: &str, userinput: &str, quote: char) -> bool { + let quoted_start = format!("{quote}{userinput}"); + let quoted_end = format!("{userinput}{quote}"); + let fully_quoted = format!("{quote}{userinput}{quote}"); + + input == quoted_start || input == quoted_end || input == fully_quoted + } + for token in tokens.iter() { match token { - Token::SingleQuotedString(s) => { - let single_quoted_start = "'".to_owned() + userinput; // 'userinput - let single_quoted_end = userinput.to_owned() + "'"; // userinput' - let fully_quoted = "'".to_owned() + userinput + "'"; // 'userinput' - - if *s == single_quoted_start { - matches -= 1; - } else if *s == single_quoted_end { - matches -= 1; - } else if *s == fully_quoted { - matches -= 1; - } - }, - Token::DoubleQuotedString(s) => { - let double_quoted_start = r#"""#.to_owned() + userinput; // "userinput - let double_quoted_end = userinput.to_owned() + r#"""#; // userinput" - let fully_quoted = r#"""#.to_owned() + userinput + r#"""#; // "userinput" - - if *s == double_quoted_start { - matches -= 1; - } else if *s == double_quoted_end { - matches -= 1; - } else if *s == fully_quoted { - matches -= 1; - } - }, + Token::SingleQuotedString(s) if is_quoted_match(s, userinput, '\'') => matches -= 1, + Token::DoubleQuotedString(s) if is_quoted_match(s, userinput, '"') => matches -= 1, _ => {} } } From 2c15426da5403f565a4d0d13cea62054af460365 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:41:09 +0100 Subject: [PATCH 03/12] Simplify --- src/sql_injection/detect_sql_injection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index aa2b110..27e7305 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -31,7 +31,7 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b // Normally if the user input is properly escaped, we wouldn't find an exact match in the query // However, if the user input is `'value` and single quote is used to escape // `'value` becomes `'''value'` in the query so we still find an exact match - if userinput.contains("'") || userinput.contains(r#"""#) { + if userinput.contains("'") || userinput.contains('"') { let mut matches = find_all_matches(query, userinput).len(); fn is_quoted_match(input: &str, userinput: &str, quote: char) -> bool { From 4636745783fd1e9e031e4cc6589b475ba0bbdff7 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:43:00 +0100 Subject: [PATCH 04/12] Improve comment --- src/sql_injection/detect_sql_injection.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index 27e7305..c63f3ba 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -29,8 +29,9 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b // Special case for single or double quotes at start and/or end of user input // Normally if the user input is properly escaped, we wouldn't find an exact match in the query - // However, if the user input is `'value` and single quote is used to escape + // However, if the user input is `'value` and the single quote is escaped with another single quote // `'value` becomes `'''value'` in the query so we still find an exact match + // (vice versa for double quotes) if userinput.contains("'") || userinput.contains('"') { let mut matches = find_all_matches(query, userinput).len(); From 8c22d444c3bb13410b7de2d444e2a7e7680fc4ef Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:45:12 +0100 Subject: [PATCH 05/12] Rename function --- src/sql_injection/detect_sql_injection.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index c63f3ba..fe5cab6 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -35,7 +35,7 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b if userinput.contains("'") || userinput.contains('"') { let mut matches = find_all_matches(query, userinput).len(); - fn is_quoted_match(input: &str, userinput: &str, quote: char) -> bool { + fn is_safely_escaped(input: &str, userinput: &str, quote: char) -> bool { let quoted_start = format!("{quote}{userinput}"); let quoted_end = format!("{userinput}{quote}"); let fully_quoted = format!("{quote}{userinput}{quote}"); @@ -45,14 +45,14 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b for token in tokens.iter() { match token { - Token::SingleQuotedString(s) if is_quoted_match(s, userinput, '\'') => matches -= 1, - Token::DoubleQuotedString(s) if is_quoted_match(s, userinput, '"') => matches -= 1, + Token::SingleQuotedString(s) if is_safely_escaped(s, userinput, '\'') => matches -= 1, + Token::DoubleQuotedString(s) if is_safely_escaped(s, userinput, '"') => matches -= 1, _ => {} } } if matches == 0 { - // All matches were found in strings, so it's not an injection. + // All matches are safely escaped, not an injection. return false; } } From ac4a831f1c49ebe3d99661fd0a7b58f9cfbbd580 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:46:17 +0100 Subject: [PATCH 06/12] Format code --- src/sql_injection/detect_sql_injection.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index fe5cab6..ffc6cbb 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -45,8 +45,12 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b for token in tokens.iter() { match token { - Token::SingleQuotedString(s) if is_safely_escaped(s, userinput, '\'') => matches -= 1, - Token::DoubleQuotedString(s) if is_safely_escaped(s, userinput, '"') => matches -= 1, + Token::SingleQuotedString(s) if is_safely_escaped(s, userinput, '\'') => { + matches -= 1 + } + Token::DoubleQuotedString(s) if is_safely_escaped(s, userinput, '"') => { + matches -= 1 + } _ => {} } } From 3d9d135fe419c6c2b9d14659883ac23b3655fc78 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:56:35 +0100 Subject: [PATCH 07/12] Simplify --- src/sql_injection/detect_sql_injection.rs | 25 ++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/sql_injection/detect_sql_injection.rs b/src/sql_injection/detect_sql_injection.rs index ffc6cbb..3226507 100644 --- a/src/sql_injection/detect_sql_injection.rs +++ b/src/sql_injection/detect_sql_injection.rs @@ -33,29 +33,26 @@ pub fn detect_sql_injection_str(query: &str, userinput: &str, dialect: i32) -> b // `'value` becomes `'''value'` in the query so we still find an exact match // (vice versa for double quotes) if userinput.contains("'") || userinput.contains('"') { - let mut matches = find_all_matches(query, userinput).len(); - - fn is_safely_escaped(input: &str, userinput: &str, quote: char) -> bool { - let quoted_start = format!("{quote}{userinput}"); - let quoted_end = format!("{userinput}{quote}"); - let fully_quoted = format!("{quote}{userinput}{quote}"); - - input == quoted_start || input == quoted_end || input == fully_quoted - } + let matches = find_all_matches(query, userinput).len(); + let mut safely_escaped = 0; for token in tokens.iter() { match token { - Token::SingleQuotedString(s) if is_safely_escaped(s, userinput, '\'') => { - matches -= 1 + Token::SingleQuotedString(s) + if userinput.contains("'") && s.contains(userinput) => + { + safely_escaped += 1 } - Token::DoubleQuotedString(s) if is_safely_escaped(s, userinput, '"') => { - matches -= 1 + Token::DoubleQuotedString(s) + if userinput.contains('"') && s.contains(userinput) => + { + safely_escaped += 1 } _ => {} } } - if matches == 0 { + if matches == safely_escaped { // All matches are safely escaped, not an injection. return false; } From 6285cb915d1e025040c191d6be8c22d0e27e4e71 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 6 Jan 2025 15:59:35 +0100 Subject: [PATCH 08/12] Add comment back --- src/helpers/find_all_matches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/find_all_matches.rs b/src/helpers/find_all_matches.rs index 1f1c5c7..67532f6 100644 --- a/src/helpers/find_all_matches.rs +++ b/src/helpers/find_all_matches.rs @@ -5,7 +5,7 @@ pub fn find_all_matches(haystack: &str, needle: &str) -> Vec { while let Some(pos) = haystack[start..].find(needle) { let match_pos = start + pos; positions.push(match_pos); - start = match_pos + 1; + start = match_pos + 1; // Allow overlapping matches } positions From eef88aa3fe616da8a31d1146d03f98186627072d Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 8 Jan 2025 15:09:18 +0100 Subject: [PATCH 09/12] Add some more tests --- .../detect_sql_injection_test.rs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/sql_injection/detect_sql_injection_test.rs b/src/sql_injection/detect_sql_injection_test.rs index 495501d..80136ce 100644 --- a/src/sql_injection/detect_sql_injection_test.rs +++ b/src/sql_injection/detect_sql_injection_test.rs @@ -694,12 +694,20 @@ mod tests { #[test] fn test_partial_match_single_quote() { - let starts_with = r#" - SELECT "foo" WHERE "bar" = '''; sleep 15 ;"' - "#; + let starts_with = r#" SELECT "foo" WHERE "bar" = '''; sleep 15 ;"' "#; // r#"..."# is a raw string literal not_injection!(starts_with, r#"'; sleep 15 ;""#); + + not_injection!("SELECT name FROM table WHERE id = ''' OR 1=1 --'", "' OR 1=1 --"); + not_injection!("SELECT name FROM table WHERE id = 'before'' OR 1=1 --'", "' OR 1=1 --"); + not_injection!("SELECT name FROM table WHERE id = ''' OR 1=1 --after'", "' OR 1=1 --"); + not_injection!("SELECT name FROM table WHERE id = 'before'' OR 1=1 --after'", "' OR 1=1 --"); + not_injection!("SELECT name FROM table WHERE id = '''", "'"); + not_injection!("SELECT name FROM table WHERE id = '''''", "''"); + + is_injection!("SELECT name FROM table WHERE id = ''--", "'--"); + is_injection!("SELECT name FROM table WHERE id = ''--''", "'--'"); } #[test] @@ -707,13 +715,23 @@ mod tests { not_injection!("SELECT '''abc', '''abc' FROM table", "'abc"); not_injection!("SELECT 'abc''', 'abc''' FROM table", "abc'"); not_injection!("SELECT '''abc''', '''abc''' FROM table", "'abc'"); + + is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'", "' OR 1=1 --"); + is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'", "' OR 1=1 --"); + is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'after", "' OR 1=1 --"); + is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'after", "' OR 1=1 --"); } #[test] fn test_multiple_partial_match_double_quote() { // r#"..."# is a raw string literal - not_injection!(r#"SELECT """"abc", """"abc"" FROM table"#, r#""abc"#); - not_injection!(r#"SELECT "abc"""", "abc""" FROM table"#, r#"abc""#); - not_injection!(r#"SELECT """"abc""", """"abc"" FROM table"#, r#""abc"#); + not_injection!(r#" SELECT """"abc", """"abc"" FROM table "#, r#""abc"#); + not_injection!(r#" SELECT "abc"""", "abc""" FROM table "#, r#"abc""#); + not_injection!(r#" SELECT """"abc""", """"abc"" FROM table "#, r#""abc"#); + + is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --" "#, r#"" OR 1=1 --"#); + is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, r#"" OR 1=1 --"#); + is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, r#"" OR 1=1 --"#); + is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, r#"" OR 1=1 --"#); } } From 3c407bce9481011ebd2beb3350f1901920a12568 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 8 Jan 2025 15:10:52 +0100 Subject: [PATCH 10/12] Format code --- .../detect_sql_injection_test.rs | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/src/sql_injection/detect_sql_injection_test.rs b/src/sql_injection/detect_sql_injection_test.rs index 80136ce..0c1ac1c 100644 --- a/src/sql_injection/detect_sql_injection_test.rs +++ b/src/sql_injection/detect_sql_injection_test.rs @@ -699,10 +699,22 @@ mod tests { // r#"..."# is a raw string literal not_injection!(starts_with, r#"'; sleep 15 ;""#); - not_injection!("SELECT name FROM table WHERE id = ''' OR 1=1 --'", "' OR 1=1 --"); - not_injection!("SELECT name FROM table WHERE id = 'before'' OR 1=1 --'", "' OR 1=1 --"); - not_injection!("SELECT name FROM table WHERE id = ''' OR 1=1 --after'", "' OR 1=1 --"); - not_injection!("SELECT name FROM table WHERE id = 'before'' OR 1=1 --after'", "' OR 1=1 --"); + not_injection!( + "SELECT name FROM table WHERE id = ''' OR 1=1 --'", + "' OR 1=1 --" + ); + not_injection!( + "SELECT name FROM table WHERE id = 'before'' OR 1=1 --'", + "' OR 1=1 --" + ); + not_injection!( + "SELECT name FROM table WHERE id = ''' OR 1=1 --after'", + "' OR 1=1 --" + ); + not_injection!( + "SELECT name FROM table WHERE id = 'before'' OR 1=1 --after'", + "' OR 1=1 --" + ); not_injection!("SELECT name FROM table WHERE id = '''", "'"); not_injection!("SELECT name FROM table WHERE id = '''''", "''"); @@ -716,22 +728,52 @@ mod tests { not_injection!("SELECT 'abc''', 'abc''' FROM table", "abc'"); not_injection!("SELECT '''abc''', '''abc''' FROM table", "'abc'"); - is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'", "' OR 1=1 --"); - is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'", "' OR 1=1 --"); - is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'after", "' OR 1=1 --"); - is_injection!("SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'after", "' OR 1=1 --"); + is_injection!( + "SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'", + "' OR 1=1 --" + ); + is_injection!( + "SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'", + "' OR 1=1 --" + ); + is_injection!( + "SELECT ''' OR 1=1 --' FROM table WHERE id = '' OR 1=1 --'after", + "' OR 1=1 --" + ); + is_injection!( + "SELECT ''' OR 1=1 --' FROM table WHERE id = 'before' OR 1=1 --'after", + "' OR 1=1 --" + ); } #[test] fn test_multiple_partial_match_double_quote() { // r#"..."# is a raw string literal - not_injection!(r#" SELECT """"abc", """"abc"" FROM table "#, r#""abc"#); + not_injection!( + r#" SELECT """"abc", """"abc"" FROM table "#, + r#""abc"# + ); not_injection!(r#" SELECT "abc"""", "abc""" FROM table "#, r#"abc""#); - not_injection!(r#" SELECT """"abc""", """"abc"" FROM table "#, r#""abc"#); + not_injection!( + r#" SELECT """"abc""", """"abc"" FROM table "#, + r#""abc"# + ); - is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --" "#, r#"" OR 1=1 --"#); - is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, r#"" OR 1=1 --"#); - is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, r#"" OR 1=1 --"#); - is_injection!(r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, r#"" OR 1=1 --"#); + is_injection!( + r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --" "#, + r#"" OR 1=1 --"# + ); + is_injection!( + r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, + r#"" OR 1=1 --"# + ); + is_injection!( + r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, + r#"" OR 1=1 --"# + ); + is_injection!( + r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, + r#"" OR 1=1 --"# + ); } } From 87f8e79b8fd9a6d482810654e9a997083410eed0 Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 8 Jan 2025 15:44:23 +0100 Subject: [PATCH 11/12] Make sure one spot has safely escaped double quote in test --- src/sql_injection/detect_sql_injection_test.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sql_injection/detect_sql_injection_test.rs b/src/sql_injection/detect_sql_injection_test.rs index 0c1ac1c..22cfeaf 100644 --- a/src/sql_injection/detect_sql_injection_test.rs +++ b/src/sql_injection/detect_sql_injection_test.rs @@ -760,19 +760,19 @@ mod tests { ); is_injection!( - r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --" "#, + r#" SELECT """ OR 1=1 --" FROM table WHERE id = "" OR 1=1 --" "#, r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, + r#" SELECT """" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT "" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, + r#" SELECT """" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT "" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, + r#" SELECT """" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, r#"" OR 1=1 --"# ); } From 10fac23469713973ae43125abe6293ea3af0ae6a Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Wed, 8 Jan 2025 15:45:25 +0100 Subject: [PATCH 12/12] Should be three double quotes The start of the string, the double quote to escape the next double quote, the actual double quote of the user input --- src/sql_injection/detect_sql_injection_test.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sql_injection/detect_sql_injection_test.rs b/src/sql_injection/detect_sql_injection_test.rs index 22cfeaf..e2c2a49 100644 --- a/src/sql_injection/detect_sql_injection_test.rs +++ b/src/sql_injection/detect_sql_injection_test.rs @@ -764,15 +764,15 @@ mod tests { r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT """" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, + r#" SELECT """ OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --" "#, r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT """" OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, + r#" SELECT """ OR 1=1 --" FROM table WHERE id = "" OR 1=1 --"after "#, r#"" OR 1=1 --"# ); is_injection!( - r#" SELECT """" OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, + r#" SELECT """ OR 1=1 --" FROM table WHERE id = "before" OR 1=1 --"after "#, r#"" OR 1=1 --"# ); }