AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

SQL Injection: Preventing Database Attacks

📚 Database Security⏱️ 24 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 24 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Suppose your school switches to a digital fee-payment portal. To log in, you type a username and a password into two boxes and click "Submit." Somewhere on a server, a program takes what you typed and turns it into a question for the database — something like "find the row where the username matches what was typed and the password matches too." If it finds a matching row, you're in.

Now suppose a visitor to that same login page does not type a normal username. Instead, in the username box, they type:

admin'-- 

and leave the password box empty or fill it with anything at all. On a carelessly built system, this single line of text — nine characters, the last one a plain space, no hacking software, no stolen password — logs that visitor in as the admin, with full access to every student's fee records. This is not science fiction. It is one of the oldest and still one of the most common ways real websites get broken into, and it has a name: SQL injection. By the end of this chapter you will understand exactly why that nine-character string works, why it is not "black magic," and — more importantly — exactly what a programmer must do so that it never works.

How a login box turns into a database question

To understand the attack, you first need to see how the server actually builds its question to the database. Databases understand a language called SQL (Structured Query Language). A typical command to search a table looks like this:

SELECT * FROM users WHERE username = 'rahul_9a' AND password = 'MyPass123';

Read in plain English: "From the users table, give me every row (* means all columns) where the username column equals rahul_9a AND the password column equals MyPass123." If the database finds a row satisfying both conditions, the login succeeds; if it finds zero rows, it fails.

The server does not have this exact sentence sitting ready in advance — your username and password are different every time someone logs in. So the programmer writes code that builds the sentence by gluing pieces of text together, with the values you typed inserted in the middle. In simple pseudocode, close to how it is often written in PHP, Python, Java, or Node.js when a programmer is not careful, it looks like this:

username = get_value_from_login_form("username")
password = get_value_from_login_form("password")

query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "';"

run_on_database(query)

Trace through this with the normal input rahul_9a and MyPass123. The three pieces — the fixed text before, the username, the fixed text between, the password, and the fixed text after — are joined end to end (this is called string concatenation, and the + symbol here means "join text," not "add numbers"). The result is exactly the query shown above, and it behaves exactly as intended.

The problem is that run_on_database cannot tell the difference between "text the programmer wrote as part of the command" and "text a random visitor typed into a form." Both arrive glued into the same string, and the database engine reads the entire string as one command, obeying every SQL keyword and punctuation mark it finds inside it — including any that the visitor supplied. This is the single root cause of every SQL injection attack: user input is trusted as if it were code, when it is actually just data that happens to be sitting next to code.

Attack 1: Commenting your way past a password check

Now trace through the malicious input from the opening example. The visitor types admin'-- (that's the word admin, then an apostrophe, then two hyphens, then a space) into the username field, and leaves the password field with anything in it, say xyz.

Follow the same gluing process, one piece at a time:

  1. Fixed start: SELECT * FROM users WHERE username='
  2. Username value inserted: admin'--
  3. Fixed middle: ' AND password='
  4. Password value inserted: xyz
  5. Fixed end: ';

Glued together, the final string sent to the database is:

SELECT * FROM users WHERE username='admin'-- ' AND password='xyz';

Here is the key fact that makes this dangerous: in SQL, two hyphens followed by a space (-- ) start a comment — everything from that point to the end of the line is ignored by the database engine, exactly the way // works in many programming languages you may already know, such as JavaScript or Java. (The trailing space matters: in strict SQL, and in MySQL specifically, -- only counts as a comment marker when followed by whitespace, which is why the attacker's input ends in a space rather than just two hyphens.)

So the database does not see the query the programmer intended. It sees:

SELECT * FROM users WHERE username='admin'    [rest of the line is a comment, ignored]

The password check has vanished entirely — not because it failed, but because the database never even read it. The query now simply asks "give me the row where username equals admin," and if such a row exists, it is returned and the login logic treats it as a success, no correct password required. The attacker never needed to know the admin's password. They needed to know one fact about how the server builds its query, and one detail of SQL comment syntax.

