Browse Source

Corrige SQL injection em DispositivoSearchFragmentFormView (#3856)

Os parâmetros texto, rotulo, num_ta e ano_ta da busca de dispositivos
eram interpolados diretamente na SQL bruta executada via
Dispositivo.objects.raw(), sem nenhum escape (ano_ta nem sequer era
citado entre aspas). Troca as interpolações por placeholders %s com
params, deixando o driver do banco responsável pela escapagem.

Além disso:
- Renomeia a lista de palavras pesquisadas para texto_valores e monta
  os fragmentos '%s' repetidos via ['d.texto ~* %s'] * len(texto_valores),
  em vez de reatribuir a mesma variável texto ou usar uma comprehension
  com variável de loop não utilizada.
- Adiciona sapl.compilacao.utils.valid_int e aplica a tipo_model,
  tipo_ta e ano_ta (colunas/PKs inteiras), rejeitando valores não
  numéricos com um erro claro em vez de deixar o filtro inválido
  seguir adiante. num_ta não é validado dessa forma porque
  ta.numero é CharField pesquisado via regex (~*), então aceitar
  valores não numéricos ali é esperado.


Claude-Session: https://claude.ai/code/session_01RZAufy4JQKWAaDgfcQByyt

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
3.1.x
Edward 1 week ago
committed by GitHub
parent
commit
c82d626540
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 13
      sapl/compilacao/utils.py
  2. 44
      sapl/compilacao/views.py

13
sapl/compilacao/utils.py

@ -1,5 +1,7 @@
import sys import sys
from django.utils.translation import ugettext_lazy as _
DISPOSITIVO_SELECT_RELATED = ( DISPOSITIVO_SELECT_RELATED = (
'tipo_dispositivo', 'tipo_dispositivo',
'ta_publicado', 'ta_publicado',
@ -66,3 +68,14 @@ def get_integrations_view_names():
if 'IntegracaoTaView' in str(base): if 'IntegracaoTaView' in str(base):
result.append(v) result.append(v)
return result return result
def valid_int(value):
if value:
try:
int(value)
except (TypeError, ValueError):
raise ValueError(
_('Valor "%(value)s" não é um número inteiro válido.') % {
'value': value})
return value

44
sapl/compilacao/views.py

@ -49,7 +49,7 @@ from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE,
VeiculoPublicacao, Vide) VeiculoPublicacao, Vide)
from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
DISPOSITIVO_SELECT_RELATED_EDIT, DISPOSITIVO_SELECT_RELATED_EDIT,
get_integrations_view_names) get_integrations_view_names, valid_int)
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, CrudListView,\ from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, CrudListView,\
make_pagination make_pagination
from sapl.settings import BASE_DIR from sapl.settings import BASE_DIR
@ -3177,14 +3177,14 @@ class DispositivoSearchFragmentFormView(ListView):
result = [] result = []
try: try:
tipo_model = self.request.GET.get('tipo_model', '') tipo_model = valid_int(self.request.GET.get('tipo_model', ''))
limit = int(self.request.GET.get('max_results', 100)) limit = int(self.request.GET.get('max_results', 100))
tipo_ta = self.request.GET.get('tipo_ta', '') tipo_ta = valid_int(self.request.GET.get('tipo_ta', ''))
num_ta = self.request.GET.get('num_ta', '') num_ta = self.request.GET.get('num_ta', '')
ano_ta = self.request.GET.get('ano_ta', '') ano_ta = valid_int(self.request.GET.get('ano_ta', ''))
rotulo = self.request.GET.get('rotulo', '') rotulo = self.request.GET.get('rotulo', '')
str_texto = self.request.GET.get('texto', '') str_texto = self.request.GET.get('texto', '')
texto = str_texto.split(' ') texto_valores = str_texto.split(' ')
tipo_resultado = self.request.GET.get('tipo_resultado', '') tipo_resultado = self.request.GET.get('tipo_resultado', '')
tipo_resultado = '' if tipo_resultado == 'False' else tipo_resultado tipo_resultado = '' if tipo_resultado == 'False' else tipo_resultado
@ -3228,20 +3228,24 @@ class DispositivoSearchFragmentFormView(ListView):
AND_CONTROLS = '''AND td.dispositivo_de_alteracao = true AND_CONTROLS = '''AND td.dispositivo_de_alteracao = true
AND td.dispositivo_de_articulacao = true''' AND td.dispositivo_de_articulacao = true'''
texto = list(map("d.texto ~* '{}'".format, texto)) texto = ['d.texto ~* %s'] * len(texto_valores)
AND_TEXTO_ROTULO = '' AND_TEXTO_ROTULO = ''
texto_rotulo_params = []
if str_texto and rotulo: if str_texto and rotulo:
AND_TEXTO_ROTULO = '''AND ( ({BUSCA_TEXTO} AND d.rotulo ~* '{BUSCA_ROTULO}') OR busca_texto = ' AND '.join(texto)
({BUSCA_TEXTO} AND d.rotulo = '' AND dp.rotulo ~* '{BUSCA_ROTULO}') AND_TEXTO_ROTULO = '''AND ( ({BUSCA_TEXTO} AND d.rotulo ~* %s) OR
({BUSCA_TEXTO} AND d.rotulo = '' AND dp.rotulo ~* %s)
)'''.format( )'''.format(
BUSCA_TEXTO=' AND '.join(texto), BUSCA_TEXTO=busca_texto,
BUSCA_ROTULO=rotulo
) )
texto_rotulo_params = (
texto_valores + [rotulo] + texto_valores + [rotulo])
elif str_texto: elif str_texto:
AND_TEXTO_ROTULO = ' AND %s' % ' AND '.join(texto) AND_TEXTO_ROTULO = ' AND ' + ' AND '.join(texto)
texto_rotulo_params = texto_valores
elif rotulo: elif rotulo:
AND_TEXTO_ROTULO = "AND d.rotulo ~* '{BUSCA_ROTULO}'".format( AND_TEXTO_ROTULO = "AND d.rotulo ~* %s"
BUSCA_ROTULO=rotulo) texto_rotulo_params = [rotulo]
else: else:
AND_TEXTO_ROTULO = '' AND_TEXTO_ROTULO = ''
@ -3288,17 +3292,21 @@ class DispositivoSearchFragmentFormView(ListView):
AND3_TIPO_TA="AND ta.tipo_ta_id = {}".format( AND3_TIPO_TA="AND ta.tipo_ta_id = {}".format(
tipo_ta.id) if tipo_ta else '', tipo_ta.id) if tipo_ta else '',
AND2_ANO="AND ta.ano = {}".format( AND2_ANO="AND ta.ano = %s" if ano_ta else '',
ano_ta) if ano_ta else '',
AND1_NUMERO="AND ta.numero ~* '{}'".format( AND1_NUMERO="AND ta.numero ~* %s" if num_ta else '',
num_ta) if num_ta else '',
AND_TEXTO_ROTULO=AND_TEXTO_ROTULO if AND_TEXTO_ROTULO else '', AND_TEXTO_ROTULO=AND_TEXTO_ROTULO if AND_TEXTO_ROTULO else '',
AND_CONTROLS=AND_CONTROLS if AND_CONTROLS else '' AND_CONTROLS=AND_CONTROLS if AND_CONTROLS else ''
) )
result = Dispositivo.objects.raw(sql) params = texto_rotulo_params.copy()
if num_ta:
params.append(num_ta)
if ano_ta:
params.append(ano_ta)
result = Dispositivo.objects.raw(sql, params)
r = [] r = []
ids = set() ids = set()

Loading…
Cancel
Save