It’s nearly 2025, yet SQL injection still ranks as the #3 most dangerous application security weakness. Despite tech industry hype suggesting that SQL databases are on their way out and it’s only a matter of time before they’re all replaced by NoSQL, GraphQL, or some other QL, SQL injections continue to feature in data breaches year after year. Clearly, relational databases are far from dead, so knowing how to prevent SQL injection is as important as ever.
Before uttering the inevitable words “parameterized queries,” let’s make one thing clear: there is nothing you can do to stop SQL attack attempts against your web apps and APIs. The vast majority of malicious probes are fully automated, so you will see bots poking around your apps with malicious SQL code payloads all the time. What you can and should do is systematically fix and prevent SQL injection vulnerabilities that would allow even one of those relentless attacks to succeed.
So, while you can’t prevent the attacks, it’s definitely possible to fix and avoid the vulnerabilities—and that’s best done with a layered approach.
Preventing SQL injection vulnerabilities
Like other injection vulnerabilities, SQL injection is possible when you mix unsanitized user input with application code, in this case when building SQL statements. SQLi prevention methods revolve around different ways to perform all the database commands required by application logic but without sending raw user-controlled input as part of the query.
Use parameterized queries (aka prepared statements)
The main secure coding practice when working with SQL queries is to avoid directly concatenating strings into a query if you can possibly help it. Parameterized queries let you do that using what are essentially query templates, where the statement itself is specified in advance (hence the other name—prepared statements), and the input data is only represented by placeholders.
To illustrate, here’s a quick and insecure way to build a query that fetches customer data by customer ID. Deployed like this, the application would be vulnerable to SQL injection via input fields that end up in the userInput
string, allowing attackers to input SQL commands for the database:
sqlQuery = "SELECT * FROM customers WHERE customerId = '" + userInput + "';"
With parameterized queries, you avoid the whole insecure and error-prone business of manually sticking strings together. Here’s a Python example for executing the same SELECT
statement more securely (where cursor
is a database access cursor over some existing connection):
cursor.execute("SELECT * FROM customers WHERE customerId = %s;", userInput)
This becomes especially important when dealing with longer and more complex queries with multiple input parameters. Prepared statements allow you to clearly separate the query logic from the input data, improving security and making it easier to work with database queries overall.
Use stored procedures
In many cases, you can completely avoid building queries yourself by calling existing data access procedures stored on the database server. Instead of having your application assemble and send an SQL query to fetch customer data by ID, you might simply call a stored procedure like getCustomerById()
that you previously defined in your database.
While they are convenient and also good for code maintenance because your application doesn’t need to know the exact current database structure each time, stored procedures are not an automatic cure for SQLi because they, too, need to be written securely. If your stored procedure just performs string concatenation internally, it’s no more secure than putting the query together yourself. The procedures also need maintenance to keep up with any database changes.
Object-relational mapping (ORM) frameworks offer a way to completely get away from working with raw SQL, especially for object-heavy programming languages such as Java.
With ORM, internal application objects are automatically stored in a database, in theory eliminating the need to explicitly execute queries and thus expose potential injection points. That said, keep in mind that ORM comes with its own limitations, plus using it is a software design decision, not a security measure in and of itself.
Perform input validation and sanitization
As part of general anti-injection hygiene, it’s always a good idea to put user-controllable inputs through some level of validation and sanitization, with value whitelisting where possible. For example, if you have a Boolean parameter for your SQL query and you’re receiving the value as a literal true
or false
string, you should do a conditional check on that value and insert your own true or false based on that (instead of directly plugging the input into your query and hoping it’s not an SQL payload).
When you know you’re only expecting some limited set of characters, it never hurts to validate against that as well—but keep in mind that this type of filtering is never a standalone defense when it comes to injection. In other words, while accepting only alphanumeric characters seems a reasonable additional security measure, be aware that merely rejecting special characters doesn’t magically protect you from SQL injection.
See the Invicti SQL injection cheat sheet for dozens of SQL injection examples and to get some idea of the countless attack payloads for getting past filters and firewalls.
Reducing the risk and impact of SQL injection attacks
Prevention is always better than cure, so not having exploitable SQLi vulnerabilities is best practice number one, two, and three. Even so, there are some additional things you can do at the level of design, configuration, and protection to further reduce the risk of successful SQL injection and minimize the impact of any attack that does succeed.
Follow the principle of least privilege in application and database design
Always a recommended practice for more reasons than security, the principle of least privilege is especially important for mitigating the impact of SQL injections. This applies at multiple levels, starting with the fundamental rule of not running production software with admin permissions—from the database server and the application itself to ensuring your app connects to the database as a restricted user without access to tables it doesn’t need.
Well-designed applications, APIs, and databases are also less at risk of extensive data breaches caused by unauthorized access, again by working only with the minimum data sets required at any given time. A more precise query to a more specialized table is less of a risk than grabbing SELECT * FROM users
each time and then only filtering data in the application.
Detecting and preventing SQL injection vulnerabilities is especially important for APIs since API endpoints are often targeted first as the direct gatekeepers of sensitive data.
Learn more about API vulnerability testing in the real world.
Use a web application firewall (WAF)
While having a WAF to stop incoming attacks before they get to your application sounds like a no-brainer and it’s always useful to have that extra layer of protection, once again this is filtering, and filtering is never fully waterproof. A firewall may weed out the most trivial single quote probes like ' OR 1=1--
that are checking for easy authentication bypasses in your login forms, but you can’t rely on it to stop more advanced SQLi attacks, especially if a more inquisitive hacker starts investigating.
Regular security practices to minimize SQLi risk
To ensure that SQLi prevention is a routine and unavoidable part of your development and security processes, follow these general cybersecurity practices:
- Run regular and automated vulnerability scans across production, staging, and development to find and fix any SQLi flaws before they can be exploited. Modern DAST scanners are extremely good at finding these vulnerabilities, including more advanced types of SQL injection like blind and out-of-band injections. The best DAST tools can even safely exploit a vulnerability and show you a proof of exploit.
- Periodically train and test programmers on secure coding practices in general and preventing SQL injection in particular. Unlike some other vulnerabilities, SQLi flaws are relatively easy to understand, fix, and prevent, provided that training and tools emphasize the right approach rather than quick fixes.
- Keep your tech stack and software components updated and patched. SQL-backed content management systems like WordPress (and their plugins) are especially at risk, considering their complex structure and widespread use. Use static and dynamic software composition analysis (SCA) tools to identify vulnerable components, and outdated technology detection tools to keep your tech stack secure.
Frequently asked questions
Is NoSQL injection the same as SQL injection?
NoSQL databases can also be vulnerable to injection attacks, but NoSQL injection is very different from SQL injection. All SQL databases use the same standardized query language, so most SQL injection payloads will, with minor tweaks, work on SQL Server, MySQL, Oracle, and most other SQL databases. There is no common query language for NoSQL databases, so NoSQL injections are always product-specific.
Learn more about NoSQL injection
Does using GraphQL prevent SQL injection?
No, GraphQL only provides a structured query language for APIs and doesn’t by itself prevent injections into the underlying databases. If you have a GraphQL interface that incorporates unsanitized inputs in queries sent to an SQL database backend, SQL injection via GraphQL is possible.
Learn more about GraphQL API security
Can a firewall stop SQL injection attacks?
Network-level firewalls cannot detect or stop any application-level attacks such as SQL injection. Web application firewalls (WAFs) can filter out some common SQL injection payloads but should never be trusted to catch every possible attack due to the huge number of possible payloads and variations.
Learn more about web application firewalls (WAFs)