Attack 2: Turning a search box into "show me everything"

Login bypass is one goal. A different, equally common goal is making a search feature reveal data it should never reveal. Consider an online bookstore's "search by category" feature, which builds this query:

SELECT title, author, price FROM books WHERE category = 'Fiction';

Here the input comes from a search box, and there is only one condition in the WHERE clause — no AND to comment away. If a visitor types, into the category box:

x' OR '1'='1

the assembled query becomes:

SELECT title, author, price FROM books WHERE category='x' OR '1'='1';

Here '1'='1' is a condition that compares the text "1" to the text "1" — which is always true, for every single row in the table, regardless of what that row's actual category is. Since the WHERE clause is now "category='x' OR (always true)," and OR only needs one side to be true, the entire condition evaluates to true for every row in the books table. The query returns the whole table — every book, regardless of category — instead of the handful of fiction titles it was designed to show. This particular trick, appending OR '1'='1, is the single most famous SQL injection payload, because it demonstrates the core idea with almost no typing: turn a narrow filter into a filter that accepts everything.

Common misconception: "OR '1'='1' always breaks the login too"

Students who have just seen Attack 2 often assume the same trick — typing ' OR '1'='1 into both the username and password boxes of the login form — will always bypass any login, since it "always" makes conditions true. This is not reliably correct, and the reason is a piece of algebra you already know from arithmetic.

In SQL, just as multiplication is evaluated before addition in an expression like 3 + 2 × 0 (which is 3 + 0 = 3, not 5 × 0 = 0), the AND operator is evaluated before OR whenever both appear in the same expression without parentheses. Suppose the login query has two conditions joined by AND, and an attacker injects only x' OR '1'='1 into the username field, leaving the password field with an incorrect guess. The resulting WHERE clause is:

username='x' OR '1'='1' AND password='wrongguess'

Because AND binds tighter, the database groups it as:

username='x' OR ('1'='1' AND password='wrongguess')

'1'='1' is true, but password='wrongguess' is false, so true AND false is false. The whole expression becomes username='x' OR false, which is only true if a user literally named "x" exists — the bypass fails. This is exactly why Attack 1 above used the comment trick (--) instead of the OR trick: commenting out the password check entirely sidesteps AND/OR precedence altogether, while the plain OR trick only reliably works against a single, uncombined condition, like the one-condition search box in Attack 2. Precedence is not a footnote here — it is the difference between an attack that works and one that silently fails.

Attack 3: Reading a completely different table with UNION

The most damaging injections do not just widen an existing search — they pull data out of tables the query was never supposed to touch, using SQL's UNION keyword, which stacks the results of two SELECT statements into one combined result. UNION has one strict rule: both SELECT statements must return the same number of columns.

Return to the bookstore search, which selects three columns — title, author, price — from the books table. Suppose there is also a users table storing username and password. An attacker who has guessed the column count (often by trial and error, feeding in different numbers of commas until the page stops showing an error) types into the category box:

nonexistent' UNION SELECT username, password, 1 FROM users -- 

The assembled query:

SELECT title, author, price FROM books
WHERE category='nonexistent' UNION SELECT username, password, 1 FROM users -- ';

