What Is SQL Injection? How It Works and How to Prevent It

What Is SQL Injection? How It Works and How to Prevent It

You type something into a login screen's username field, but instead of a name you paste a fragment of code. The site doesn't complain: it runs it. That is, in essence, how SQL injection works — a flaw publicly described more than 25 years ago that keeps showing up in major incidents. This post explains the mechanism with an example you can follow without being a programmer, and shows what actually fixes the problem.

What is SQL injection?

What is SQL injection in one sentence?

SQL injection is an attack in which the intruder inserts SQL commands into an input field (form, URL, cookie) and the application runs them against the database as if they were part of the legitimate query.

SQL is the language applications use to talk to databases such as MySQL, PostgreSQL and SQL Server. The problem starts when the programmer builds the query by gluing fixed text together with whatever the user typed. From that point on, the database can't tell which part is the programmer's command and which part is user data.

How the attack works, step by step

Imagine a login that runs this query, built by text concatenation:

SELECT * FROM usuarios
WHERE login = 'INPUT_LOGIN' AND senha = 'INPUT_SENHA';

If the attacker types admin'-- in the login field, the query becomes:

SELECT * FROM usuarios
WHERE login = 'admin'--' AND senha = '';

In SQL, -- starts a comment. Everything after it is ignored, including the password check. The database returns the admin user and the application lets them in. No password was cracked and no malware was installed: the system did the work itself.

What can an attacker do beyond bypassing the login?

Depending on the database permissions, they can read entire tables (users, emails, password hashes, cards), change or delete data and, in badly configured setups, even run commands on the server. The most common variants are:

  • In-band (classic): the injected query's result shows up in the page itself, using UNION SELECT to pull data from other tables.
  • Error-based: database error messages leak table and column names.
  • Blind: the page shows no data, but the attacker infers information from behavior (true or false responses, response time) and extracts the content bit by bit, usually with automated tools such as sqlmap.

Finding targets is cheap, too. Search techniques like the ones explained in Google Dorks: how hackers use Google to find exposed data help list pages with URL parameters that are candidates for testing.

Real cases: this isn't a thing of the past

  • Heartland, Hannaford and 7-Eleven (2008–2009): the US Department of Justice indicted Albert Gonzalez and others in August 2009. According to the indictment, the group used SQL injection to install sniffers on the companies' servers and intercept card data, with more than 130 million credit and debit card numbers stolen. CSO Online and Computerworld covered the case at the time.
  • Sony (2011): the LulzSec group claimed to have accessed Sony Online Entertainment data through SQL injection, as we covered in PSN down: the hack that exposed 77 million accounts in 2011.
  • MOVEit Transfer (2023): CVE-2023-34362 was a SQL injection flaw exploited at scale and became one of the largest supply chain attacks of the decade, detailed in MOVEit: 3 years of the zero-day that exposed 93 million records.

Code injection sits under the "Injection" category of the OWASP Top 10 (A03:2021), one of the best-known web application risks.

How to prevent SQL injection

What is the most effective defense against SQL injection?

Use parameterized queries (prepared statements), which keep the SQL code and the user data separate so the input can never change the structure of the query. It is the first defense recommended by OWASP's SQL Injection Prevention Cheat Sheet.

Example in Python with sqlite3 (the same principle applies in PHP/PDO, Java/JDBC, C# and Node):

# Vulnerable: concatenation
cur.execute("SELECT * FROM usuarios WHERE login = '" + login + "'")

# Safe: parameter
cur.execute("SELECT * FROM usuarios WHERE login = ?", (login,))

With the parameter, admin'-- is treated as an odd-looking username, not as SQL.

Other layers, in order of importance:

  1. Least privilege in the database: the application account should not own the database or hold admin rights. If an attack gets through, the damage is limited.
  2. Allow-list validation: for fields that can't take a parameter (column name, sort order), accept only predefined values. OWASP discourages relying on character escaping alone.
  3. Hide detailed error messages from end users; write them to the log, not to the screen.
  4. A WAF as a complement: it helps block known patterns but doesn't replace fixing the code.
  5. Update third-party software: in the MOVEit case, the vulnerable code didn't belong to the victims. Fast patching and an inventory of exposed systems matter as much as your own code.
  6. Strong password hashing: if a table leaks, the damage drops a lot. See also the Adobe 2013 case and what not to do with passwords.

What if you're not a developer?

You can't fix a site's code, but you control the damage when it leaks: use a unique password per service in a password manager (how to use Bitwarden), turn on 2FA and check whether your email showed up in breaches. If you run a site (WordPress, online store, internal system), keep plugins and themes updated, because most SQL injection flaws on small sites come from outdated plugins.

The bigger lesson

SQL injection is a design mistake: mixing command and data in the same text. The fix is a design fix too, and doesn't depend on a magic filter. To follow the trail of credential attacks after a leak, read Credential Stuffing: what it is and how it works.

Comments