Ir para o conteúdo

SQL Injection

📋 Visão Geral

SQL Injection é uma técnica de ataque onde código SQL malicioso é inserido em queries, permitindo acesso não autorizado a dados.


⚠️ O Problema

Código Vulnerável

PARAMETERS: p_name TYPE string.

" ❌ VULNERÁVEL!
DATA(lv_sql) = |SELECT * FROM kna1 WHERE name1 = '{ p_name }'|.

EXEC SQL.
  :lv_sql
ENDEXEC.

Ataque:

User digita: admin' OR '1'='1
Query gerada: SELECT * FROM kna1 WHERE name1 = 'admin' OR '1'='1'
Resultado: Retorna TODOS os clientes! (1=1 sempre verdade)


🔒 Soluções

1️⃣ Usar @-Escaping (Recomendado)

PARAMETERS: p_name TYPE string.

" ✅ SEGURO
SELECT * FROM kna1
  WHERE name1 = @p_name  " @ protege contra injection
  INTO TABLE @DATA(lt_customers).

Por quê funciona? - @p_name é tratado como valor, não código SQL - Caracteres especiais (', --, ;) são escapados automaticamente


2️⃣ Validar e Sanitizar Input

PARAMETERS: p_name TYPE string.

" Validar caracteres permitidos
IF p_name CA `';--*?/\`.
  MESSAGE 'Caracteres inválidos no nome' TYPE 'E'.
  RETURN.
ENDIF.

" Limitar tamanho
IF strlen( p_name ) > 35.
  MESSAGE 'Nome muito longo' TYPE 'E'.
  RETURN.
ENDIF.

" Usar com @-escaping
SELECT * FROM kna1
  WHERE name1 = @p_name
  INTO TABLE @DATA(lt_customers).

3️⃣ Prepared Statements (ADBC)

DATA: lo_sql    TYPE REF TO cl_sql_statement,
      lo_result TYPE REF TO cl_sql_result_set.

lo_sql = cl_sql_statement=>create_instance( ).

" ✅ Prepared statement
DATA(lv_sql) = 'SELECT * FROM kna1 WHERE name1 = ?'.

lo_result = lo_sql->execute_query(
  statement = lv_sql
  p_bnd_tab = VALUE #( ( p_name ) ) ).  " Parâmetro seguro

lo_result->next_package( IMPORTING et_data = lt_customers ).

💡 Exemplos de Ataques

Ataque 1: Bypass de Autenticação

" Código vulnerável
SELECT SINGLE * FROM zusr_login
  WHERE username = p_user
    AND password = p_pass.

" Ataque:
p_user = "admin"
p_pass = "' OR '1'='1"

" Query gerada:
" SELECT * WHERE username = 'admin' AND password = '' OR '1'='1'
" Resultado: Login sem senha!

Proteção:

" ✅ SEGURO
SELECT SINGLE * FROM zusr_login
  WHERE username = @p_user
    AND password = @p_pass.


Ataque 2: Data Exfiltration

" Vulnerável
p_id = "100001' UNION SELECT username, password, '0' FROM zusr_login--"

" Query gerada:
" SELECT name, city, country FROM kna1
"   WHERE kunnr = '100001'
"   UNION SELECT username, password, '0' FROM zusr_login--'

" Resultado: Expõe tabela de senhas!

Proteção:

" ✅ SEGURO
SELECT name1, ort01, land1 FROM kna1
  WHERE kunnr = @p_id
  INTO TABLE @DATA(lt_data).


Ataque 3: Data Destruction

" Vulnerável
p_table = "kna1'; DELETE FROM zusr_login; SELECT * FROM kna1 WHERE '1'='1"

" Queries geradas:
" 1. SELECT * FROM kna1'
" 2. DELETE FROM zusr_login
" 3. SELECT * FROM kna1 WHERE '1'='1'

" Resultado: Tabela deletada!

Proteção:

" ✅ Validar nome da tabela
DATA(lt_allowed_tables) = VALUE stringtab( ( 'KNA1' ) ( 'MARA' ) ).

IF NOT line_exists( lt_allowed_tables[ table_value = to_upper( p_table ) ] ).
  MESSAGE 'Tabela não permitida' TYPE 'E'.
  RETURN.
ENDIF.

" Usar @-escaping
SELECT * FROM (p_table)
  UP TO 100 ROWS
  INTO TABLE @DATA(lt_data).


🛡️ Dynamic SQL Seguro

WHERE Dinâmico - ERRADO

" ❌ VULNERÁVEL
DATA(lv_where) = |name1 = '{ p_name }'|.

SELECT * FROM kna1
  WHERE (lv_where)
  INTO TABLE @DATA(lt_data).

WHERE Dinâmico - CORRETO

" ✅ SEGURO
DATA(lv_where) = |name1 = @p_name|.  " @ no string dinâmico

SELECT * FROM kna1
  WHERE (lv_where)
  INTO TABLE @DATA(lt_data).

Validar Campos Dinâmicos

" ✅ Whitelist de campos permitidos
DATA(lt_allowed_fields) = VALUE stringtab(
  ( 'NAME1' ) ( 'ORT01' ) ( 'LAND1' ) ).

IF NOT line_exists( lt_allowed_fields[ table_value = to_upper( p_field ) ] ).
  MESSAGE 'Campo não permitido' TYPE 'E'.
  RETURN.
ENDIF.

" Usar campo validado
SELECT * FROM kna1
  ORDER BY (p_field)
  INTO TABLE @DATA(lt_data).

🎓 Exemplo Completo: Search Seguro

*&---------------------------------------------------------------------*
*& Report Z_SECURE_SEARCH
*&---------------------------------------------------------------------*
REPORT z_secure_search.

PARAMETERS: p_search TYPE string.

START-OF-SELECTION.

  " ═══ VALIDAÇÃO DE INPUT ═══

  " 1. Não vazio
  IF p_search IS INITIAL.
    MESSAGE 'Digite algo para buscar' TYPE 'I'.
    RETURN.
  ENDIF.

  " 2. Tamanho máximo
  IF strlen( p_search ) > 35.
    MESSAGE 'Busca muito longa (máx 35 caracteres)' TYPE 'E'.
    RETURN.
  ENDIF.

  " 3. Caracteres perigosos
  IF p_search CA `';--*/\`.
    MESSAGE 'Caracteres não permitidos: ; -- * / \' TYPE 'E'.
    RETURN.
  ENDIF.

  " ═══ QUERY SEGURA ═══

  SELECT * FROM kna1
    WHERE name1 LIKE @p_search  " ✅ @-escaping
       OR ort01 LIKE @p_search
    INTO TABLE @DATA(lt_customers)
    UP TO 100 ROWS.

  " ═══ RESULTADO ═══

  IF lt_customers IS INITIAL.
    MESSAGE 'Nenhum cliente encontrado' TYPE 'I'.
  ELSE.
    LOOP AT lt_customers INTO DATA(ls_customer).
      WRITE: / ls_customer-kunnr, ls_customer-name1, ls_customer-ort01.
    ENDLOOP.

    WRITE: / |Total: { lines( lt_customers ) } clientes|.
  ENDIF.

⚡ Boas Práticas

✅ Fazer

" 1. SEMPRE usar @-escaping
SELECT * FROM kna1
  WHERE kunnr = @p_customer.  " ✅

" 2. Validar inputs
IF p_input CA `';--*`.
  MESSAGE 'Caracteres inválidos' TYPE 'E'.
ENDIF.

" 3. Whitelist para valores dinâmicos
IF NOT line_exists( lt_allowed[ table_value = p_table ] ).
  MESSAGE 'Não permitido' TYPE 'E'.
ENDIF.

" 4. Limitar resultados
SELECT * ... UP TO 100 ROWS.  " ✅

" 5. Prepared statements para ADBC
lo_sql->execute_query(
  statement = 'SELECT * FROM kna1 WHERE kunnr = ?'
  p_bnd_tab = VALUE #( ( p_customer ) ) ).

❌ Evitar

" 1. SQL dinâmico sem @-escaping
DATA(lv_sql) = |SELECT * WHERE id = '{ p_id }'|.  " ❌

" 2. Concatenar strings em WHERE
lv_where = |name = '{ p_name }'|.  " ❌

" 3. EXEC SQL com input do usuário
EXEC SQL.
  SELECT * WHERE :p_field = :p_value
ENDEXEC.  " ❌

" 4. Não validar inputs
SELECT * WHERE (p_field) = @p_value.  " ❌ p_field não validado

" 5. Confiar no frontend
" ❌ SEMPRE validar no backend também

🔍 Detectar Vulnerabilidades

Code Inspector (SCI)

SE38 → Code Inspector (Ctrl+Shift+F3)
Check Variant: SECURITY

Revisão Manual

Procurar por: - WHERE (lv_variable) sem validação - Concatenação de strings em SQL - EXEC SQL com inputs dinâmicos - Falta de @ antes de variáveis


🔗 Próximos Passos


Tags: #SQL-Injection #Security #Dynamic-SQL #Input-Validation