Skip to content

Commit 9c01aa2

Browse files
committed
Python: Add modeling for django.shortcuts.redirect
1 parent ff2f2b5 commit 9c01aa2

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

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

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private module Django {
3535
* WARNING: Only holds for a few predefined attributes.
3636
*/
3737
private DataFlow::Node django_attr(DataFlow::TypeTracker t, string attr_name) {
38-
attr_name in ["db", "urls", "http", "conf"] and
38+
attr_name in ["db", "urls", "http", "conf", "shortcuts"] and
3939
(
4040
t.start() and
4141
result = DataFlow::importNode("django" + "." + attr_name)
@@ -1649,6 +1649,62 @@ private module Django {
16491649
}
16501650
}
16511651
}
1652+
1653+
// -------------------------------------------------------------------------
1654+
// django.shortcuts
1655+
// -------------------------------------------------------------------------
1656+
/** Gets a reference to the `django.shortcuts` module. */
1657+
DataFlow::Node shortcuts() { result = django_attr("shortcuts") }
1658+
1659+
/** Provides models for the `django.shortcuts` module */
1660+
module shortcuts {
1661+
/**
1662+
* Gets a reference to the attribute `attr_name` of the `django.shortcuts` module.
1663+
* WARNING: Only holds for a few predefined attributes.
1664+
*/
1665+
private DataFlow::Node shortcuts_attr(DataFlow::TypeTracker t, string attr_name) {
1666+
attr_name in ["redirect"] and
1667+
(
1668+
t.start() and
1669+
result = DataFlow::importNode("django.shortcuts" + "." + attr_name)
1670+
or
1671+
t.startInAttr(attr_name) and
1672+
result = shortcuts()
1673+
)
1674+
or
1675+
// Due to bad performance when using normal setup with `shortcuts_attr(t2, attr_name).track(t2, t)`
1676+
// we have inlined that code and forced a join
1677+
exists(DataFlow::TypeTracker t2 |
1678+
exists(DataFlow::StepSummary summary |
1679+
shortcuts_attr_first_join(t2, attr_name, result, summary) and
1680+
t = t2.append(summary)
1681+
)
1682+
)
1683+
}
1684+
1685+
pragma[nomagic]
1686+
private predicate shortcuts_attr_first_join(
1687+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
1688+
DataFlow::StepSummary summary
1689+
) {
1690+
DataFlow::StepSummary::step(shortcuts_attr(t2, attr_name), res, summary)
1691+
}
1692+
1693+
/**
1694+
* Gets a reference to the attribute `attr_name` of the `django.shortcuts` module.
1695+
* WARNING: Only holds for a few predefined attributes.
1696+
*/
1697+
private DataFlow::Node shortcuts_attr(string attr_name) {
1698+
result = shortcuts_attr(DataFlow::TypeTracker::end(), attr_name)
1699+
}
1700+
1701+
/**
1702+
* Gets a reference to the `django.shortcuts.redirect` function
1703+
*
1704+
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
1705+
*/
1706+
DataFlow::Node redirect() { result = shortcuts_attr("redirect") }
1707+
}
16521708
}
16531709

16541710
// ---------------------------------------------------------------------------
@@ -1956,4 +2012,32 @@ private module Django {
19562012
)
19572013
}
19582014
}
2015+
2016+
// ---------------------------------------------------------------------------
2017+
// django.shortcuts.redirect
2018+
// ---------------------------------------------------------------------------
2019+
/**
2020+
* A call to `django.shortcuts.redirect`.
2021+
*
2022+
* Note: This works differently depending on what argument is used.
2023+
* _One_ option is to redirect to a full URL.
2024+
*
2025+
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
2026+
*/
2027+
private class DjangoShortcutsRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
2028+
DataFlow::CfgNode {
2029+
override CallNode node;
2030+
2031+
DjangoShortcutsRedirectCall() { node.getFunction() = django::shortcuts::redirect().asCfgNode() }
2032+
2033+
override DataFlow::Node getRedirectLocation() {
2034+
result.asCfgNode() in [node.getArg(0), node.getArgByName("to")]
2035+
}
2036+
2037+
override DataFlow::Node getBody() { none() }
2038+
2039+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
2040+
2041+
override string getMimetypeDefault() { none() }
2042+
}
19592043
}

python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def redirect_through_normal_response(request):
5151

5252
def redirect_shortcut(request):
5353
next = request.GET.get("next")
54-
return django.shortcuts.redirect(next) # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next
54+
return django.shortcuts.redirect(next) # $ HttpResponse HttpRedirectResponse redirectLocation=next
5555

5656

5757
# Ensure that simple subclasses are still vuln to XSS

0 commit comments

Comments
 (0)