PostgreSQL Tutorial: Protecting Against SQL Injection

September 5, 2024

Summary: In this tutorial, you will learn how to secure PostgreSQL against SQL injection attacks.

Table of Contents

SQL injection is a code injection technique used to attack data-driven applications by inserting malicious SQL statements into an entry field. In the case of PostgreSQL, a robust and feature-rich database, ensuring security against SQL injections is paramount for maintaining data integrity and preventing unauthorized access.

Prepared Statements and Parameterized Queries

A parameterized query is one made in code such that values are passed in alongside the SQL statement, which has placeholder values denoting where the provided values will be inserted. A prepared statement is a reference to a pre-compiled query that is ready to accept parameters.

The most effective way to prevent SQL injection attacks is to use prepared statements with parameterized queries. This method involves creating a SQL query template and sending it to the database, with parameters that are substituted in a safe way by the database engine itself.

PREPARE plan (int, text) AS
  INSERT INTO employees (id, name) VALUES ($1, $2);

EXECUTE plan (1, 'John Doe');

This approach ensures that the database treats the values as data rather than part of the SQL command, thereby preventing an attacker from injecting malicious code.

Stored Procedures

Stored procedures are a sequence of instructions that let you store a series of queries frequently used by the application. Stored procedures offer another layer of security. They encapsulate the SQL code within the database and allow for permissions to be set on their execution, restricting who can run what within your database.

CREATE OR REPLACE FUNCTION add_employee(employee_id int, employee_name text)
RETURNS void AS $$
BEGIN
    INSERT INTO employees (id, name) VALUES (employee_id, employee_name);
END;
$$ LANGUAGE plpgsql;

By using stored procedures, you minimize the surface area for SQL injection as the input parameters can be treated securely within the procedure.

When used properly, stored procedures have additional benefits. Their use can improve performance as they are compiled once, then stored in executable form. This executable code is cached, reducing memory requirements. Procedures also support reusable code, easier maintenance, and improved security. Procedures can control the processes and activities performed, protecting the database object and simplifying security. It is harder for attackers to insert commands into SQL statements inside procedures.

Input Validation

Validating user input on both client and server sides can vastly reduce the risk of SQL injection. Ensure that every piece of user input matches expected patterns and constraints before it is processed by your application or passed to your PostgreSQL database.

In addition to language-specific input validation, using constraints when designing a database, specifically check constraints, can help as another line of defense against SQL injection. From the PostgreSQL documentation regarding check constraints, “A check constraint is the most generic constraint type. It allows you to specify that the value in a certain column must satisfy a Boolean (truth-value) expression. For instance, to require positive product prices, you could use:”

/* Adding a check that requires positive product prices */
CREATE TABLE products (
    productnum integer,
    name text,
    price numeric CHECK (price > 0)
);

This can also be accomplished on existing databases using ALTER TABLE. Subcommands such as ADD table_constraint, ALTER CONSTRAINT, and DROP CONSTRAINT also exist to help facilitate this.

Escaping All User Inputs

If you must include user input within SQL statements, ensure that the inputs are properly escaped. PostgreSQL provides functions such as quote_literal or quote_nullable, which can help escape strings that are incorporated into SQL commands.

SELECT * FROM accounts WHERE username = quote_literal(user_input);

In addition, the PostgreSQL C library (libpq) has built-in escaping functions that can be used in conjunction with the command execution functions. PQescapeLiteral is used for escaping strings for inclusion in SQL commands. As stated in the official libpq documentation, “this is useful when inserting data values as literal constants in SQL commands. Certain characters (such as quotes and backslashes) must be escaped to prevent them from being interpreted specially by the SQL parser.”. The library also includes other useful escaping functions, such as PQescapeByteaConn (used to escape binary data), and PQescapeIdentifier (used to escape strings used as SQL identifiers such as table names). Like input validation, it is best practice to use these types of built in functions, instead of “rolling your own”.

However, this method is less secure than using prepared statements or stored procedures and should be used with caution.

Least Privilege Principle

Least privilege is a security principle that advocates for granting only the minimum level of access or permissions necessary for users or processes to perform their required tasks. Applying the principle of least privilege to limit the impact of SQL involves ensuring that database users and applications have only the necessary permissions to execute SQL queries and access data. This can be accomplished by assigning database users the minimum necessary permissions required for their specific tasks. For example, if a user or service account only needs to read data from certain tables, granting them read-only access to those specific tables, rather than full read-write access to the entire database can reduce the blast radius of a SQL injection attack by limiting or preventing the attacker’s ability to modify or delete data.

Adhering to the principle of least privilege is essential in securing databases. Ensure that application database users have only the permissions necessary to perform their tasks. This minimizes potential damage if an injection attack were to occur.

Least privilege also works in conjunction with the prior mentioned stored procedures. Utilizing stored procedures to encapsulate database operations and enforce access controls by defining specific stored procedures for common database operations and granting execute permissions only on these procedures, restricts direct access to underlying tables, thus preventing SQL injection vulnerabilities.

Regularly Updating and Patching

Keeping PostgreSQL updated with the latest security patches is crucial as vulnerabilities are discovered and fixed over time. Regular updates help protect against known attack vectors.

Conclusion

In conclusion, securing PostgreSQL against SQL injection requires a combination of best practices including prepared statements, stored procedures, input validation, proper escaping of user inputs, adherence to least privilege principles, and keeping software up-to-date.