Compile Time Prevention of SQL-Injections in Rust
polyfloyd.net
polyfloyd.net
What &'static means is that whatever the reference is pointing at will never be modified or go out of scope. One way to provide this is to put it in the read-only part of the executable, which is what literals do. Another is to use into_boxed_str() [1] and Box::leak() [2] to leak the string and thus make sure it will never be modified or freed. Neither function is unsafe, while Box::leak() is still only in nightly.
[1]: https://doc.rust-lang.org/std/string/struct.String.html#meth... [2]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.l...
use std::io::stdin;
fn main() {
let mut s = String::new();
stdin().read_line(&mut s).expect("Did not enter a correct string");
let sql_example = format!("SELECT * FROM users WHERE username={}", s);
let x = Box::new(sql_example);
let static_ref: &'static str = Box::leak(x);
println!("{}", static_ref)
}
Note the type on our variable "static_ref". It is a static str, meaning it could be an argument to the code in the blog post. No "unsafe" blocks either.When I run it
Input: ' OR '1'='1
Output: SELECT * FROM users WHERE username=' OR '1'='1
The technique might be still useful if it was only allowed in debug builds to reduce boilerplate during debugging in a system that tried to use types to solve the issue.If your SQL has to be computed at compile time, how do you implement any sort of search where you will have a variable number of ANDS & ORs?
Then you can use conditionals and loops to decide which fragments are included.
That is, sure, it's not like Rust takes the option to check for injection at runtime.
It's not your SQL (e.g. whole query) that needs to be computed at compile time, but the proper escaping of placeholder parameters.
So, the answer is: trivially.
What would be checked by Rust is the actual fitness of the fragments, not if your building them in a loop from pre-made WHERE, AND etc clauses + placeholders.
where = "1=1";
if (cond) { where += " and foo ='bar'"; }
...
If you know all the possible ANDs and ORs beforehand, you could pass in flags to select which ones to use. That would also mean you could prepare queries ahead of time, and not need to compile them every time you run them.
I don't think there would be many situations where you wouldn't know what possible filters you could have at compile time, and in such a situation I'd question whether there was a better way to implement what I was trying to do.
- Getting untrusted input as a separate type
- Having only a controlled way to put instances of this type into an SQL query
Basically, the 'static lifetime guarantees that the query string was built at compile time. And then it allows the parameters to be from user input. To Rust, these are effectively different types due to the restrictions on the function definition, which restricts the first parameter to 'static, and the list of SQL parameters can come from anywhere (static or dynamic runtime).
let _rows = sql_query("SELECT * FROM users WHERE username=?", &[username]);
The statement is static, but the [username] part is not, it's just a variable that can have whatever username you want.
Problem is, that is too limiting to satisfy the functional use cases in a lot of code that constructs SQL queries by substituting values into templates, where some of the pieces are necessarily dependent on inputs from logged-in users and such.
They're saying injection attacks come from dynamically building strings, so if you prevent that you stop the vulnerabilities.
The argument isn't "block ' characters to stop SQL injection" it's "force the developer to use prepared statements and no dynamically constructed queries".
Second, the naive and silly attacks are the default definition of SQL injection -- and the most commonly found holes, so even if it just prevented those that would be still a huge help. Nothing statically ensures that at runtime except the programmer's due diligence.
Always build your queries out of strings defined at compile time and use them as prepared statements with parameters.
So we always end up with a mix of prepared statements and string manipulation.
https://en.wikipedia.org/wiki/Prepared_statement
The ? is a placeholder for dynamic values, the user input is bound to it.