Uni-Sine
Computer ScienceCyber security

How do you prevent SQL injection attacks?

SQL injection is still one of the most common serious web vulnerabilities. In the OWASP Top 10:2025, Injection is fifth, and OWASP names SQL injection as one of its two largest contributors. The good news is that the main fix is simple, well known and built into every modern database library.

What is SQL injection?

SQL injection happens when a program pastes user input straight into a SQL query, and the input changes the meaning of the query. Here is a login check built the unsafe way:

# UNSAFE: user input glued into the SQL text
query = "SELECT * FROM users WHERE email = '" + email + "' AND password_hash = '" + pw_hash + "'"

If someone types ' OR '1'='1 as their email, the quote mark closes the string early and the rest becomes part of the SQL. The condition now includes OR '1'='1', which is always true, so the query can match rows it never should. The same trick can read other tables, change data or delete it. The root problem is that the database can't tell which part of the text was meant as code and which was meant as data.

Our Hack a Website course shows this from the attacker's side on a practice site, so you understand exactly what you are defending against. Only ever test systems you own or have written permission to test: attacking anyone else's is a crime.

1. Use parameterised queries (prepared statements)

This is the main defence, and OWASP lists it first. You write the query with placeholders where the values go, and pass the values separately. The database compiles the query first, then slots the values in as data. However strange the input, it can never become SQL.

# Python (sqlite3): ? is the placeholder
cur.execute("SELECT * FROM users WHERE email = ?", (email,))
// Node.js (mysql2): ? placeholders
const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);

// Node.js (node-postgres): numbered placeholders
const result = await client.query('SELECT * FROM users WHERE email = $1', [email]);
// Java (JDBC)
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE email = ?");
ps.setString(1, email);
ResultSet rs = ps.executeQuery();
// PHP (PDO)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);

Try spotting and fixing an unsafe query yourself, then press Mark to check it in the course.

Fill the gaps so each query uses placeholders.

Press Mark to have your answer marked in the Hack a Website course.

ORMs help, but watch the raw query functions

Object-relational mappers (ORMs) such as Prisma, Django's ORM, SQLAlchemy and Hibernate build parameterised queries for you, which removes most of the risk. Most also offer a way to run raw SQL. Those raw functions are only safe when you pass values as parameters, never by pasting them into the SQL string.

2. Use an allow-list for parts that can't be parameters

Placeholders only work for values. As OWASP puts it, they represent data values, not table names, column names or keywords like ASC and DESC. If users can choose a sort column or direction, map their choice to a fixed list you control, and never paste their text in:

const sortColumns = { name: 'name', newest: 'created_at' };
const column = sortColumns[req.query.sort] ?? 'created_at';   // only known names
const direction = req.query.dir === 'asc' ? 'ASC' : 'DESC';    // only two options
const sql = `SELECT * FROM products ORDER BY ${column} ${direction}`;

Whatever the user sends, the SQL can only ever contain one of the names you wrote.

3. Validate input against what you expect

Check that input looks the way it should: an ID is a whole number, a date is a real date, a country code is one of a known list. Reject anything else on the server, not just in the browser, because attackers can send requests without your web page. Validation is a second line of defence. It catches mistakes early but doesn't replace parameterised queries, since some valid input, like the name O'Brien, contains quote marks.

4. Give the database account the least privilege it needs

If an injection does get through, the damage is limited by what the app's database account is allowed to do. A web app that only reads and writes its own tables shouldn't connect as an administrator. Don't let it drop tables, change permissions or read other databases. OWASP lists this least privilege principle as its main additional defence.

5. Don't show database errors to users

Raw error messages can reveal table names, column names and the database type, which helps an attacker build their next attempt. Log the details on the server and show users a plain message. This makes attacks harder but fixes nothing by itself: attackers can still probe a vulnerable query without seeing errors, using techniques called blind SQL injection.

