How To Mitigate SQL Injection in PL/PgSQL
By: Gowthami Kandala
Tags: Postgresql, Sql Injection, Information Security

Hi Folks,

Hope everyone is doing good and staying safe at home. I have been working in Information Security from 4 years and my interest lies in finding security issues by performing code reviews.

Source Code Review has always been an important aspect of security, unfortunately I haven’t seen people focusing on it. It’s because of lack of proper resources, guidelines and the lack of coding skills. Keeping that in mind, I have decided to come up with a series of articles which will help you in understanding and securing application from the code perspective. Knowing how to read code is a major plus in the security field, it will help you to understand what exactly is happening in the application behind the GUI.

If you’re new to Source Code Review, I’ll suggest you to watch this presentation which I gave in InfoSecGirls https://www.youtube.com/watch?v=s7ftkf9IaXE. It will help you to understand the basics. In case you have any doubt, feel free to contact me.

SQL Injection was first discovered in 1998, surprisingly, it still continues to plague web applications across the internet. This article will deal with the methodology of securing PostgreSQL DB from SQL Injection at code level.

PostgreSQL

A stored procedure and user-defined function (UDF) is a set of SQL and procedural statements (declarations, assignments, loops, flow-of-control etc.) that are stored on the database server and can be invoked using the SQL interface.

PostgreSQL allows you to extend the database functionality with user-defined functions by using various procedural languages, which are often referred to as stored procedures.

With stored procedures you can create your own custom functions and reuse them in applications or as part of other database’s workflow.

Basic structure of the PL/PgSQL function:

CREATE FUNCTION function_name(argument1 type,argument2 type) RETURNS type AS

BEGIN staments; END; LANGUAGE 'language_name

Secure Code Sample

In Postgres, the plain SQL statements using parameters as values are safe against SQL injection like SQL functions. The safest way to execute SQL inside a PL/PgSQL statement is just to do as:

CREATE OR REPLACE FUNCTION user_access (uname TEXT)

RETURNS timestamp LANGUAGE plpgsql AS $func$ BEGIN

RETURN details FROM users WHERE username = uname;

END $func$;

But sometimes you have to do more complicated things. Perhaps you dynamically add WHERE clause expressions based on input. In such a case, you’ll end up using PL/PgSQL’s EXECUTE syntax. Here’s an example with an SQL injection vulnerability:

Insecure Code Sample:

Below SQL query with EXECUTE and without proper parameter handling to actually demonstrate SQL injection.

CREATE OR REPLACE FUNCTION login_v(email varchar, password varchar) RETURNS SETOF users AS $func$ BEGIN RETURN QUERY EXECUTE 'SELECT * FROM users WHERE email = $1 AND password = crypt(''' || passwd || ''', encrypted_password)' USING email; END $func$ LANGUAGE plpgsql;

The first variable email is properly passed with the USING clause as value and thus cannot be abused for SQL injection. But the second variable password is improperly concatenated without properly escaping. Thus, user input can be converted to SQL code and lead to SQL injection.

While performing code reviews, I observed that the sink points of SQL injection where the dynamic queries are constructed without proper escaping. An attacker can inject the malicious payloads in the database and can result in data loss or corruption, lack of accountability, or denial of access. Injection can sometimes lead to complete host takeover.

To avoid these security issues and safe from attackers, the below are the best practices to follow while constructing the dynamic queries:

How to securely construct the SQL queries in PL/PgSQL?

A secure implementation is not to use the dynamic queries constructed with EXECUTE statement even the code may become complex/longer so that the injection attacks cannot be possible.

1. Using Clause:

An EXECUTE with a simple constant command string and some USING parameters is functionally equivalent to just writing the command directly in PL/PgSQL and allowing replacement of PL/PgSQL variables to happen automatically.

The command string can use parameter values, which are referenced in the command as $1, $2, etc. These symbols refer to values supplied in the USING clause. This method is often preferable to inserting data values into the command string as text: it avoids run-time overhead of converting the values to text and back, and it is much less prone to SQL-injection attacks since there is no need for quoting or escaping.

CREATE OR REPLACE FUNCTION getdetails(id text) RETURNS SETOF user AS $func$ BEGIN RETURN QUERY EXECUTE ' SELECT * FROM user WHERE col = $1 USING id END $func$ LANGUAGE plpgsql;

2. Quoting values in Dynamic Queries

Dynamic values require careful handling since they might contain quote characters.

The use of the quote_ident and quote_literal functions, expressions containing column or table identifiers should be passed through quote_ident before insertion in a dynamic query.

Expressions containing values that should be literal strings in the constructed command should be passed through quote_literal. These functions take the appropriate steps to return the input text enclosed in double or single quotes respectively, with any embedded special characters properly escaped.

CREATE OR REPLACE FUNCTION getuserdetails(p_column TEXT, p_value TEXT) RETURNS SETOF users LANGUAGE plpgsql AS $func$ DECLARE query TEXT := 'SELECT * FROM users'; BEGIN IF p_column IS NOT NULL THEN query := query || ' WHERE ' || quote_ident(p_column) || ' = ' || quote_literal(p_value); END IF; RETURN QUERY EXECUTE query; END; $func$;

Format() method:

The function format produces output formatted according to a format string and the format string have the format specifiers by a % character and have the form

%<type>

The type of format conversion to use to produce the format specifier’s output. The following types are supported:

‘%S’ formats the argument value as a simple string. A null value is treated as an empty string.

‘%I’ treat the argument value as an SQL identifier, double-quoting it if necessary. It is an error for the value to be null (equivalent to quote_ident).

‘%L’ quotes the argument value as an SQL literal. A null value is displayed as the string NULL, without quotes (equivalent to quote_nullable).

EXECUTE format('update %I SET source = %L, target = %L WHERE %I = %L', table, source, target, gid_cname, r.id);

3. Prepare

PREPARE creates a prepared statement. When the PREPARE statement is executed, the specified statement is parsed, analysed, and rewritten. When an EXECUTE command is subsequently issued, the prepared statement is planned and executed.

Prepared statements can take parameters: values that are substituted into the statement when it is executed. When creating the prepared statement, refer to parameters by position, using $1, $2, etc. A corresponding list of parameter data types can optionally be specified.

PREPARE usrrptplan (int) AS SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid AND l.date = $2; EXECUTE usrrptplan(1, current_date);

That’s all for this article friends! Please feel free to give feedback or suggest new topics for discussion. I’ll be soon adding more articles in this series.

And always remember “Anyone who stops learning is old, whether at twenty or eighty. Anyone who keeps learning STAYS YOUNG!” So keep learning!

Gowthami Kandala