Django custom для сложной функции Func (функция sql)
в процессе поиска решения для заказ Django ORM точным, Я создал пользовательский Django Func:
from django.db.models import Func
class Position(Func):
function = 'POSITION'
template = "%(function)s(LOWER('%(substring)s') in LOWER(%(expressions)s))"
template_sqlite = "instr(lower(%(expressions)s), lower('%(substring)s'))"
def __init__(self, expression, substring):
super(Position, self).__init__(expression, substring=substring)
def as_sqlite(self, compiler, connection):
return self.as_sql(compiler, connection, template=self.template_sqlite)
, которая работает следующим образом:
class A(models.Model):
title = models.CharField(max_length=30)
data = ['Port 2', 'port 1', 'A port', 'Bport', 'Endport']
for title in data:
A.objects.create(title=title)
search = 'port'
qs = A.objects.filter(
title__icontains=search
).annotate(
pos=Position('title', search)
).order_by('pos').values_list('title', flat=True)
# result is
# ['Port 2', 'port 1', 'Bport', 'A port', 'Endport']
но как прокомментировал @hynekcer:
"это падает легко by
') in '') from myapp_suburb; drop ...
ожидается, что имя приложения " myapp и autocommit включен."
главная проблема заключается в том, что дополнительные данные (substring
) попал в шаблон без sqlescape, который оставляет приложение уязвимым для атак SQL-инъекций.
Я не могу найти, какой способ Django защитить от этого.
Я создал РЕПО (djposfunc) где вы можете проверить любое решение.
3 ответов
TL; DR:
Все примеры с Func()
в Django docs можно легко использовать для безопасной реализации других подобных функций SQL с одним аргументом.
Все встроенные в Django функции базы данных и условные функции это потомки Func()
также безопасны по дизайну. Приложение не нуждается в комментариях.
класс кнопку func() - самый общая часть выражений запросов Django. Это позволяет реализовать практически любую функцию или оператор в Django ORM каким-то образом. Это как швейцарский армейский нож, очень универсальный, но нужно быть немного более внимательным, чтобы не порезать себя, чем специализированным инструментом (например, электрическим резцом с оптическим барьером). Это все еще гораздо более безопасно, чем подделать собственный инструмент молотком из куска железа, если когда-то "модернизированный" "безопасный" карманный нож не вписывается карман.
примечания по безопасности
краткая документация для
Func(*expressions, **extra)
с примерами следует ознакомиться в первую очередь. (Я рекомендую здесь документы разработки для Django 2.0, где недавно добавлена дополнительная информация о безопасности, включая избегая SQL инъекции, связанные именно с вашим примером.)все позиционные аргументы в
*expressions
несколько составлен Джанго, то естьValue(string)
перемещаются в Параметры, где они правильно экранируются драйвером базы данных.- другие строки интерпретируются как имена полей
F(name)
, затем с префиксом справаtable_name.
alias dot, в конечном итоге добавляется соединение с этой таблицей, а имена обрабатываются
на основе идей Джона Moutafis, заключительная функция (внутри __init__
метод, который мы используем Values
в результате безопасности.)
from django.db.models import Func, F, Value
from django.db.models.functions import Lower
class Instr(Func):
function = 'INSTR'
def __init__(self, string, substring, insensitive=False, **extra):
if not substring:
raise ValueError('Empty substring not allowed')
if not insensitive:
expressions = F(string), Value(substring)
else:
expressions = Lower(string), Lower(Value(substring))
super(Instr, self).__init__(*expressions)
def as_postgresql(self, compiler, connection):
return self.as_sql(compiler, connection, function='STRPOS')
обычно то, что оставляет вас уязвимыми для атаки SQL-инъекций, - это "бродячие" одинарные кавычки '
.
Все, что содержится между парой одинарных кавычек, будет обработано так, как должно, но непарная одинарная кавычка может завершить строку и позволить остальной части записи действовать как исполняемый фрагмент кода.
Именно так обстоит дело на примере @hynekcer.
Django предоставляет Value
метод для предотвращения сверху:
значение будет добавлено в список параметров SQL и правильно процитировал.
поэтому, если вы убедитесь, что передаете каждый пользовательский ввод через Value
метод вы будете в порядке:
from django.db.models import Value
search = user_input
qs = A.objects.filter(title__icontains=search)
.annotate(pos=Position('title', Value(search)))
.order_by('pos').values_list('title', flat=True)
EDIT:
как указано в комментариях, это, похоже, не работает так, как ожидалось в приведенной выше настройке. Но если вызов следующий, он работает:
pos=Func(F('title'), Value(search), function='INSTR')
как Примечание: зачем возиться с шаблонами в первую очередь?
вы можете найти функцию, которую хотите использовать, из любого языка базы данных (например: SQLite, PostgreSQL, MySQL и т. д.) и использовать ее явно:
class Position(Func):
function = 'POSITION' # MySQL default in your example
def as_sqlite(self, compiler, connection):
return self.as_sql(compiler, connection, function='INSTR')
def as_postgresql(self, compiler, connection):
return self.as_sql(compiler, connection, function='STRPOS')
...
EDIT:
вы можете использовать другие функции (например,