The first SELECT finds zero books (no category is named "nonexistent"). The UNION then appends a second, completely unrelated result set — every username and password from the users table — dressed up to look like it belongs in the title, author, and price columns (the harmless number 1 is supplied as a filler value to satisfy the "price" column's slot). The bookstore's own search page, with no visible error, now lists every account's credentials on screen as if they were book titles. This is why SQL injection is treated as a critical vulnerability rather than a minor bug: a flaw in one small search box can expose data from tables that box was never designed to reach at all.

Defense 1: Parameterized queries — the real fix

Every attack above depends on one habit: building the SQL command by pasting raw user text directly into it. The actual fix is not to "clean up" that text with clever tricks — it is to stop pasting text into commands at all. Instead, the query's fixed structure is sent to the database separately from the values that fill its blanks, using placeholders. This technique is called a parameterized query or prepared statement. Compare directly with the vulnerable version:

# VULNERABLE — builds one string, values glued into the command
query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "';"
run_on_database(query)

# SAFE — placeholders (%s) in the command, values sent separately
query = "SELECT * FROM users WHERE username=%s AND password=%s;"
run_on_database(query, (username, password))

The difference looks small, but the mechanism underneath is completely different. In the safe version, the database engine first receives only the fixed text with two blank placeholders and compiles it into a fixed query plan — at this stage it locks in exactly two things it will ever do: compare a username column to some value, and compare a password column to some value. Only afterwards does it receive the two actual values, over a separate channel, and drop them into the blanks as pure data. Even if username arrives containing admin'-- , the database does not re-read that text looking for SQL syntax — it was never going to; the query's shape was already fixed before the value showed up. The apostrophe and the two hyphens are treated as four ordinary characters to search for in the username column, nothing more. There is no username in the table literally spelled admin'-- , so the query correctly finds no match. This is why parameterized queries close every one of the three attacks shown above at once: they do not patch individual tricks, they remove the mechanism (user text being interpreted as SQL) that all three tricks depend on.

Defense 2: Input validation, as a second layer

Parameterized queries should always be the primary defense, but checking that input looks reasonable before it is even used adds a useful second layer. An employee ID field that should only ever contain digits, such as EMP1042's numeric part, can safely reject any input containing letters, quotes, or symbols — an allowlist approach, where you state exactly what is permitted and reject everything else. This is far safer than a blocklist approach that tries to list "dangerous" characters like the apostrophe and strip or reject them, because blocklists are easy to miss cases for (encoded characters, alternate comment styles, or fields where an apostrophe is legitimately needed, such as a surname like O'Brien). Validation narrows the attack surface; it is not, by itself, a substitute for parameterized queries, because plenty of legitimate-looking text (a book category, a search term, a comment) cannot be restricted to "digits only" and still needs the query-building mechanism itself to be safe.

Defense 3: Give the application's database account the least power it needs

A separate, often-overlooked defense limits the damage an injection can do even if one somehow slips through. The database username and password that a web application itself logs in with (not a human user's login — the application's own backend account) should hold only the permissions it actually needs: typically, reading and writing rows in its own tables. It should not hold the power to delete entire tables, read other applications' databases on the same server, or create new database accounts. This principle is called least privilege. It is the reason that a much-shared cybersecurity comic depicting a boy named "Robert'); DROP TABLE Students;--" remains a well-known illustration of the danger of destructive injected commands — though whether such a semicolon-separated second command actually executes depends on the specific database driver in use, since many modern parameterized-query interfaces only ever execute a single statement per call and simply reject or ignore anything appended after a semicolon. Least privilege matters precisely because you cannot always predict which interfaces will and will not allow a stacked command through; restricting what the account is allowed to do turns a potential catastrophe into, at worst, a contained one.

Misconception 2: "Client-side checks in the browser are enough"

Many student projects add a JavaScript check that disables the submit button until the username field "looks valid," and assume this blocks bad input. It does not, and the reason matters: JavaScript validation runs inside the visitor's own browser, which the visitor fully controls. An attacker does not need to use your form at all — tools built into every browser (the developer console), or simple command-line programs, can send a request directly to your server with any text whatsoever in the username field, skipping your JavaScript entirely. Client-side validation is a convenience that gives ordinary users a faster error message; it is never a security boundary, because the server can never trust that the request in front of it actually came from your form. The only checks that matter for security are the ones performed on the server, on the data as it arrives — which is exactly where parameterized queries do their work.

Seeing the two paths side by side

The diagram below traces both paths a login attempt can take once it reaches the server: string concatenation, where attacker-controlled text merges into the command itself, versus a parameterized query, where the command's shape is fixed before any value is attached.

Same malicious input, two different query mechanisms admin'-- VULNERABLE: concatenation "...username='" + input + "' AND password=..." input text merges into command SELECT * FROM users WHERE username='admin'-- ' AND password='xyz'; -- comments out password check Logged in as admin — no password SAFE: parameterized query "...username=%s AND password=%s;" (fixed first) query shape compiled before input input sent as DATA, not code: username = "admin'-- " (literal) password = "xyz" (literal) quotes/dashes are just characters No such username — login denied

