How to Prevent SQL Injection in MySQL 8: Explained with Examples

Updated: January 26, 2024 By: Guest Contributor Post a comment

Introduction

SQL Injection is a form of attack that can allow attackers to execute arbitrary SQL code on a database. This can lead to unauthorized viewing of data, data manipulation, and even full control over the database. Preventing SQL injection is a critical aspect of database security.

MySQL, as one of the most popular relational database management systems, had significant improvements in security features and performance in its 8th version. This guide showcases preventative steps and coding practices to avoid SQL injection attacks in MySQL 8, including coding examples from basic to advanced levels, aiming to protect your application against these malicious attempts.

Understanding SQL Injection

Before we discuss prevention, it’s crucial to understand how SQL injection works. An attacker uses SQL injection by sending malicious SQL code through an application’s input channels, hoping that the application will concatenate that input into an SQL query and execute it. A classic example:

SELECT * FROM users WHERE username = 'given_username' AND password = 'given_password';

If an attacker provides a username of ' OR '1'='1, the query becomes:

SELECT * FROM users WHERE username = '' OR '1'='1' --  AND password = 'given_password';

This will return all rows from the users table since OR '1'='1' is always true, and the -- MySQL comment syntax will make the password check ineffective.

Using Prepared Statements (Parameterized Queries)

A fundamental technique to prevent SQL injection is to use prepared statements. Prepared statements ensure that an attacker cannot change the intent of a query, even if SQL commands are inserted by an attacker.

In the Python MySQL.connector library, use the following pattern when crafting SQL queries:

import mysql.connector
from mysql.connector import Error

try:
    connection = mysql.connector.connect(host='localhost',
                                         database='test_db',
                                         user='test_user',
                                         password='test_password')
    sql_select_query = """SELECT * FROM users WHERE username = %s AND password = %s"""
    cursor = connection.cursor()
    cursor.execute(sql_select_query, ('example_user', 'example_password'))
    records = cursor.fetchall()
    for row in records:
        print(row)
except Error as e:
    print("Error reading data from MySQL table", e)
finally:
    if connection.is_connected():
        connection.close()
        cursor.close()
        print("MySQL connection is closed")

The %s in the query are placeholders that get replaced by the corresponding user-supplied values in a tuple sent as a second argument to cursor.execute(). This ensures that the input is never treated as SQL code but strictly as data to be used for the values of the parameters.

Escaping User Input

In some cases, you may be constructing queries dynamically or using database features not supported by prepared statements. In such situations, it is important to properly escape user input to ensure that any SQL executed is done so safely.

Using the MySQL.connector library, the method converter.escape() helps to sanitize the input:

from mysql.connector import connection

conn = connection.MySQLConnection(user='your_username', password='your_password', host='127.0.0.1', database='your_db')

user_input = "user' --"
escaped_input = conn.converter.escape(user_input)

query = "SELECT * FROM users WHERE username = '" + escaped_input + "'"

However, be aware that escaping user input can still be error-prone and is not as secure as using parameterized queries.

White List Input Validation

For more control over what can be submitted, you could employ white list input validation. By this approach, you match against an approved list of inputs, and only then does the input proceed further into your application.

This can be especially useful for cases where inputs are expected to follow a specific pattern, like an enumeration or set of known values. For example:

WHITELISTED_STATUSES = {'Pending', 'Approved', 'Denied'}

status = user_input # assuming user_input is something we'll check

if status not in WHITELISTED_STATUSES:
    raise ValueError('Invalid status value')
else:
    query = "SELECT * FROM orders WHERE status = '%s'" % status
    # note: still use parameterized queries in production code

The example above is simplistic and should not replace parameterized queries in situations where they are the preferred solution. It is meant to illustrate an additional layer of validation you can apply.

Utilizing Stored Procedures

Another method to enhance security is to use stored procedures. These are precompiled SQL statements which can provide an additional layer of separation between data access and business logic. In MySQL 8, you can define a stored procedure like so:

DELIMITER $
CREATE PROCEDURE GetUser(IN user_id INT)
BEGIN
    SELECT * FROM users WHERE id = user_id;
END$
DELIMITER ;

Then invoke the stored procedure from your program:

try:
    connection = mysql.connector.connect(host='localhost', database='test_db', user='test_user', password='test_password')
    cursor = connection.cursor()
    cursor.callproc('GetUser', [1])
    for result in cursor.stored_results():
        print(result.fetchall())
except Error as e:
    print("Error: ", e)
finally:
    if connection.is_connected():
        connection.close()
        cursor.close()
        print("MySQL connection is closed")

This practice not only provides structure to the database interaction but also greatly reduces the attack surface for SQL injection.

Conclusion

In conclusion, preventing SQL injection in MySQL 8 is about being stringent with the inputs your application allows, employing best practices in query formation, and knowing the tools at your disposal. By sticking to prepared statements, validating and sanitizing user inputs, and using stored procedures, you can enforce a strong security policy against SQL injection threats.