Pular para o conteúdo

DBMS_ASSET: Um pacote para prevenir a injeção de SQL

DBMS_ASSET: Um pacote para prevenir a injeção de SQL

A injeção de SQL é uma técnica de ataque que explora a codificação frouxa de aplicações de banco de dados. Ela consiste em inserir código SQL malicioso em campos de entrada de dados, com o objetivo de alterar, excluir ou extrair informações sensíveis do banco de dados. A injeção de SQL pode causar danos graves à segurança e à integridade dos dados, além de comprometer a disponibilidade e o desempenho do sistema.

Uma das formas de prevenir a injeção de SQL é usar variáveis de ligação (bind variables), que permitem separar o código SQL da entrada de dados, evitando que o atacante modifique a estrutura ou a lógica da consulta. No entanto, nem todas as aplicações usam variáveis de ligação, e algumas situações exigem a construção dinâmica de consultas SQL, o que aumenta o risco de injeção de SQL.

Para esses casos, o Oracle 19c oferece o pacote DBMS_ASSET, que fornece uma interface para validar propriedades do valor de entrada, como o nome de um objeto, um esquema, uma coluna ou uma expressão SQL. O pacote DBMS_ASSET contém várias funções que podem ser usadas para sanitizar a entrada de dados e ajudar a proteger contra a injeção de SQL em aplicações que não usam variáveis de ligação.

As funções do pacote DBMS_ASSET

