python.django.security.audit.extends-custom-expression.extends-custom-expression
Community Favorite
semgrep
Author
49,307
Download Count*
License
Found extension of custom expression: $CLASS. Extending expressions in this way could inadvertently lead to a SQL injection vulnerability, which can result in attackers exfiltrating sensitive data. Instead, ensure no user input enters this function or that user input is properly sanitized.
Run Locally
Run in CI
Defintion
rules:
- id: extends-custom-expression
languages:
- python
message: "Found extension of custom expression: $CLASS. Extending expressions in
this way could inadvertently lead to a SQL injection vulnerability, which
can result in attackers exfiltrating sensitive data. Instead, ensure no
user input enters this function or that user input is properly sanitized."
metadata:
cwe:
- "CWE-89: Improper Neutralization of Special Elements used in an SQL
Command ('SQL Injection')"
owasp:
- A01:2017 - Injection
- A03:2021 - Injection
references:
- https://docs.djangoproject.com/en/3.0/ref/models/expressions/#avoiding-sql-injection
- https://semgrep.dev/blog/2020/preventing-sql-injection-a-django-authors-perspective/
category: security
technology:
- django
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- audit
likelihood: LOW
impact: HIGH
confidence: LOW
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
vulnerability_class:
- SQL Injection
severity: WARNING
pattern-either:
- pattern: |
class $CLASS(..., django.db.models.Func, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Func, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Expression, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Expression, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Value, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Value, ...):
...
- pattern: |
class $CLASS(..., django.db.models.DurationValue, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.DurationValue, ...):
...
- pattern: |
class $CLASS(..., django.db.models.RawSQL, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.RawSQL, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Star, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Star, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Random, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Random, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Col, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Col, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Ref, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Ref, ...):
...
- pattern: |
class $CLASS(..., django.db.models.ExpressionList, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.ExpressionList, ...):
...
- pattern: |
class $CLASS(..., django.db.models.ExpressionWrapper, ...):
...
- pattern: >
class $CLASS(..., django.db.models.expressions.ExpressionWrapper,
...):
...
- pattern: |
class $CLASS(..., django.db.models.When, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.When, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Case, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Case, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Subquery, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Subquery, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Exists, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Exists, ...):
...
- pattern: |
class $CLASS(..., django.db.models.Window, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.Window, ...):
...
- pattern: |
class $CLASS(..., django.db.models.WindowFrame, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.WindowFrame, ...):
...
- pattern: |
class $CLASS(..., django.db.models.RowRange, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.RowRange, ...):
...
- pattern: |
class $CLASS(..., django.db.models.ValueRange, ...):
...
- pattern: |
class $CLASS(..., django.db.models.expressions.ValueRange, ...):
...
Examples
extends-custom-expression.py
from django.db.models import (
CharField, Expression, Field, FloatField, Lookup, TextField, Value,
)
from django.db.models.expressions import CombinedExpression, Func, Subquery
from django.db.models.functions import Cast, Coalesce
# ok: extends-custom-expression
class Star(CharField):
pass
# ok: extends-custom-expression
class MoreStar(Star):
pass
# ruleid: extends-custom-expression
class Position(Func):
function = 'POSITION'
template = "%(function)s('%(substring)s' in %(expressions)s)"
# todoruleid: extends-custom-expression
def __init__(self, expression, substring):
# substring=substring is a SQL injection vulnerability!
super().__init__(expression, substring=substring)
# ruleid: extends-custom-expression
class Aggregate(Func):
template = '%(function)s(%(distinct)s%(expressions)s)'
contains_aggregate = True
name = None
filter_template = '%s FILTER (WHERE %%(filter)s)'
window_compatible = True
allow_distinct = False
def __init__(self, *expressions, distinct=False, filter=None, **extra):
if distinct and not self.allow_distinct:
raise TypeError("%s does not allow distinct." % self.__class__.__name__)
self.distinct = distinct
self.filter = filter
super().__init__(*expressions, **extra)
# ruleid: extends-custom-expression
class CastToInteger(Func):
"""
A helper class for casting values to signed integer in database.
"""
function = 'CAST'
template = '%(function)s(%(expressions)s as %(integer_type)s)'
def __init__(self, *expressions, **extra):
super().__init__(*expressions, **extra)
self.extra['integer_type'] = 'INTEGER'
def as_mysql(self, compiler, connection):
self.extra['integer_type'] = 'SIGNED'
return super().as_sql(compiler, connection)
# ruleid: extends-custom-expression
class DateFormat(Func):
function = 'DATE_FORMAT'
template = '%(function)s(%(expressions)s, "%(format)s")'
def __init__(self, *expressions, **extra):
strf = extra.pop('format', None)
extra['format'] = strf.replace("%", "%%")
extra['output_field'] = CharField()
super(DateFormat, self).__init__(*expressions, **extra)
# ruleid: extends-custom-expression
class StrFtime(Func):
function = 'strftime'
template = '%(function)s("%(format)s", %(expressions)s)'
def __init__(self, *expressions, **extra):
strf = extra.pop('format', None)
extra['format'] = strf.replace("%", "%%")
extra['output_field'] = CharField()
super(StrFtime, self).__init__(*expressions, **extra)
# ruleid: extends-custom-expression
class RandomUUID(Func):
template = 'GEN_RANDOM_UUID()'
output_field = UUIDField()
# ruleid: extends-custom-expression
class SearchHeadline(Func):
function = 'ts_headline'
template = '%(function)s(%(expressions)s%(options)s)'
output_field = TextField()
def __init__(
self, expression, query, *, config=None, start_sel=None, stop_sel=None,
max_words=None, min_words=None, short_word=None, highlight_all=None,
max_fragments=None, fragment_delimiter=None,
):
if not hasattr(query, 'resolve_expression'):
query = SearchQuery(query)
options = {
'StartSel': start_sel,
'StopSel': stop_sel,
'MaxWords': max_words,
'MinWords': min_words,
'ShortWord': short_word,
'HighlightAll': highlight_all,
'MaxFragments': max_fragments,
'FragmentDelimiter': fragment_delimiter,
}
self.options = {
option: value
for option, value in options.items() if value is not None
}
expressions = (expression, query)
if config is not None:
config = SearchConfig.from_parameter(config)
expressions = (config,) + expressions
super().__init__(*expressions)
# ruleid: extends-custom-expression
class _Str(Func):
"""Casts a value to the database's text type."""
function = "CAST"
template = "%(function)s(%(expressions)s as %(db_type)s)"
def __init__(self, expression):
super().__init__(expression, output_field=models.TextField())
def as_sql(self, compiler, connection):
self.extra["db_type"] = self.output_field.db_type(connection)
return super().as_sql(compiler, connection)
# ruleid: extends-custom-expression
class SQCount(Subquery):
template = "(SELECT count(*) FROM (%(subquery)s) _count)"
output_field = IntegerField()
Short Link: https://sg.run/N4Ay