What Is SQL Injection And How Do I Prevent It in C#?
SQL injection is a code injection technique that lets attackers send malicious SQL code to your database server through user input. In his video “What Is SQL Injection And How Do I Prevent It in C#?”, Tim Corey demonstrates exactly how SQL injection vulnerabilities appear in real code, shows several successful SQL injection attack examples (including union-based and destructive attacks), and walks through practical SQL injection prevention techniques you can apply in C#. This article follows Tim’s walkthrough so you can see the exact problems and fixes he shows.
Demo app and why it matters
Tim starts with a small WPF demo app tied to a local InjectableDB (People and Secrets tables). The app’s web form-like search box takes user input (a last name) and builds a SQL query to return ID, first name and last name. It “works” — type Corey and you get Tim Corey — but Tim emphasizes the core point: “Just because an application works doesn't mean it's safe.” A working web application can still have SQL injection vulnerabilities when user supplied input is inserted directly into SQL statements via string concatenation or dynamic SQL.
The unsafe code — string concatenation and dynamic SQL
Tim shows the exact unsafe pattern many devs use:
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);This original query uses string concatenation to create a SQL statement. Tim warns: if you see code that injects user inputs directly into SQL queries, stop — this is a SQL injection vulnerability. Attackers can craft malicious input that changes the structure of your SQL commands or even runs additional malicious SQL statements.
How an attacker exploits it — UNION and DROP
To show how a SQL injection attack works, Tim reproduces queries in SQL Server and then crafts injections using UNION ALL and SQL comments (--) to hide trailing characters. Example malicious payloads Tim demonstrates:
Union-based SQL injection to read other tables:
UNION ALL SELECT ID, Username AS FirstName, Password AS LastName FROM Secrets;This mixes results from Secrets into the original SELECT result set, exposing sensitive data like usernames and passwords.
Destructive injection to drop tables:
DROP TABLE DemoTable;This runs a second SQL statement (DROP TABLE) by terminating the first statement with a semicolon and then adding the destructive command. Tim shows the table disappears — the database has been modified by malicious SQL.
Tim’s point: attackers don’t need to know table or column names ahead of time — they can enumerate table or column names from database servers, or simply try blind or timing-based techniques to discover behavior.
Fix 1 — Parameterized queries
Tim’s first and primary defense is to stop building SQL strings with user data. Replace dynamic SQL with parameterized queries:
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });Tim explains that parameterization (prepared statement style usage) means the database treats the user supplied input strictly as data — malicious SQL becomes just a string value and cannot change the SQL structure. This prevents many common SQL injection attacks including union-based payloads and appended ; DROP TABLE commands.
He also recommends pairing parameterization with minimal input validation: sanitize or block characters unlikely in a last name (e.g., semicolons or -- comment markers) while allowing legitimate characters like apostrophes (O’Reilly). Parameterized queries + input cleansing give substantial protection against SQL injection attacks.
Fix 2 — Stored procedures
Tim next shows two stored procedures: an unsafe stored procedure that concatenates SQL inside the proc and then executes it, and a safe stored procedure that uses parameters directly.
Unsafe stored procedure builds a @sql string from the parameter and EXECs it — still vulnerable to injection.
- Safe stored procedure performs SELECT ... WHERE LastName = @LastName and executes with the parameter — safe.
Tim clarifies: stored procedures are not an automatic cure if you still build dynamic SQL inside them. But when used properly (no dynamic SQL), stored procedures help centralize SQL statements, making it easier to parameterize and audit queries. Stored procedures can also help simplify SQL injection prevention in your app.
Don’t trust any data — not even database data
An important, often-missed point Tim raises: you mustn't blindly trust data retrieved from your own SQL database. Attackers sometimes plant malicious payloads in columns (a “ticking time bomb”) that will later be concatenated into dynamic SQL by another process. Tim insists: always use parameters and sanitize data at every step — whether it comes from a web form, a file upload, or your own database — so malicious input cannot later become an avenue for injection.
Bonus tip — least privilege and limiting database privileges
Beyond code fixes, Tim recommends defensive configuration: limit database privileges for your application accounts. In his demo the connection uses an admin account via integrated security — dangerous. Instead, use the principle of least privilege:
Create a database account for the app with only the rights it needs.
If you use stored procedures, grant that account only EXECUTE permission on specific stored procedures and nothing else.
- Do not give application accounts broad administrator privileges that allow DROP TABLE, listing all tables, or reading other databases.
This reduces the impact of a successful SQL injection attack — even if injection is possible, the attacker can’t do more than the account is allowed.
Tim also notes Entity Framework complicates this: EF often requires elevated permissions (migrations, schema changes). If you use EF in production, plan its permissions and deployment carefully.
Recap — stop, parameterize, sanitize, limit
Tim wraps up his video with a clear checklist to prevent SQL injection in C# applications:
Stop building SQL statements with string concatenation or dynamic SQL that includes user input.
Use parameterized queries / prepared statement patterns so user data is always treated as data.
Sanitize input where appropriate (block semicolons, SQL comments, unexpected characters).
Prefer safe stored procedures (no dynamic SQL inside them) for centralizing query logic.
Apply least privilege to database accounts — limit what the app’s database user can do.
- Review code (especially places that build SQL dynamically) and test for SQL injection flaws.
Tim’s final warning: sloppy handling of user inputs, dynamic SQL, and excessive database privileges can lead to serious breaches — leaked sensitive data, destroyed tables, or long-running undetected exfiltration. Treat SQL injection prevention as a core security requirement, not an optional polish.