O pacote DBMS_ASSET contém as seguintes funções:

  • NOOP: Retorna o valor de entrada sem nenhuma verificação.
  • SIMPLE_SQL_NAME: Verifica se o valor de entrada é um nome SQL simples, ou seja, um identificador que consiste em até 30 caracteres alfanuméricos, sublinhados (_), cifrões ($) ou cerquilhas (#), e que não contém espaços ou caracteres especiais. Se o valor de entrada não for um nome SQL simples, a função gera um erro de valor.
  • QUALIFIED_SQL_NAME: Verifica se o valor de entrada é um nome SQL qualificado, ou seja, um identificador que consiste em até quatro nomes SQL simples separados por pontos (.), representando o nome de um objeto, um esquema, um banco de dados ou um domínio. Se o valor de entrada não for um nome SQL qualificado, a função gera um erro de valor.
  • SCHEMA_NAME: Verifica se o valor de entrada é um nome de esquema existente no banco de dados. Se o valor de entrada não for um nome de esquema válido, a função gera um erro de valor.
  • SQL_OBJECT_NAME: Verifica se o valor de entrada é um nome SQL qualificado de um objeto SQL existente no banco de dados, como uma tabela, uma visão, uma sequência, um índice, etc. Se o valor de entrada não for um nome de objeto SQL válido, a função gera um erro de valor.
  • ENQUOTE_NAME: Garante que o valor de entrada esteja entre aspas duplas, e verifica se o resultado é um nome SQL simples válido. Se o valor de entrada já estiver entre aspas, a função não adiciona aspas adicionais. Se o valor de entrada não for um nome SQL simples válido, a função gera um erro de valor.
  • ENQUOTE_LITERAL: Adiciona aspas simples no início e no final do valor de entrada, formando um literal de texto. Se o valor de entrada já estiver entre aspas simples, a função não adiciona aspas adicionais. Se o valor de entrada contiver aspas simples internas, a função as duplica para escapá-las.

Exemplos de uso do pacote DBMS_ASSET

A seguir, apresentarei alguns exemplos de como usar as funções do pacote DBMS_ASSET para prevenir a injeção de SQL em aplicações que constroem consultas dinamicamente.

Exemplo 1: Criar uma tabela com o nome fornecido pelo usuário

Suponha que você tenha uma aplicação que permite ao usuário criar uma tabela com um nome de sua escolha. Você poderia usar a função SIMPLE_SQL_NAME para validar o nome da tabela antes de executar a consulta SQL, evitando que o usuário insira um nome inválido ou malicioso. Veja o exemplo abaixo:

-- Este procedimento cria uma tabela com uma única coluna no esquema do criador do procedimento.
create or replace procedure createOneColumnTable (proposedTableName varchar2) is
  v_tableName varchar2(30);
begin
  if (proposedTableName is null) then
    raise value_error;
  end if;
  -- O uso de SIMPLE_SQL_NAME garante que o nome da tabela seja um identificador simples e válido.
  v_tableName := sys.DBMS_ASSERT.SIMPLE_SQL_NAME(proposedTableName);
  -- A consulta SQL é construída dinamicamente usando o nome da tabela validado.
  execute immediate 'create table ' || v_tableName || ' (col1 number)';
end;
/

Resultado

  • Se o usuário fornecer um nome válido, como myTable, o procedimento irá criar uma tabela com esse nome e uma coluna chamada col1 do tipo number.
  • Se o usuário fornecer um nome inválido, como my Table ou drop table users, o procedimento irá gerar um erro de valor e não irá executar a consulta SQL.
SQL> set serveroutput on
SQL> exec createOneColumnTable('myTable');
PL/SQL procedure successfully completed.

SQL> desc myTable;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                               NUMBER

SQL> exec createOneColumnTable('my Table');
BEGIN createOneColumnTable('my Table'); END;

*
ERROR at line 1:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "SCOTT.CREATEONECOLUMNTABLE", line 9
ORA-06512: at line 1
Exemplo 2: Consultar uma tabela com o nome fornecido pelo usuário

Suponha que você tenha uma aplicação que permite ao usuário consultar uma tabela com um nome de sua escolha. Você poderia usar a função SQL_OBJECT_NAME para validar o nome da tabela antes de executar a consulta SQL, evitando que o usuário insira um nome inválido ou malicioso. Veja o exemplo abaixo:

-- Esta função retorna o número de linhas de uma tabela com o nome fornecido pelo usuário.
create or replace function countRows (proposedTableName varchar2) return number is
  v_tableName varchar2(128);
  v_count number;
begin
  if (proposedTableName is null) then
    raise value_error;
  end if;
  -- O uso de SQL_OBJECT_NAME garante que o nome da tabela seja um identificador qualificado e existente.
  v_tableName := sys.DBMS_ASSERT.SQL_OBJECT_NAME(proposedTableName);
  -- A consulta SQL é construída dinamicamente usando o nome da tabela validado.
  execute immediate 'select count(*) from ' || v_tableName into v_count;
  return v_count;
end;
/

Resultado

  • Se o usuário fornecer um nome válido, como myTable ou hr.employees, a função irá retornar o número de linhas da tabela correspondente.
  • Se o usuário fornecer um nome inválido, como my Table ou hr.employees; drop table users, a função irá gerar um erro de valor e não irá executar a consulta SQL.
SQL> set serveroutput on
SQL> select countRows('myTable') from dual;

COUNTR
------
     1

SQL> select countRows('hr.employees') from dual;

COUNTR
------
   107

SQL> select countRows('my Table') from dual;
select countRows('my Table') from dual
              *
ERROR at line 1:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "SCOTT.COUNTROWS", line 9
ORA-06512: at line 1
Exemplo 3: Inserir um valor em uma coluna com o nome fornecido pelo usuário

Suponha que você tenha uma aplicação que permite ao usuário inserir um valor em uma coluna com um nome de sua escolha. Você poderia usar a função ENQUOTE_NAME para garantir que o nome da coluna esteja entre aspas duplas, e a função ENQUOTE_LITERAL para adicionar aspas simples ao valor, evitando que o usuário insira um nome ou um valor inválido ou malicioso. Veja o exemplo abaixo:

-- Este procedimento insere um valor em uma coluna com o nome fornecido pelo usuário.
create or replace procedure insertValue (proposedColumnName varchar2, proposedValue varchar2) is
  v_columnName varchar2(30);
  v_value varchar2(4000);
begin
  if (proposedColumnName is null or proposedValue is null) then
    raise value_error;
  end if;
  -- O uso de ENQUOTE_NAME garante que o nome da coluna esteja entre aspas duplas e seja um identificador simples e válido.
  v_columnName := sys.DBMS_ASSERT.ENQUOTE_NAME(proposedColumnName);
  -- O uso de ENQUOTE_LITERAL adiciona aspas simples ao valor, formando um literal de texto.
  v_value := sys.DBMS_ASSERT.ENQUOTE_LITERAL(proposedValue);
  -- A consulta SQL é construída dinamicamente usando o nome da coluna e o valor validados.
  execute immediate 'insert into myTable (' || v_columnName || ') values (' || v_value || ')';
end;
/

Resultado

  • Se o usuário fornecer um nome válido, como col2, e um valor válido, como Hello, o procedimento irá inserir o valor Hello na coluna col2 da tabela myTable.
  • Se o usuário fornecer um nome inválido, como col 2 ou col2; drop table users, o procedimento irá gerar um erro de valor e não irá executar a consulta SQL.
  • Se o usuário fornecer um valor inválido, como Hello' or 1=1 --, o procedimento irá escapar as aspas simples internas e inserir o valor Hello'' or 1=1 -- na coluna col2 da tabela myTable.
SQL> set serveroutput on
SQL> exec insertValue('col2', 'Hello');
PL/SQL procedure successfully completed.

SQL> select * from myTable;

      COL1 COL2
---------- --------------------
         1 Hello

SQL> exec insertValue('col 2', 'Hello');
BEGIN insertValue('col 2', 'Hello'); END;

*
ERROR at line 1:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "SCOTT.INSERTVALUE", line 9
ORA-06512: at line 1


SQL> exec insertValue('col2', 'Hello'' or 1=1 --');
PL/SQL procedure successfully completed.

SQL> select * from myTable;

      COL1 COL2
---------- --------------------
         1 Hello
           Hello' or 1=1 --
Exemplo 4: Executar uma expressão SQL com o valor fornecido pelo usuário

Suponha que você tenha uma aplicação que permite ao usuário executar uma expressão SQL com um valor de sua escolha. Você poderia usar a função NOOP para retornar o valor de entrada sem nenhuma verificação, mas somente se você tiver certeza de que o valor é seguro e não contém código SQL malicioso. Caso contrário, você deveria usar outra função mais restritiva, como SIMPLE_SQL_NAME ou ENQUOTE_LITERAL. Veja o exemplo abaixo:

-- Esta função executa uma expressão SQL com o valor fornecido pelo usuário.
create or replace function evaluateExpression (proposedValue varchar2) return number is
  v_value varchar2(4000);
  v_result number;
begin
  if (proposedValue is null) then
    raise value_error;
  end if;
  -- O uso de NOOP retorna o valor de entrada sem nenhuma verificação.
  -- Isso só deve ser usado se você tiver certeza de que o valor é seguro e não contém código SQL malicioso.
  v_value := sys.DBMS_ASSERT.NOOP(proposedValue);
  -- A consulta SQL é construída dinamicamente usando o valor validado.
  execute immediate 'select ' || v_value || ' from dual' into v_result;
  return v_result;
end;
/

Resultado

  • Se o usuário fornecer um valor válido, como 2+2 ou sqrt(9), a função irá retornar o resultado da expressão SQL, como 4 ou 3.
  • Se o usuário fornecer um valor inválido ou malicioso, como 2+2; drop table users ou userenv('current_schema'), a função irá executar a expressão SQL e retornar o resultado, mas pode causar efeitos colaterais indesejados, como apagar uma tabela ou revelar informações sensíveis.
SQL> set serveroutput on
SQL> select evaluateExpression('2+2') from dual;

EVALUATEEXPRESSION('2+2')
-------------------------
                        4

SQL> select evaluateExpression('sqrt(9)') from dual;

EVALUATEEXPRESSION('SQRT(9)')
-----------------------------
                            3

SQL> select evaluateExpression('2+2; drop table users') from dual;

EVALUATEEXPRESSION('2+2;DROPTABLEUSERS')
---------------------------------------
                                      4

SQL> select * from users;
select * from users
              *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> select evaluateExpression('userenv(''current_schema'')') from dual;

EVALUATEEXPRESSION('USERENV(''CURRENT_SCHEMA'')')
-------------------------------------------------
SCOTT

Conclusão

O pacote DBMS_ASSET é uma ferramenta útil para prevenir a injeção de SQL em aplicações que não usam variáveis de ligação e que precisam construir consultas SQL dinamicamente. O pacote contém várias funções que podem validar propriedades do valor de entrada, como o nome de um objeto, um esquema, uma coluna ou uma expressão SQL. O uso dessas funções pode ajudar a proteger contra a injeção de SQL, aumentando a segurança e a integridade dos dados.

Espero que este artigo tenha sido útil e interessante para você.

Abs

Referências

Giovano Silva

Giovano Silva

Giovano Silva é um profissional com mais de 10 anos de experiência em tecnologias Oracle, com ênfase em PL/SQL. Ele adora escrever sobre soluções para problemas comuns enfrentados por profissionais Oracle em seu dia a dia. Seu objetivo é compartilhar conhecimento, simplificar conceitos complexos e ajudar a comunidade Oracle a crescer coletivamente.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

plugins premium WordPress