Fixed -- Doc'd Func.__init__()'s **extra and as_sql()'s **extra_context aren't escaped.
Thanks Hynek Cernoch for the report and review.
@@ -306,6 +306,11 @@ The ``Func`` API is as follows:
template="%(function)s('', %(expressions)s)",
)
+ To avoid a SQL injection vulnerability, ``extra_context`` :ref:`must
+ not contain untrusted user input <avoiding-sql-injection-in-query-expressions>`
+ as these values are interpolated into the SQL string rather than passed
+ as query parameters, where the database driver would escape them.
+
The ``*expressions`` argument is a list of positional expressions that the
function will be applied to. The expressions will be converted to strings,
joined together with ``arg_joiner``, and then interpolated into the ``template``
@@ -316,10 +321,15 @@ assumed to be column references and will be wrapped in ``F()`` expressions
while other values will be wrapped in ``Value()`` expressions.
The ``**extra`` kwargs are ``key=value`` pairs that can be interpolated
-into the ``template`` attribute. The ``function``, ``template``, and
-``arg_joiner`` keywords can be used to replace the attributes of the same name
-without having to define your own class. ``output_field`` can be used to define
-the expected return type.
+into the ``template`` attribute. To avoid a SQL injection vulnerability,
+``extra`` :ref:`must not contain untrusted user input
+<avoiding-sql-injection-in-query-expressions>` as these values are interpolated
+into the SQL string rather than passed as query parameters, where the database
+driver would escape them.
+
+The ``function``, ``template``, and ``arg_joiner`` keywords can be used to
+replace the attributes of the same name without having to define your own
+class. ``output_field`` can be used to define the expected return type.
``Aggregate()`` expressions
---------------------------
@@ -1067,6 +1077,45 @@ Let's see how it works::
Yahoo: Internet Company
Django Software Foundation: No Tagline
+.. _avoiding-sql-injection-in-query-expressions:
+
+Avoiding SQL injection
+~~~~~~~~~~~~~~~~~~~~~~
+
+Since a ``Func``'s keyword arguments for ``__init__()`` (``**extra``) and
+``as_sql()`` (``**extra_context``) are interpolated into the SQL string rather
+than passed as query parameters (where the database driver would escape them),
+they must not contain untrusted user input.
+
+For example, if ``substring`` is user-provided, this function is vulnerable to
+SQL injection::
+
+ from django.db.models import Func
+
+ class Position(Func):
+ function = 'POSITION'
+ template = "%(function)s('%(substring)s' in %(expressions)s)"
+
+ def __init__(self, expression, substring):
+ # substring=substring is a SQL injection vulnerability!
+ super().__init__(expression, substring=substring)
+
+This function generates a SQL string without any parameters. Since ``substring``
+is passed to ``super().__init__()`` as a keyword argument, it's interpolated
+into the SQL string before the query is sent to the database.
+
+Here's a corrected rewrite::
+
+ class Position(Func):
+ function = 'POSITION'
+ arg_joiner = ' IN '
+
+ def __init__(self, expression, substring):
+ super().__init__(substring, expression)
+
+With ``substring`` instead passed as a positional argument, it'll be passed as
+a parameter in the database query.
+
Adding support in third-party database backends
-----------------------------------------------
