59
Juliano Atanazio Neutralizando SQL Injection no PostgreSQL Neutralizando SQL Injection no PostgreSQL Neutralizing SQL Injection in PostgreSQL Neutralizing SQL Injection in PostgreSQL

Neutralizing SQL Injection in PostgreSQL

Embed Size (px)

Citation preview

Page 1: Neutralizing SQL Injection in PostgreSQL

Juliano Atanazio

Neutralizando SQL Injection no PostgreSQLNeutralizando SQL Injection no PostgreSQL

Neutralizing SQL Injection in PostgreSQLNeutralizing SQL Injection in PostgreSQL

Page 2: Neutralizing 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/

Page 3: Neutralizing SQL Injection in PostgreSQL

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.

Page 4: Neutralizing SQL Injection in PostgreSQL

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.

Page 5: Neutralizing SQL Injection in PostgreSQL

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;"

Page 6: Neutralizing SQL Injection in PostgreSQL

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;"

Page 7: Neutralizing SQL Injection in PostgreSQL

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}

Page 8: Neutralizing SQL Injection in PostgreSQL

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');

Page 9: Neutralizing SQL Injection in PostgreSQL

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))

____________________________________________________

Page 10: Neutralizing SQL Injection in PostgreSQL

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';

Page 11: Neutralizing SQL Injection in PostgreSQL

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.

Page 12: Neutralizing SQL Injection in PostgreSQL

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)→

Page 13: Neutralizing SQL Injection in PostgreSQL

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: ')

Page 14: Neutralizing SQL Injection in PostgreSQL

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()

Page 15: Neutralizing SQL Injection in PostgreSQL

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()

____________________________________________________

Page 16: Neutralizing SQL Injection in PostgreSQL

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!

Page 17: Neutralizing SQL Injection in PostgreSQL

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!

Page 18: Neutralizing SQL Injection in PostgreSQL

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?

Page 19: Neutralizing SQL Injection in PostgreSQL

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...

Page 20: Neutralizing SQL Injection in PostgreSQL

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

Page 21: Neutralizing SQL Injection in PostgreSQL

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.

:(

Page 22: Neutralizing SQL Injection in PostgreSQL

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$

Page 23: Neutralizing SQL Injection in PostgreSQL

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.

Page 24: Neutralizing SQL Injection in PostgreSQL

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)→

Page 25: Neutralizing SQL Injection in PostgreSQL

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: ')

Page 26: Neutralizing SQL Injection in PostgreSQL

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()

Page 27: Neutralizing SQL Injection in PostgreSQL

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()

____________________________________________________

Page 28: Neutralizing SQL Injection in PostgreSQL

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!

Page 29: Neutralizing SQL Injection in PostgreSQL

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.

Page 30: Neutralizing SQL Injection in PostgreSQL

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

Page 31: Neutralizing SQL Injection in PostgreSQL

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.

:(

Page 32: Neutralizing SQL Injection in PostgreSQL

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]

Page 33: Neutralizing SQL Injection in PostgreSQL

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)

Page 34: Neutralizing SQL Injection in PostgreSQL

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)

Page 35: Neutralizing SQL Injection in PostgreSQL

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: ')

Page 36: Neutralizing SQL Injection in PostgreSQL

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()

Page 37: Neutralizing SQL Injection in PostgreSQL

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()

____________________________________________________

Page 38: Neutralizing SQL Injection in PostgreSQL

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!

Page 39: Neutralizing SQL Injection in PostgreSQL

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.

Page 40: Neutralizing SQL Injection in PostgreSQL

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.

Page 41: Neutralizing SQL Injection in PostgreSQL

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.

Page 42: Neutralizing SQL Injection in PostgreSQL

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.

Page 43: Neutralizing SQL Injection in PostgreSQL

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

Page 44: Neutralizing SQL Injection in PostgreSQL

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;

Page 45: Neutralizing SQL Injection in PostgreSQL

45/59

Prepared Statement: Practice

Execute a prepared statement:

Executar um prepared statement:

> EXECUTE q_user('foo', 'mypassword');

bool ------ t

Page 46: Neutralizing SQL Injection in PostgreSQL

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)→

Page 47: Neutralizing SQL Injection in PostgreSQL

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: ')

Page 48: Neutralizing SQL Injection in PostgreSQL

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)

Page 49: Neutralizing SQL Injection in PostgreSQL

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()

Page 50: Neutralizing SQL Injection in PostgreSQL

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()

____________________________________________________

Page 51: Neutralizing SQL Injection in PostgreSQL

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!

Page 52: Neutralizing SQL Injection in PostgreSQL

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!

Page 53: Neutralizing SQL Injection in PostgreSQL

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

Page 54: Neutralizing SQL Injection in PostgreSQL

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.

Page 55: Neutralizing SQL Injection in PostgreSQL

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.

Page 56: Neutralizing SQL Injection in PostgreSQL

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.

Page 57: Neutralizing SQL Injection in PostgreSQL

57/59

Donate!

The elephant needs you!O Elefante precisa de você!

Contribute! Contribua!

:)

http://www.postgresql.org/about/donate/

Page 58: Neutralizing SQL Injection in PostgreSQL

58/59

Save our planet!Save our planet!

Page 59: Neutralizing SQL Injection in PostgreSQL

59/59

See you soon!!! Até a próxima!!!

Juliano Atanazio

[email protected]

http://slideshare.net/spjuliano

https://speakerdeck.com/julianometalsp

https://juliano777.wordpress.com

:)