python: broaden local protection concept

This commit is contained in:
Rasmus Lerchedahl Petersen
2022-03-25 12:28:33 +01:00
parent 179f77b123
commit 1e9840d779
5 changed files with 34 additions and 24 deletions

View File

@@ -123,7 +123,7 @@ class CsrfProtectionSetting extends DataFlow::Node instanceof CsrfProtectionSett
/** Provides a class for modeling new CSRF protection setting APIs. */ /** Provides a class for modeling new CSRF protection setting APIs. */
module CsrfProtectionSetting { module CsrfProtectionSetting {
/** /**
* A data-flow node that may set or unset Cross-site request forgery protection * A data-flow node that enables or disables Cross-site request forgery protection
* in a global manner. * in a global manner.
* *
* Extend this class to model new APIs. If you want to refine existing API models, * Extend this class to model new APIs. If you want to refine existing API models,
@@ -139,35 +139,39 @@ module CsrfProtectionSetting {
} }
/** /**
* A data-flow node that provides Cross-site request forgery protection * A data-flow node that enables or disables Cross-site request forgery protection
* for a specific part of an application. * for a specific part of an application.
* *
* Extend this class to refine existing API models. If you want to model new APIs, * Extend this class to refine existing API models. If you want to model new APIs,
* extend `CsrfLocalProtection::Range` instead. * extend `CsrfLocalProtectionSetting::Range` instead.
*/ */
class CsrfLocalProtection extends DataFlow::Node instanceof CsrfLocalProtection::Range { class CsrfLocalProtectionSetting extends DataFlow::Node instanceof CsrfLocalProtectionSetting::Range {
/** /**
* Gets a `Function` representing the protected interaction * Gets a request handler whose CSRF protection is changed.
* (probably a request handler).
*/ */
Function getProtected() { result = super.getProtected() } Function getRequestHandler() { result = super.getRequestHandler() }
/** Holds if CSRF protection is enabled by this setting */
predicate csrfEnabled() { super.csrfEnabled() }
} }
/** Provides a class for modeling new CSRF protection setting APIs. */ /** Provides a class for modeling new CSRF protection setting APIs. */
module CsrfLocalProtection { module CsrfLocalProtectionSetting {
/** /**
* A data-flow node that provides Cross-site request forgery protection * A data-flow node that enables or disables Cross-site request forgery protection
* for a specific part of an application. * for a specific part of an application.
* *
* Extend this class to model new APIs. If you want to refine existing API models, * Extend this class to model new APIs. If you want to refine existing API models,
* extend `CsrfLocalProtection` instead. * extend `CsrfLocalProtectionSetting` instead.
*/ */
abstract class Range extends DataFlow::Node { abstract class Range extends DataFlow::Node {
/** /**
* Gets a `Function` representing the protected interaction * Gets a request handler whose CSRF protection is changed.
* (probably a request handler).
*/ */
abstract Function getProtected(); abstract Function getRequestHandler();
/** Holds if CSRF protection is enabled by this setting */
abstract predicate csrfEnabled();
} }
} }

View File

@@ -2356,20 +2356,24 @@ module PrivateDjango {
} }
} }
private class DjangoCsrfDecorator extends CsrfLocalProtection::Range { private class DjangoCsrfDecorator extends CsrfLocalProtectionSetting::Range {
string decoratorName;
Function function; Function function;
DjangoCsrfDecorator() { DjangoCsrfDecorator() {
decoratorName in ["csrf_protect", "csrf_exempt", "requires_csrf_token", "ensure_csrf_cookie"] and
this = this =
API::moduleImport("django") API::moduleImport("django")
.getMember("views") .getMember("views")
.getMember("decorators") .getMember("decorators")
.getMember("csrf") .getMember("csrf")
.getMember("csrf_protect") .getMember(decoratorName)
.getAUse() and .getAUse() and
this.asExpr() = function.getADecorator() this.asExpr() = function.getADecorator()
} }
override Function getProtected() { result = function } override Function getRequestHandler() { result = function }
override predicate csrfEnabled() { decoratorName in ["csrf_protect", "requires_csrf_token"] }
} }
} }

View File

@@ -17,7 +17,7 @@ import semmle.python.Concepts
from CsrfProtectionSetting s from CsrfProtectionSetting s
where where
s.getVerificationSetting() = false and s.getVerificationSetting() = false and
not exists(CsrfLocalProtection p) and not exists(CsrfLocalProtectionSetting p | p.csrfEnabled()) and
// rule out test code as this is a common place to turn off CSRF protection // rule out test code as this is a common place to turn off CSRF protection
not s.getLocation().getFile().getAbsolutePath().matches("%test%") not s.getLocation().getFile().getAbsolutePath().matches("%test%")
select s, "Potential CSRF vulnerability due to forgery protection being disabled or weakened." select s, "Potential CSRF vulnerability due to forgery protection being disabled or weakened."

View File

@@ -520,18 +520,20 @@ class CsrfProtectionSettingTest extends InlineExpectationsTest {
} }
} }
class CsrfLocalProtectionTest extends InlineExpectationsTest { class CsrfLocalProtectionSettingTest extends InlineExpectationsTest {
CsrfLocalProtectionTest() { this = "CsrfLocalProtectionTest" } CsrfLocalProtectionSettingTest() { this = "CsrfLocalProtectionSettingTest" }
override string getARelevantTag() { result = "CsrfLocalProtection" } override string getARelevantTag() { result = "CsrfLocalProtection" + ["Enabled", "Disabled"] }
override predicate hasActualResult(Location location, string element, string tag, string value) { override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and exists(location.getFile().getRelativePath()) and
exists(CsrfLocalProtection p | exists(CsrfLocalProtectionSetting p |
location = p.getLocation() and location = p.getLocation() and
element = p.toString() and element = p.toString() and
value = p.getProtected().getName().toString() and value = p.getRequestHandler().getName().toString() and
tag = "CsrfLocalProtection" if p.csrfEnabled()
then tag = "CsrfLocalProtectionEnabled"
else tag = "CsrfLocalProtectionDisabled"
) )
} }
} }

View File

@@ -118,7 +118,7 @@ class CustomJsonResponse(JsonResponse):
def __init__(self, banner, content, *args, **kwargs): def __init__(self, banner, content, *args, **kwargs):
super().__init__(content, *args, content_type="text/html", **kwargs) super().__init__(content, *args, content_type="text/html", **kwargs)
@csrf_protect # $CsrfLocalProtection=safe__custom_json_response @csrf_protect # $CsrfLocalProtectionEnabled=safe__custom_json_response
def safe__custom_json_response(request): def safe__custom_json_response(request):
return CustomJsonResponse("ACME Responses", {"foo": request.GET.get("foo")}) # $HttpResponse mimetype=application/json MISSING: responseBody=Dict SPURIOUS: responseBody="ACME Responses" return CustomJsonResponse("ACME Responses", {"foo": request.GET.get("foo")}) # $HttpResponse mimetype=application/json MISSING: responseBody=Dict SPURIOUS: responseBody="ACME Responses"