// Security · 05
Injection
Untrusted text is read as code; keep data and code apart.
How it fits together
- Attacker input fails / error path Orders API (1 crafted input)
- Orders API creates Built SQL (2 build string)
- Built SQL uses / calls Orders database (3 runs as code)
Spot it
- A quote in the search box breaks the page
- Login succeeds with a crafted username
- Shell command built with string formatting
It costs
A rewrite of query-building code; dynamic SQL needs an allow-list instead of binding.
In code
using System.Data;using Microsoft.Data.SqlClient;public static class OrderQueries{ public static async Task<int> CountOpenAsync( SqlConnection connection, string customerId, CancellationToken ct) { // Unsafe: "... WHERE CustomerId = '" + customerId + "'" lets the input rewrite the query. const string sql = """ SELECT COUNT(*) FROM Orders WHERE CustomerId = @customerId AND Status = @status """; using var command = new SqlCommand(sql, connection); var customer = new SqlParameter("@customerId", SqlDbType.NVarChar, 64) { Value = customerId }; var status = new SqlParameter("@status", SqlDbType.NVarChar, 16) { Value = "Open" }; command.Parameters.Add(customer); command.Parameters.Add(status); return Convert.ToInt32(await command.ExecuteScalarAsync(ct)); }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
Injection happens when untrusted input is interpreted as part of a command: SQL, an operating system shell, an LDAP filter, a template. The root cause is mixing data and code in one string. A parameterised query sends the statement and the values separately, so the database treats a value as data, whatever characters it contains.
Parameters bind values only. A table name, column name, sort direction or keyword cannot be bound, so pick it from a fixed list in code and reject anything else. For shell commands, pass the program and its arguments as a list to the process API, with no shell in between. For LDAP filters, escape the special characters (asterisk, parentheses, backslash, NUL) before embedding input.
Validation and escaping are backups, not the fix. Input checks catch junk but cannot make every string safe for every interpreter. Also limit the damage: the application database login should not own the schema or be able to drop tables. When NOT to hand-write SQL: a query layer that always binds values gives you this by default; the risk returns the moment you drop to raw text built by hand.
Ask for it
- parameterised query / bind parameter
- string concatenation
- allow-list
- command injection
- LDAP filter escaping
- least-privilege database login
Convert each SQL built by concatenation in Orders to a parameterised query, one bind parameter per user value. Take table, column and sort names from a fixed allow-list. Run commands with an argument list, never a shell string. Add a test: searching x' OR '1'='1 returns no extra rows.