Skip to content

Commit 8d0f608

Browse files
committed
Python: Model django forms/fields
I'm not feeling 100% confident about `SelfRefMixin`, but since I needed it for both DjangoViewClass and DjangoFormClass, I wanted to avoid copy-pasting this code around. However, I'm not so opitimistic about it that I want to add it to a sharable utility qll file :D
1 parent 3a83ecf commit 8d0f608

File tree

3 files changed

+150
-35
lines changed

3 files changed

+150
-35
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Improved modeling of `django` to recognize sources of remote user input (`RemoteFlowSource`) in Django forms (`django.forms.Form`) and fields (`django.forms.Field`) subclasses.

python/ql/src/semmle/python/frameworks/Django.qll

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.python.dataflow.new.DataFlow
88
private import semmle.python.dataflow.new.RemoteFlowSources
99
private import semmle.python.dataflow.new.TaintTracking
1010
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
1112
private import semmle.python.frameworks.PEP249
1213
private import semmle.python.regex
1314

@@ -1975,6 +1976,50 @@ private module Django {
19751976
}
19761977
}
19771978

1979+
/** Provides models for django forms (defined in the `django.forms` module) */
1980+
module Forms {
1981+
/**
1982+
* Provides models for the `django.forms.Form` class and subclasses.
1983+
*
1984+
* See https://docs.djangoproject.com/en/3.1/ref/forms/api/
1985+
*/
1986+
module Form {
1987+
/** Gets a reference to the `django.forms.Form` class or any subclass. */
1988+
API::Node subclassRef() {
1989+
result =
1990+
API::moduleImport("django")
1991+
.getMember("forms")
1992+
.getMember([
1993+
"Form"
1994+
// TODO: Known subclasses
1995+
])
1996+
.getASubclass*()
1997+
}
1998+
}
1999+
2000+
/**
2001+
* Provides models for the `django.forms.Field` class and subclasses.
2002+
*
2003+
* See https://docs.djangoproject.com/en/3.1/ref/forms/fields/
2004+
*/
2005+
module Field {
2006+
/** Gets a reference to the `django.forms.Form` class or any subclass. */
2007+
API::Node subclassRef() {
2008+
result =
2009+
API::moduleImport("django")
2010+
.getMember("forms")
2011+
.getMember([
2012+
"Field"
2013+
// TODO: Known subclasses
2014+
])
2015+
.getASubclass*()
2016+
}
2017+
}
2018+
}
2019+
2020+
// ---------------------------------------------------------------------------
2021+
// Helpers
2022+
// ---------------------------------------------------------------------------
19782023
/**
19792024
* Gets the last decorator call for the function `func`, if `func` has decorators.
19802025
*/
@@ -1983,6 +2028,97 @@ private module Django {
19832028
not exists(Call other_decorator | other_decorator.getArg(0) = result)
19842029
}
19852030

2031+
/** Adds the `getASelfRef` member predicate when modeling a class. */
2032+
abstract private class SelfRefMixin extends Class {
2033+
/**
2034+
* Gets a reference to instances of this class, originating from a self parameter of
2035+
* a method defined on this class.
2036+
*
2037+
* Note: TODO: This doesn't take MRO into account
2038+
* Note: TODO: This doesn't take staticmethod/classmethod into account
2039+
*/
2040+
private DataFlow::Node getASelfRef(DataFlow::TypeTracker t) {
2041+
t.start() and
2042+
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
2043+
or
2044+
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
2045+
}
2046+
2047+
/**
2048+
* Gets a reference to instances of this class, originating from a self parameter of
2049+
* a method defined on this class.
2050+
*
2051+
* Note: TODO: This doesn't take MRO into account
2052+
* Note: TODO: This doesn't take staticmethod/classmethod into account
2053+
*/
2054+
DataFlow::Node getASelfRef() { result = this.getASelfRef(DataFlow::TypeTracker::end()) }
2055+
}
2056+
2057+
// ---------------------------------------------------------------------------
2058+
// Form and form field modeling
2059+
// ---------------------------------------------------------------------------
2060+
/**
2061+
* A class that is a subclass of the `django.forms.Form` class,
2062+
* thereby handling user input.
2063+
*/
2064+
class DjangoFormClass extends Class, SelfRefMixin {
2065+
DjangoFormClass() { this.getABase() = Django::Forms::Form::subclassRef().getAUse().asExpr() }
2066+
}
2067+
2068+
/**
2069+
* A source of cleaned_data (either the return value from `super().clean()`, or a reference to `self.cleaned_data`)
2070+
*
2071+
* See https://docs.djangoproject.com/en/3.1/ref/forms/validation/#form-and-field-validation
2072+
*/
2073+
private class DjangoFormCleanedData extends RemoteFlowSource::Range, DataFlow::Node {
2074+
DjangoFormCleanedData() {
2075+
exists(DjangoFormClass cls, Function meth |
2076+
cls.getAMethod() = meth and
2077+
(
2078+
this = API::builtin("super").getReturn().getMember("clean").getACall() and
2079+
this.getScope() = meth
2080+
or
2081+
this.(DataFlow::AttrRead).getAttributeName() = "cleaned_data" and
2082+
this.(DataFlow::AttrRead).getObject() = cls.getASelfRef()
2083+
)
2084+
)
2085+
}
2086+
2087+
override string getSourceType() {
2088+
result = "django.forms.Field subclass, value parameter in method"
2089+
}
2090+
}
2091+
2092+
/**
2093+
* A class that is a subclass of the `django.forms.Field` class,
2094+
* thereby handling user input.
2095+
*/
2096+
class DjangoFormFieldClass extends Class {
2097+
DjangoFormFieldClass() {
2098+
this.getABase() = Django::Forms::Field::subclassRef().getAUse().asExpr()
2099+
// api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
2100+
}
2101+
}
2102+
2103+
/**
2104+
* A parameter in a method on a `DjangoFormFieldClass` that receives the user-supplied value for this field.
2105+
*
2106+
* See https://docs.djangoproject.com/en/3.1/ref/forms/validation/#form-and-field-validation
2107+
*/
2108+
private class DjangoFormFieldValueParam extends RemoteFlowSource::Range, DataFlow::ParameterNode {
2109+
DjangoFormFieldValueParam() {
2110+
exists(DjangoFormFieldClass cls, Function meth |
2111+
cls.getAMethod() = meth and
2112+
meth.getName() in ["to_python", "validate", "run_validators", "clean"] and
2113+
this.getParameter() = meth.getArg(1)
2114+
)
2115+
}
2116+
2117+
override string getSourceType() {
2118+
result = "django.forms.Field subclass, value parameter in method"
2119+
}
2120+
}
2121+
19862122
// ---------------------------------------------------------------------------
19872123
// routing modeling
19882124
// ---------------------------------------------------------------------------
@@ -2068,7 +2204,7 @@ private module Django {
20682204
}
20692205