Where this fits in your CBSE syllabus

Your Computer Science / Informatics Practices coursework already introduces SQL commands like SELECT, WHERE, INSERT, and comparison conditions when working with database connectivity. SQL injection is what happens when those exact same commands are built carelessly from outside input rather than fixed in advance — it is not a separate, exotic topic, but a direct consequence of how WHERE clauses and string values combine. This is also why "cyber safety" and "safe computing practices," covered in your syllabus's societal-impact strand, are not just about strong passwords or avoiding suspicious links: the code a programmer writes is itself part of what keeps (or fails to keep) other people's data safe, whether that data is book prices or classmates' fee records.

Check your understanding

Work through each question before checking the answer that follows it.

1. A "find student by roll number" feature builds this query: SELECT name, marks FROM students WHERE roll_no = 'INPUT';. A visitor types 0' OR '1'='1 into the roll number box. Write out the exact final query string, and state what it returns.

Answer: SELECT name, marks FROM students WHERE roll_no='0' OR '1'='1';. Since '1'='1' is always true and this is a single condition joined only by OR, the whole WHERE clause is true for every row — it returns every student's name and marks, not just roll number 0.

2. Using the same login query, WHERE username='...' AND password='...', an attacker types x' OR '1'='1 into the username box only, and types a random wrong guess, abc, into the password box. Does this bypass the login? Show the grouped expression to justify your answer, and explain why admin'-- in the username box works instead.

Answer: The assembled clause is username='x' OR '1'='1' AND password='abc'. AND is evaluated before OR, so it groups as username='x' OR ('1'='1' AND password='abc'). '1'='1' is true but password='abc' is false, so the AND group is false, leaving just username='x' — false unless a user literally named "x" exists. The bypass fails. The comment trick works instead because -- deletes the AND password='...' condition from the query entirely, leaving only username='admin' with nothing left for AND to combine with — it sidesteps precedence rather than fighting it.

3. Rewrite this vulnerable line as a parameterized query: query = "SELECT * FROM books WHERE category='" + category + "';".

Answer: query = "SELECT * FROM books WHERE category=%s;" followed by running it with category passed as a separate parameter, e.g. run_on_database(query, (category,)) — the value is never glued into the command text.

4. A programmer says, "I fixed the injection risk by writing code that deletes any apostrophe the user types before building the query." Explain one weakness of this fix.

Answer: It is a blocklist, not a real fix — it depends on the programmer having thought of every dangerous character and pattern (comment markers, encoded quotes, UNION-based tricks that need no apostrophe at all in some contexts) and it also breaks legitimate data, such as a surname like O'Brien, by silently mangling it. The reliable fix is a parameterized query, which does not need to guess which characters are "dangerous" because it never lets any input character be interpreted as part of the command in the first place.

Summary

SQL injection happens whenever a program builds a database command by directly pasting in text a user typed, letting that text's punctuation — apostrophes, comment markers, UNION keywords — be read and obeyed by the database as part of the command rather than treated as plain data. A comment marker (-- ) can delete an entire password check; an OR condition that is always true can widen a single-condition filter to match every row; a UNION can staple an unrelated table's data onto a search result, as long as the column counts line up. AND binds tighter than OR, exactly as multiplication binds tighter than addition, which is why some naive injection attempts fail against multi-condition queries while the comment trick still succeeds. The dependable defense is the parameterized query: sending the command's fixed structure to the database separately from the values that fill its blanks, so that no value — however strange, however full of quotes and dashes — can ever be read as anything but literal data. Input validation and a low-privilege database account are useful additional layers, but they are not substitutes for that one core habit, and neither is a JavaScript check running in a browser the attacker fully controls.

Think About It

Think about this: How would you explain sql injection: preventing database attacks to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

← XSS and CSRF: Protecting Against Common AttacksRate Limiting: Protecting APIs from Abuse →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn