High risk Website · SQL injection
SQL injection found: what it means and how to fix it
Short answer: a page on your site sends what visitors type straight into a database command. An attacker can type database instructions instead of a name or a search term, and make your database hand over, change or delete data, or let them past a login without a password.
The fix: use parameterized queries (prepared statements) everywhere visitor input reaches the database, give the site's database account only the rights it needs, and update the plugin or software that has the flaw. Treat it as urgent.
What is SQL injection?
Most websites keep their data in a database and talk to it in a language called SQL. A search box might run something like SELECT * FROM products WHERE name = 'shoes'. If the site builds that command by gluing in whatever the visitor typed, a visitor can type characters that end the intended command and start their own.
The classic example is a login form: typing ' OR '1'='1 as a password turns "find the user with this password" into "find any user", and the site logs them in. Real attacks use automated tools that try thousands of variations and pull out data one piece at a time.
Why it's so serious
- Data theft. Customer records, emails, password hashes, orders, sometimes payment details: whatever the database holds.
- Data changes. Prices, account balances, user roles, or quietly creating an admin account.
- Data loss. Deleting tables, or holding the data for ransom.
- Server takeover. On some database setups, SQL injection leads on to running commands on the server itself.
- Legal duties. If personal data is exposed, breach-notification laws may require you to tell customers or a regulator.
Injection, which includes SQL injection, has been in the OWASP Top 10 list of the most critical web application risks for about two decades.
- 443 SQL Injection Found by the Deep Scan's OWASP ZAP test High
How it was found
- Deep Scan (OWASP ZAP): ZAP sends carefully chosen inputs to your forms and links and watches for database error messages, changes in the page, or delays that only happen when the input reaches the database. Its finding names the page and the parameter, for example
idon/product.php. ZAP rates confirmed SQL injection High. - Free Basic Scan (Nmap): Nmap's
http-sql-injectioncheck tries a few simple inputs on links it finds and reports "possible" SQL injection when the page reacts like a database error. Treat it as a strong hint that needs checking, not as proof.
How to confirm it safely
Don't try attack strings from the internet against a live site with real data. Instead:
- Give the report to whoever maintains the site: your developer, agency or hosting provider. The page and parameter in the report tell them exactly where to look in the code.
- Look at the code behind that page. If it builds a query by joining strings with user input, for example
"... WHERE id = " + id, it's vulnerable whether or not the scanner is right about this exact page. - Check your logs for unusual requests to that page: lots of quote characters, the words
UNION,SELECTorSLEEPin the address, or database errors. Those suggest someone has already been probing it.
Only test sites you own or have written permission to test.
How to fix it
1. Use parameterized queries everywhere
With a parameterized query, the command and the visitor's input travel to the database separately, so input can never become part of the command. This is the fix that actually works.
| Language | Vulnerable | Safe |
|---|---|---|
| PHP (PDO) | $db->query("SELECT * FROM users WHERE email = '$email'") | $s = $db->prepare("SELECT * FROM users WHERE email = ?"); $s->execute([$email]); |
| Node.js (mysql2) | db.query("SELECT * FROM users WHERE email = '" + email + "'") | db.execute("SELECT * FROM users WHERE email = ?", [email]) |
| Python | cur.execute(f"SELECT * FROM users WHERE email = '{email}'") | cur.execute("SELECT * FROM users WHERE email = %s", (email,)) |
Frameworks and ORMs (Laravel's Eloquent, Django's ORM, Rails' ActiveRecord, Prisma, Entity Framework) use parameters automatically, as long as you don't drop down to raw SQL built from strings.
2. WordPress and other CMS sites
- Update WordPress, plugins and themes. Most WordPress SQL injection is in a plugin with a published fix. If the vulnerable page belongs to an abandoned plugin, remove it.
- In your own code, use
$wpdb->prepare()for every query that includes user input.
3. Limit the damage
- Least privilege: the website's database account should only have the rights the site needs, usually reading and writing its own tables. It should never be the database administrator account.
- Hide database errors from visitors. Show a plain error page and log the details privately. Detailed errors make attacks much easier.
- Keep the database off the internet: see the exposed database guide.
4. A firewall as a stopgap
A web application firewall, such as Cloudflare's WAF or ModSecurity with the OWASP Core Rule Set, blocks many common attack patterns while the code is being fixed. Attackers often get around firewalls, so don't treat one as the fix.
5. If it was exploitable, assume it was used
Check the logs, change the database password and any API keys stored in the database, reset passwords for admin accounts, and check whether personal data could have been read. If it could, get advice on your notification duties.
6. Re-scan
Run another Deep Scan after the fix to confirm the finding is gone.
Common questions
Isn't escaping input enough?
Escaping helps, but it's easy to get wrong in one place out of hundreds. Parameterized queries remove the problem by design.
The scan says "possible". Should I still act?
Yes. Have the code behind that page checked. A "possible" finding from the free scan is worth a Deep Scan or a developer's look.
Do I need the Deep Scan to find SQL injection?
For a thorough test, yes. The free Basic Scan only tries a few simple checks; the Deep Scan's ZAP test works through your pages and forms. Compare both on the website scanner page.
Sources: OWASP: SQL Injection · OWASP SQL Injection Prevention Cheat Sheet · ZAP alert: SQL Injection · Nmap http-sql-injection · WordPress: wpdb::prepare()