Post on 14-Apr-2017
Juliano Atanazio
Neutralizando SQL Injection no PostgreSQLNeutralizando SQL Injection no PostgreSQL
Neutralizing SQL Injection in PostgreSQLNeutralizing SQL Injection in PostgreSQL
2/59
About me
Juliano Atanazio
● Graduated in Computer Science for Business Management (Informática para Gestão de Negócios), FATEC Zona Sul, São Paulo – SP;
● PostgreSQL DBA;
● Linux admin;
● Instructor (PostgreSQL);
● LPIC-1, LPIC-2 Certified;
● Linux user since 2000;
● Free Software enthusiast;
● Favorite technologies: PostgreSQL, Linux, Python, Shell Script, FreeBSD, etc...;
● Headbanger :) \m/
3/59
SQL Injection
Definition
SQL Injection is a method to introducing malicious SQL code to get unauthorized access or even damage a system.
Definição
SQL Injection é um método para introduzir código SQL maligno para obter acesso indevido ou mesmo danificar um sistema.
4/59
SQL Injection: Practice
$DBHOST enviroment variable to database server address:
Variável de ambiente $DBHOST para o endereço do servidor de banco de dados:
$ read -p 'Type the database host address: ' DBHOST
Type the database host address:
Type the server address.
Digite o endereço do servidor.
5/59
SQL Injection: Practice
Database user with encrypted stored password, login permission, no superuser:
Usuário de banco de dados com senha armazenada criptografada, permissão de login, não superuser:
$ psql -U postgres -h ${DBHOST} -c \"CREATE ROLE u_sql_injection \ENCRYPTED PASSWORD 'secret' LOGIN NOSUPERUSER;"
6/59
SQL Injection: Practice
Database creation "db_sql_injection" with user "u_sql_injection" as owner:
Criação de banco de dados "db_sql_injection" com o usuário "u_sql_injection" como proprietário:
$ psql -U postgres -h ${DBHOST} -c \"CREATE DATABASE db_sql_injection OWNER u_sql_injection;"
7/59
SQL Injection: Practice
Accessing the database via psql:
Acessando a base de dados via psql:
$ psql -U u_sql_injection db_sql_injection -h ${DBHOST}
8/59
SQL Injection: Practice
User table creation for the application (without hashing):
Criação de tabela de usuários para a aplicação (sem hashing):
> CREATE TABLE tb_user( username varchar(50) PRIMARY KEY, -- natural primary key password VARCHAR(72) NOT NULL);
Inserting a application user in the table:
Inserindo um usuário do aplicativo na tabela:
> INSERT INTO tb_user (username, password) VALUES ('foo', 'mypassword');
9/59
SQL Injection: Practice
Script (1):__________ sql_injection_1.py ___________________________
#_*_ encoding: utf-8 _*_
import getpass
user = input('User: ')password = getpass.getpass('Password: ')
sql = """SELECT TRUE FROM tb_userWHERE username = '{}'AND password = '{}';""".format(user, password)
print('\n{}'.format(sql))
____________________________________________________
10/59
SQL Injection: Practice
A simple test:
Um teste simples:
$ python3 sql_injection_1.py
User: fooPassword:
SELECT TRUE FROM tb_userWHERE username = 'foo'AND password = 'mypassword';
11/59
SQL Injection: About the Script
The script is pretty simple, does not yet have any interaction with the database, but it serves to illustrate.
O script é bem simples, ainda não possui qualquer interação com o banco de dados, mas serve para ilustrar.
12/59
SQL Injection: Practice
Script (2):__________ sql_injection_2.py ___________________________
# _*_ encoding: utf-8 _*_
import getpassimport psycopg2import sys
# DB server as first argumentdbhost = sys.argv[1]
# Connection stringconn_string = """ host='{}' dbname='db_sql_injection' user='u_sql_injection' password='secret' port='5432' """.format(dbhost)→
13/59
SQL Injection: Practice
Script (2):__________ sql_injection_2.py ___________________________
try: # Connection conn = psycopg2.connect(conn_string)
# Cursor creation to execute SQL commands cursor = conn.cursor()
# User input user = input('User: ')
# Password input password = getpass.getpass('Password: ')
→
14/59
SQL Injection: Practice
Script (2):__________ sql_injection_2.py ___________________________
# SQL string sql = """ SELECT TRUE FROM tb_user \ WHERE username = '{}' \ AND password = '{}'; """.format(user, password)
# Print the sql string after user and password input print('{}\n'.format(sql))
# Execute the SQL string in database cursor.execute(sql)
# The result of the string SQL execution res = cursor.fetchone()
→
15/59
SQL Injection: Practice
Script (2):__________ sql_injection_2.py ___________________________
# User login validation if res: print('\nAcessed!') else: print('\nError: Invalid user and password combination!') sys.exit(1)
except psycopg2.Error as e: print('\nAn error has occurred!\n{}'.format(e))
# Close the database connectionconn.close()
____________________________________________________
16/59
SQL Injection: Practice
A simple test access with correct password:
Um teste simples de acesso com senha correta:
$ python3 sql_injection_2.py ${DBHOST}
User: fooPassword:
SELECT TRUE FROM tb_userWHERE username = 'foo' AND password = 'mypassword';
Acessed!
17/59
SQL Injection: Practice
A simple test access with wrong password:
Um teste simples de acesso com senha errada:
$ python3 sql_injection_2.py ${DBHOST}
User: fooPassword:
SELECT TRUE FROM tb_userWHERE username = 'foo'AND password = '123';
Error: Invalid user and password combination!
18/59
SQL Injection: Practice
Malicious code at user login input:
Código malicioso na entrada de login de usuário:
$ python3 sql_injection_2.py ${DBHOST}
User: ' OR 1 = 1; DROP TABLE tb_user; --Password:
SELECT TRUE FROM tb_userWHERE username = '' OR 1 = 1; DROP TABLE tb_user; –-'AND password = '';
An error has occurred!no results to fetch
Does the table has been deleted?
Será que a tabela foi apagada?
19/59
SQL Injection: Practice
Checking the table in the database:
Verificando a tabela na base de dados:
> SELECT TRUE FROM tb_user;
bool ------ t
Everithing is OK... for a while...No commit...
Está tudo OK... por enquanto...Sem efetivação...
20/59
SQL Injection: Practice
Malicious code at user login input (with COMMIT):
Código malicioso na entrada de login de usuário (com COMMIT):
$ python3 sql_injection.py
User: ' OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password:
SELECT TRUE FROM tb_user WHERE username = '' OR 1 = 1; DROP TABLE tb_user; COMMIT; –-'AND password = '';
An error has occurred!no results to fetch
21/59
SQL Injection: Practice
Checking the table in the database:
Verificando a tabela na base de dados:
> SELECT TRUE FROM tb_user;
ERROR: relation "tb_user" does not existLINE 1: SELECT id FROM tb_user; ^
The table was dropped and must be created with the data again.
A tabela foi apagada e terá que ser criada com os dados novamente.
:(
22/59
Dollar Quoting
It consists of a dollar sign ($), an optional “tag” of zero or more characters, another dollar sign, an arbitrary sequence of characters that makes up the string content, a dollar sign, the same tag that began this dollar quote, and a dollar sign. For example, here are two different ways to specify the string “Dianne's horse” using dollar quoting:
Consiste de um caractere de dólar, uma “tag” opcional de zero ou mais caracteres, outro caractere de dólar, uma sequência arbitrária de caracteres que é o conteúdo da string, um caractere de dólar, a mesma tag que começou o dollar quoting e um caractere de dólar. Por exemplo, há duas maneiras diferentes de especificar a string “Dianne's horse” usando dollar quoting:
$$Dianne's horse$$$SomeTag$Dianne's horse$SomeTag$
23/59
Dollar Quoting
Dollar quoting is also a very nice feature to avoid SQL injection, particularly when the application generates a random tag.This tag must start with either a letter or with an underscore, the rest can have underscore, letters or numbers.
Dollar quoting também é um recurso muito interessante para se evitar SQL injection, principalmente quando a aplicação gera uma tag aleatória.Essa tag deve começar ou com uma letra ou com underscore, o resto pode ter underscore, letras ou números.
24/59
Dollar Quoting: Practice
Script (3):__________ sql_injection_3.py ___________________________
# _*_ encoding: utf-8 _*_
import getpassimport psycopg2import sys
# DB server as first argumentdbhost = sys.argv[1]
# Connection stringconn_string = """ host='{}' dbname='db_sql_injection' user='u_sql_injection' password='secret' port='5432' """.format(dbhost)→
25/59
Dollar Quoting: Practice
Script (3):__________ sql_injection_3.py ___________________________
try: # Connection conn = psycopg2.connect(conn_string)
# Cursor creation to execute SQL commands cursor = conn.cursor()
# User input user = input('User: ')
# Password input password = getpass.getpass('Password: ')
→
26/59
Dollar Quoting: Practice
Script (3):__________ sql_injection_3.py ___________________________
# SQL string sql = """ SELECT TRUE FROM tb_user WHERE username = $${}$$ AND password = $${}$$; """.format(user, password)
# Print the sql string after user and password input print('{}\n'.format(sql))
# Execute the SQL string in database cursor.execute(sql)
# The result of the string SQL execution res = cursor.fetchone()
→
27/59
Dollar Quoting: Practice
Script (3):__________ sql_injection_3.py ___________________________
# User login validation if res: print('\nAcessed!\n') else: print('\nError: Invalid user and password combination!\n') sys.exit(1)except psycopg2.Error as e: print('\nAn error has occurred!\n{}'.format(e))
# Close the database connectionconn.close()
____________________________________________________
28/59
Dollar Quoting: Practice
Normal access:
Acesso normal:
$ python3 sql_injection_3.py ${DBHOST}
User: fooPassword: SELECT TRUE FROM tb_userWHERE username = $$foo$$AND password = $$mypassword$$;
Acessed!
29/59
Dollar Quoting: Practice
Attempted malicious code (with apostrophe):
Tentativa de código malicioso (com apóstrofo):
$ python3 sql_injection_3.py ${DBHOST}
User: ' OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password: SELECT TRUE FROM tb_userWHERE username = $$' OR 1 = 1; DROP TABLE tb_user; COMMIT; --$$AND password = $$$$;
Error: Invalid user and password combination!
Neutralized malicious code.
Código malicioso neutralizado.
30/59
Dollar Quoting: Practice
Attempted malicious code (with double dollar sign):
Tentativa de código malicioso (com dólar duplo):
$ python3 sql_injection_3.py ${DBHOST} User: $$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password:
SELECT TRUE FROM tb_userWHERE username = $$$$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --$$AND password = $$$$;
An error has occurred!no results to fetch
31/59
Dollar Quoting: Practice
Checking the table in the database:
Verificando a tabela na base de dados:
> SELECT TRUE FROM tb_user;
ERROR: relation "tb_user" does not existLINE 1: SELECT id FROM tb_user; ^
The table was dropped and must be created with the data again.
A tabela foi apagada e terá que ser criada com os dados novamente.
:(
32/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
# _*_ encoding: utf-8 _*_
import getpassimport psycopg2import sysimport stringimport random
# DB server as first argumentdbhost = sys.argv[1]
→
33/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
# Connection stringconn_string = """ host='{}' dbname='db_sql_injection' user='u_sql_injection' password='secret' port='5432' """.format(dbhost)
→
34/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
# Function: tag generatordef tag_gen(size): first_char = '{}_'.format(string.ascii_letters) last_chars = '{}{}'.format(string.digits, first_char) tag = random.choice(first_char)
for i in range(size - 1): tag = '{}{}'.format(tag, random.choice(last_chars))
return tag
# Tag for dollar quotingtag = tag_gen(7)
→
35/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
try: # Connection conn = psycopg2.connect(conn_string)
# Cursor creation to execute SQL commands cursor = conn.cursor()
# User input user = input('User: ')
# Password input password = getpass.getpass('Password: ')
→
36/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
# SQL string sql = """ SELECT TRUE FROM tb_user WHERE username = ${}${}${}$ AND password = ${}${}${}$; """.format(tag, user, tag, tag, password, tag)
# Print the sql string after user and password input print('{}\n'.format(sql))
# Execute the SQL string in database cursor.execute(sql)
# The result of the string SQL execution res = cursor.fetchone()
→
37/59
Dollar Quoting: Practice
Script (4):__________ sql_injection_4.py ___________________________
# User login validation if res: print('\nAcessed!\n') else: print('\nError: Invalid user and password combination!\n') sys.exit(1)except psycopg2.Error as e: print('\nAn error has occurred!\n{}'.format(e))
# Close the database connectionconn.close()
____________________________________________________
38/59
Dollar Quoting: Practice
A simple test access with correct password:
Um teste simples de acesso com senha correta:
$ python3 sql_injection_4.py ${DBHOST}
User: fooPassword: SELECT TRUE FROM tb_userWHERE username = $PJPWqvS$foo$PJPWqvS$AND password = $PJPWqvS$mypassword$PJPWqvS$;
Acessed!
39/59
Dollar Quoting: Practice
Attempted malicious code (with apostrophe):
Tentativa de código malicioso (com apóstrofo):
$ python3 sql_injection_4.py ${DBHOST}
User: ' OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password: SELECT TRUE FROM tb_userWHERE username = $EbVRSoG$' OR 1 = 1; DROP TABLE tb_user; COMMIT; --$EbVRSoG$AND password = $EbVRSoG$$EbVRSoG$;
Error: Invalid user and password combination!
Neutralized malicious code.
Código malicioso neutralizado.
40/59
Dollar Quoting: Practice
Attempted malicious code (with double dollar sign):
Tentativa de código malicioso (com dólar duplo):
$ python3 sql_injection_4.py ${DBHOST}
User: $$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password:
SELECT TRUE FROM tb_userWHERE username = $Re7Gqwb$$$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --$Re7Gqwb$AND password = $Re7Gqwb$$Re7Gqwb$;
Error: Invalid user and password combination!
Neutralized malicious code.
Código malicioso neutralizado.
41/59
Prepared Statement
A prepared statement is a server-side object that can be used to optimize performance.
Um prepared statement (comando preparado) é um objeto do lado do servidor que pode ser usado para otimizar performance.
When the PREPARE statement is executed, the statement is analyzed, statistics collections are made (ANALYZE) and rewritten.
Quando PREPARE statement é executado, o comando (statement) é analisado, são feitas coletas de estatísticas (ANALYZE) e reescrito.
42/59
Prepared Statement
When given an EXECUTE statement, the statement is planned and prepared executed.
Quando é dado um comando EXECUTE, o prepared statement é planejado e executado.
This division of labor prevents repetitive tasks of collecting statistics, while allowing the execution plan depend on specific parameters that can be provided.
Essa divisão de trabalho evita repetitivos trabalhos de coleta de estatística, enquanto permite ao plano de execução de depender de parâmetros específicos que podem ser fornecidos.
43/59
Prepared Statement
Steps / Etapas
Normal query:
Consulta normal:
1) Parser → 2) Rewrite System → 3) Planner / Optimizer → 4) Executor
Prepared Statement:
1) Planner / Optimizer → 2) Executor
44/59
Prepared Statement: Practice
Create a prepared statement:
Criar um prepared statement:
> PREPARE q_user(text, text) AS SELECT TRUE FROM tb_user WHERE username = $1 AND password = $2;
45/59
Prepared Statement: Practice
Execute a prepared statement:
Executar um prepared statement:
> EXECUTE q_user('foo', 'mypassword');
bool ------ t
46/59
Prepared Statement: Practice
Script (5):__________ sql_injection_5.py ___________________________
# _*_ encoding: utf-8 _*_
import getpassimport psycopg2import sys
# DB server as first argumentdbhost = sys.argv[1]
# Connection stringconn_string = """ host='{}' dbname='db_sql_injection' user='u_sql_injection' password='secret' port='5432' """.format(dbhost)→
47/59
Prepared Statement: Practice
Script (5):__________ sql_injection_5.py ___________________________
try: # Connection conn = psycopg2.connect(conn_string)
# Cursor creation to execute SQL commands cursor = conn.cursor()
# User input user = input('User: ')
# Password input password = getpass.getpass('Password: ')
→
48/59
Prepared Statement: Practice
Script (5):__________ sql_injection_5.py ___________________________
# SQL string sql = """ PREPARE q_user (text, text) AS SELECT TRUE FROM tb_user WHERE username = $1 AND password = $2; """
# Print the sql string after user and password input print('{}\n'.format(sql))
# Execute the SQL string in database cursor.execute(sql)
→
49/59
Prepared Statement: Practice
Script (5):__________ sql_injection_5.py ___________________________ # SQL string with EXECUTE sql = "EXECUTE q_user('{}', '{}');".format(user, password)
# Print the SQL string print('{}\n'.format(sql)) # Execute the SQL string in database cursor.execute(sql)
# The result of the string SQL execution res = cursor.fetchone()
→
50/59
Prepared Statement: Practice
Script (5):__________ sql_injection_5.py ___________________________ # User login validation if res: print('\nAcessed!') else: print('\nError: Invalid user and password combination!') sys.exit(1)except psycopg2.Error as e: print('\nAn error has occurred!\n{}'.format(e))
# Close the database connectionconn.close()
____________________________________________________
51/59
Prepared Statement: Practice
A simple test access with correct password:
Um teste simples de acesso com senha correta:
$ python3 sql_injection_5.py ${DBHOST}
User: fooPassword:
PREPARE q_user (text, text) ASSELECT TRUE FROM tb_userWHERE username = $1 AND password = $2;
EXECUTE q_user('foo', 'mypassword');
Acessed!
52/59
Prepared Statement: Practice
A simple test access with wrong password:
Um teste simples de acesso com senha errada:
$ python3 sql_injection_5.py ${DBHOST} User: fooPassword:
PREPARE q_user (text, text) ASSELECT TRUE FROM tb_user WHERE username = $1 AND password = $2;
EXECUTE q_user('foo', '123');
Error: Invalid user and password combination!
53/59
Prepared Statement: Practice
Attempted malicious code (with apostrophe):
Tentativa de código malicioso (com apóstrofo):
$ python3 sql_injection_5.py ${DBHOST}
User: ' OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password:
PREPARE q_user (text, text) AS SELECT TRUE FROM tb_userWHERE username = $1 AND password = $2;
EXECUTE q_user('' OR 1 = 1; DROP TABLE tb_user; COMMIT; --', '');
An error has occurred!syntax error at or near ";"LINE 1: EXECUTE q_user('' OR 1 = 1; DROP TABLE tb_user; COMMIT; --',... ^
Neutralized malicious code. / Código malicioso neutralizado
54/59
Prepared Statement: Practice
Attempted malicious code (with double dollar sign):
Tentativa de código malicioso (com dólar duplo):
$ python3 sql_injection_5.py ${DBHOST} User: $$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --Password: PREPARE q_user (text, text) AS SELECT TRUE FROM tb_user WHERE username = $1 AND password = $2;
EXECUTE q_user('$$ OR 1 = 1; DROP TABLE tb_user; COMMIT; --', '');
Error: Invalid user and password combination!
Neutralized malicious code. / Código malicioso neutralizado.
55/59
Conclusion / Conclusão
PostgreSQL has its own mechanisms against SQL injection which makes it very independent of the application.
O PostgreSQL possui mecanismos próprios contra SQL injection que o torna muito independente da aplicação.
56/59
Conclusion / Conclusão
This makes it easier for the application developer, may delegate such tasks to the database, avoiding technical adjustments in the application and finally provide a robust solution independent of language. Isso facilita para o desenvolvedor da aplicação, podendo confiar tais tarefas ao banco de dados, evitando adaptações técnicas na aplicação e por fim prover uma solução robusta independente da linguagem.
57/59
Donate!
The elephant needs you!O Elefante precisa de você!
Contribute! Contribua!
:)
http://www.postgresql.org/about/donate/
58/59
Save our planet!Save our planet!
59/59
See you soon!!! Até a próxima!!!
Juliano Atanazio
juliano777@gmail.com
http://slideshare.net/spjuliano
https://speakerdeck.com/julianometalsp
https://juliano777.wordpress.com
:)