Rust拼接SQL语句的函数是否存在SQL注入风险?
Absolutely—this function has a critical SQL injection vulnerability, and it’s a classic example of why never building SQL queries with string concatenation is a golden rule for database code.
Why the Vulnerability Exists
Let’s break down the problem with your current code:
fn add_repo_statement(repo: &Repo) -> String { format!("INSERT INTO repositories (name, scm, path) VALUES ({}, {}, {})", repo.name, repo.scm, repo.path) }
The issue is that you’re directly inserting raw values from the Repo struct into the SQL string. If any of those values (like repo.name) contain SQL syntax, an attacker can hijack the query’s behavior entirely.
For example, if repo.name is set to:
'); DROP TABLE repositories; --
The generated SQL becomes:
INSERT INTO repositories (name, scm, path) VALUES ('); DROP TABLE repositories; --, {}, {})
The -- comments out the rest of the query, so the database will first run a junk INSERT and then drop your entire repositories table. That’s a catastrophic attack, and it’s trivial to pull off with this code.
The root cause is simple: you’re treating untrusted (or user-controlled) data as part of the SQL syntax itself, rather than separating the query structure from the data it operates on.
Fixes: Use Parameterized Queries
The only safe way to handle dynamic values in SQL is to use parameterized queries (also called prepared statements). These keep the query structure and data completely separate, so the database never interprets data as SQL code.
In Rust, popular SQLite libraries like rusqlite have built-in support for this. Here’s how to rewrite your code safely:
Option 1: Execute the Query Directly with Parameters
Instead of returning a raw SQL string, handle execution using parameterized placeholders:
use rusqlite::{Connection, Result}; struct Repo { name: String, scm: String, path: String, } fn add_repo(conn: &Connection, repo: &Repo) -> Result<()> { conn.execute( "INSERT INTO repositories (name, scm, path) VALUES (?1, ?2, ?3)", (&repo.name, &repo.scm, &repo.path), )?; Ok(()) }
The ?1, ?2, ?3 are placeholders that rusqlite replaces with your values safely—no chance of SQL injection, because the database explicitly knows these are data, not syntax.
Option 2: Return a Prepared Statement (For Reusability)
If you need to reuse the query structure multiple times, create a prepared statement:
fn prepare_add_repo_statement(conn: &Connection) -> Result<rusqlite::Statement> { conn.prepare("INSERT INTO repositories (name, scm, path) VALUES (?1, ?2, ?3)") } // Usage later: // let mut stmt = prepare_add_repo_statement(&conn)?; // stmt.execute((&repo.name, &repo.scm, &repo.path))?;
Key Takeaways
- Never concatenate untrusted data into SQL strings: This is the #1 cause of SQL injection attacks.
- Always use parameterized queries: All reputable database libraries support this, and it’s the only reliable defense.
- Skip manual escaping: Even if you try to escape quotes, you’ll miss edge cases (like database-specific syntax or character encodings). Let the library handle safe data handling for you.
内容的提问来源于stack exchange,提问作者Amani

