SQL injection prevention cheat sheet
This SQL injection prevention cheat sheet provides developers with actionable strategies to protect web applications from one of the most critical security risks. It explains attack types, showcases safe coding patterns, and emphasizes testing and monitoring for robust protection.
Your Information will be kept private.
Begin your DAST-first AppSec journey today.
Request a demo
SQL injection (SQLi) continues to be one of the most dangerous and persistent security vulnerabilities in web applications. Even with advances in frameworks and development practices, the risk of exposing sensitive data through flawed SQL statements remains high. This cheat sheet walks through practical techniques for preventing SQL injection vulnerabilities, focusing on what developers can do at the code level to build secure, resilient web apps.
Types of SQL injection attacks
SQL injection vulnerabilities are a wide class of security flaws that allow an attacker to supply database queries or commands in input data and have them executed by the database server. There are different types of SQLi depending on where and how the SQL code is injected:
- In-band (“regular”) SQL injection: Directly injecting malicious SQL into inline queries. This includes both error-based and union-based SQL injection.
- Blind SQL injection: Inferring data using Boolean logic or time-based techniques when no error messages are shown. Typically relies on observing page behavior, response content, or time delays.
- Out-of-band SQL injection: Exploiting methods like DNS or HTTP requests to extract data when direct responses aren’t available.
- Second-order SQL injection: Injecting a malicious payload that only triggers an SQL injection later in the application’s workflow. Often missed during testing because the payload is not executed immediately.
To see a wide variety of SQL injection examples, check out the Invicti SQL injection cheat sheet.
Best practices for preventing SQL injection vulnerabilities
Avoid building SQL queries using string concatenation
Directly including user data in a string query is inherently unsafe. Whenever you concatenate user inputs with a database query, you are mixing application data and logic, leaving room for an attacker to control application behavior. An insecure concatenated query in PHP might look like this:
$query = "SELECT * FROM users WHERE userid = " . $_GET['userid'];
If an attacker is able to supply SQL code as the userid
parameter value, the application will be vulnerable to SQL injection.
Build queries safely with parameterized queries or prepared statements
The number one rule for preventing SQL injection is never to insert user input directly into SQL code. Whether working with MySQL, PostgreSQL, Oracle, or Microsoft SQL Server, always use parameterized queries or prepared statements. These separate user input from SQL syntax, ensuring that even special characters cannot alter the query logic.
Here’s a Java example of creating a prepared statement:
// Make sure conn is a valid open Connection, and handle exceptions properly in production code
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE userid = ?");
stmt.setInt(1, userId); // userId holds the user-supplied value
ResultSet rs = stmt.executeQuery();
A similar example for PHP using PDO:
// Remember to set PDO::ATTR_ERRMODE to handle errors properly
$stmt = $pdo->prepare("SELECT * FROM users WHERE userid = :userid");
$stmt->execute(['userid' => $userId]); // $userId holds the user-supplied value
Validate input to reduce risk and enforce intent
Parameterization stops injection, but input validation is also important to improve resilience, preventing both attack attempts and data entry errors. Use allowlists (aka whitelisting) to restrict inputs for known closed-set parameters. Sanitize inputs to check that integers are really integers, strings fit expected patterns, and unexpected values like NULL or special characters are handled gracefully. For example, to enforce integer values in PHP, you might use:
if (!filter_var($userid, FILTER_VALIDATE_INT)) {
throw new Exception("Invalid userid");
}
This eliminates the risk of (potentially malicious) string values in the userid
parameter. Note that this only validates the data format and does not replace the need for parameterized queries.
Handle stored procedures carefully
Stored procedures can improve security, but only if they use parameterized inputs and avoid dynamic SQL. If you use any dynamic scripting inside your procedures, you are reintroducing the injection risk. For example, in Oracle, you could define this procedure to get a user by ID and then call the procedure instead of executing a query from the application code:
CREATE PROCEDURE GetUser(p_userid IN NUMBER) AS
BEGIN
SELECT * FROM users WHERE userid = p_userid;
END;
Handle errors securely
Avoid exposing detailed error messages that reveal table names, column names, or query structure. Return generic error messages to users and log details on the server side. As an additional benefit, standardizing your error handling and messages may prevent or hinder some timing-based attacks.
Monitor and log queries
Monitoring helps detect unusual query patterns like repeated Boolean conditions or time-based delays that may indicate blind SQL injection attacks. Also look for unexpected SQL commands or failed authentication attempts in your logs, covering both database-level logging and application-level monitoring. As a more general precaution, sensitive data should never be written to logs.
Keep your tech stack updated
Regularly update not only your application components but also ORMs, database drivers, the DBMS software itself, and the back-end operating systems. This holds equally for open-source and commercial platforms, from MySQL and PostgreSQL to Oracle or Microsoft SQL Server. Staying up to date helps to minimize exposure to reported vulnerabilities and thus reduce the overall attack surface.
Apply the principle of least privilege
While this will not directly prevent attacks, as a best practice, your application should connect to the database server using a low-privilege account and have access only to the tables and columns required for its operation. This helps to minimize impact and restrict escalation if an attacker does find and exploit an SQLi vulnerability somewhere. Similarly, the database server itself should run with only the minimum required permissions and features to limit the options available to a successful attacker.
Use ORMs carefully
Object-relational mapping (ORM) systems are commonly used to interface between object-oriented programming languages (most often Java) and relational databases. While modern ORMs can entirely abstract away the query language and reduce the risk of injection, you still need to know your ORM’s parameterization features and use them correctly. If you fall back to raw SQL queries, you will need to ensure their security in the same way as with no ORM at all.
Follow secure coding practices—and relentlessly scan for vulnerabilities
Applying all these recommendations will go a long way to avoiding SQL injection weaknesses in your applications, but the only way to be sure is to test and test again. Every piece of software should be scanned for security vulnerabilities, including SQL injection, and scanned at multiple points in the development lifecycle.
Static application security testing (SAST) tools can flag some of the most obvious risks already at the early stages of coding, but making sure the running app doesn’t have exploitable vulnerabilities is a job for dynamic application security testing (DAST). With advanced DAST tools such as Invicti, you can run security checks in your pipeline and in production to catch a wide variety of SQL injection vulnerabilities, including out-of-band weaknesses. And with Invicti’s proof-based scanning, exploitable SQLi is reported to you complete with a detailed proof of exploit so you know the issue is real.
Final thoughts
Preventing SQL injection takes more than applying a single fix. It requires combining parameterized queries, input validation, least privilege, error handling, and continuous testing to build resilient web apps. Using the recommendations in this cheat sheet alongside a DAST-first approach to comprehensive application security will help you prioritize real, exploitable risks and strengthen the security of your users, systems, and data against one of the oldest and still most impactful classes of attacks in web application security.