6. Be careful with stored procedures and escaping

  • Stored procedures are safe when they take parameters and use them as values. OWASP warns they are not automatically safe: a procedure that builds SQL from its arguments inside itself is just as vulnerable as application code.
  • Escaping input, adding backslashes before quote marks, is the option OWASP marks as strongly discouraged. Escaping rules differ between databases and character sets, and one missed case leaves a hole.

7. Test, review and keep software up to date

  • Code review: search your code for SQL built with string joining or template strings, especially in older parts of an app.
  • Automated scanning: static analysis tools flag unsafe queries, and authorised security testing finds what tools miss.
  • Patching: keep frameworks, plugins and off-the-shelf software updated, since injection flaws in popular products are found and fixed regularly.
  • Defence in depth: a web application firewall can block common attack patterns, but treat it as an extra layer, not a fix for unsafe code.

Real attacks that used SQL injection

  • TalkTalk, 2015: an attacker used SQL injection on three old web pages and accessed the personal details of 156,959 customers. The ICO, the UK data protection regulator, found that TalkTalk had missed two earlier SQL injection attacks on the same pages, and fined it £400,000, its largest fine at the time.
  • MOVEit Transfer, 2023: a criminal group exploited a SQL injection flaw (CVE-2023-34362) in this widely used file transfer software to steal data from many organisations, according to CISA, the US cyber security agency.

In both cases the defences above were well known. SQL injection keeps working not because it is clever, but because unsafe queries survive in old or rushed code.

Checklist

DoDon't
Use parameterised queries for every valueJoin user input into SQL strings
Map names and sort orders to an allow-listPaste column names or ASC/DESC from a request
Validate input on the serverRely on checks in the browser
Connect with a least-privilege accountConnect as a database administrator
Log errors privatelyShow raw database errors to users
Review raw SQL in ORMs and stored proceduresAssume an ORM or stored procedure makes any query safe

Common mistakes

  • Parameterising some values and joining others. One unsafe value is enough. Every value must be a parameter.
  • Blocking "bad words". Filters that strip words like SELECT or characters like quotes are easy to get around and break real input.
  • Trusting data from your own database. Data stored earlier can carry an injection that fires when it is later used to build a query. Treat it as untrusted too.
  • Validating only in JavaScript in the browser. Attackers send requests directly, skipping your page.
  • Thinking a firewall or hidden errors fixed the bug. They slow attackers down. Only safe queries fix it.

Common questions

What is the best way to prevent SQL injection?
Parameterised queries, also called prepared statements, for every value that comes from outside your code. They send the SQL and the data separately, so data can never be run as SQL.
Do prepared statements prevent all SQL injection?
They prevent it for the values they handle. They can't protect table names, column names or keywords, which need an allow-list, and they don't help if other code in the same app still joins input into SQL.
Does input validation stop SQL injection?
Not on its own. It is a useful second layer, but some valid input contains characters like quotes, and validation is easy to get slightly wrong. Use it alongside parameterised queries.
Do ORMs prevent SQL injection?
Mostly, because they build parameterised queries for you. Their raw SQL features can still be unsafe if you paste input into the query text.
Is SQL injection still a problem today?
Yes. Injection is in the OWASP Top 10:2025, and SQL injection flaws keep turning up in old code and in widely used products.
Is it legal to test a website for SQL injection?
Only with permission. Testing systems you don't own without written authorisation is illegal in most countries, including under the UK's Computer Misuse Act and the US Computer Fraud and Abuse Act. Practise on deliberately vulnerable training sites instead.

Revision cards

Say the answer first, then flip the card to check it.

Where to go next

  • Learn it

    Hack a Website

    XSS, CSRF and SQL injection: learn to defend web applications

    Start the course
Read next
Computer Science · DatabasesWhat is SQL? What it is used for, with simple examplesSQL is the language for asking databases questions. Learn what SQL is, what it is used for, the main commands and how queries work, with examples.