20702206
/** A class that we consider a django View class. */
2071-
abstract class DjangoViewClass extends DjangoViewClassHelper {
2207+
abstract class DjangoViewClass extends DjangoViewClassHelper, SelfRefMixin {
20722208
/** Gets a function that could handle incoming requests, if any. */
20732209
Function getARequestHandler() {
20742210
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
@@ -2080,29 +2216,6 @@ private module Django {
20802216
result.getName() = "get_redirect_url"
20812217
)
20822218
}
2083-
2084-
/**
2085-
* Gets a reference to instances of this class, originating from a self parameter of
2086-
* a method defined on this class.
2087-
*
2088-
* Note: TODO: This doesn't take MRO into account
2089-
* Note: TODO: This doesn't take staticmethod/classmethod into account
2090-
*/
2091-
private DataFlow::Node getASelfRef(DataFlow::TypeTracker t) {
2092-
t.start() and
2093-
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
2094-
or
2095-
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
2096-
}
2097-
2098-
/**
2099-
* Gets a reference to instances of this class, originating from a self parameter of
2100-
* a method defined on this class.
2101-
*
2102-
* Note: TODO: This doesn't take MRO into account
2103-
* Note: TODO: This doesn't take staticmethod/classmethod into account
2104-
*/
2105-
DataFlow::Node getASelfRef() { result = this.getASelfRef(DataFlow::TypeTracker::end()) }
21062219
}
21072220

21082221
/**

python/ql/test/library-tests/frameworks/django-v2-v3/TestTaint.expected

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
| response_test.py:61 | ok | get_redirect_url | foo |
2-
| taint_forms.py:6 | fail | to_python | value |
3-
| taint_forms.py:9 | fail | validate | value |
4-
| taint_forms.py:12 | fail | run_validators | value |
5-
| taint_forms.py:15 | fail | clean | value |
6-
| taint_forms.py:33 | fail | clean | cleaned_data |
7-
| taint_forms.py:34 | fail | clean | cleaned_data["key"] |
8-
| taint_forms.py:35 | fail | clean | cleaned_data.get(..) |
9-
| taint_forms.py:39 | fail | clean | self.cleaned_data |
10-
| taint_forms.py:40 | fail | clean | self.cleaned_data["key"] |
11-
| taint_forms.py:41 | fail | clean | self.cleaned_data.get(..) |
12-
| taint_forms.py:46 | fail | clean_foo | self.cleaned_data |
2+
| taint_forms.py:6 | ok | to_python | value |
3+
| taint_forms.py:9 | ok | validate | value |
4+
| taint_forms.py:12 | ok | run_validators | value |
5+
| taint_forms.py:15 | ok | clean | value |
6+
| taint_forms.py:33 | ok | clean | cleaned_data |
7+
| taint_forms.py:34 | ok | clean | cleaned_data["key"] |
8+
| taint_forms.py:35 | ok | clean | cleaned_data.get(..) |
9+
| taint_forms.py:39 | ok | clean | self.cleaned_data |
10+
| taint_forms.py:40 | ok | clean | self.cleaned_data["key"] |
11+
| taint_forms.py:41 | ok | clean | self.cleaned_data.get(..) |
12+
| taint_forms.py:46 | ok | clean_foo | self.cleaned_data |
1313
| taint_test.py:8 | ok | test_taint | bar |
1414
| taint_test.py:8 | ok | test_taint | foo |
1515
| taint_test.py:9 | ok | test_taint | baz |

0 commit comments

Comments
 (0)