From 93750fe17fbc1133642d6a7af63826b314d19d15 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Fri, 4 Mar 2022 12:47:23 +0100 Subject: [PATCH] python: minimal CSRF implementation - currectly only looks for custom django middleware --- python/ql/lib/semmle/python/Concepts.qll | 31 ++++++++++ .../lib/semmle/python/frameworks/Django.qll | 31 ++++++++++ .../CWE-352/CSRFProtectionDisabled.qhelp | 60 +++++++++++++++++++ .../CWE-352/CSRFProtectionDisabled.ql | 19 ++++++ .../src/Security/CWE-352/examples/setting.py | 9 +++ 5 files changed, 150 insertions(+) create mode 100644 python/ql/src/Security/CWE-352/CSRFProtectionDisabled.qhelp create mode 100644 python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql create mode 100644 python/ql/src/Security/CWE-352/examples/setting.py diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 6c67b0e5d91..8e4f810d4a0 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -105,6 +105,37 @@ module FileSystemWriteAccess { } } +/** + * A data-flow node that may set or unset Cross-site request forgery protection. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `CSRFProtectionSetting::Range` instead. + */ +class CSRFProtectionSetting extends DataFlow::Node instanceof CSRFProtectionSetting::Range { + /** + * Gets the boolean value corresponding to if CSRF protection is enabled + * (`true`) or disabled (`false`) by this node. + */ + boolean getVerificationSetting() { result = super.getVerificationSetting() } +} + +/** Provides a class for modeling new CSRF protection setting APIs. */ +module CSRFProtectionSetting { + /** + * A data-flow node that may set or unset Cross-site request forgery protection. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `CSRFProtectionSetting` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets the boolean value corresponding to if CSRF protection is enabled + * (`true`) or disabled (`false`) by this node. + */ + abstract boolean getVerificationSetting(); + } +} + /** Provides classes for modeling path-related APIs. */ module Path { /** diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index 8f34043f093..f5989badfe4 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2313,4 +2313,35 @@ module PrivateDjango { .getAnImmediateUse() } } + + // --------------------------------------------------------------------------- + // Settings + // --------------------------------------------------------------------------- + /** + * A custom middleware stack + */ + private class DjangoSettingsMiddlewareStack extends CSRFProtectionSetting::Range { + List list; + + DjangoSettingsMiddlewareStack() { + this.asExpr() = list and + // we look for an assignment to the `MIDDLEWARE` setting + exists(DataFlow::Node mw, string djangomw | + mw.asVar().getName() = "MIDDLEWARE" and + DataFlow::localFlow(this, mw) + | + // check that the list contains at least one reference to `django` + list.getAnElt().(StrConst).getText() = djangomw and + // TODO: Consider requiring `django.middleware.security.SecurityMiddleware` + // or something indicating that a security middleware is enabled. + djangomw.matches("django.%") + ) + } + + override boolean getVerificationSetting() { + if list.getAnElt().(StrConst).getText() = "django.middleware.csrf.CsrfViewMiddleware" + then result = true + else result = false + } + } } diff --git a/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.qhelp b/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.qhelp new file mode 100644 index 00000000000..98a5dae20ba --- /dev/null +++ b/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.qhelp @@ -0,0 +1,60 @@ + + + + +

+ Cross-site request forgery (CSRF) is a type of vulnerability in which an + attacker is able to force a user carry out an action that the user did + not intend. +

+ +

+ The attacker tricks an authenticated user into submitting a request to the + web application. Typically this request will result in a state change on + the server, such as changing the user's password. The request can be + initiated when the user visits a site controlled by the attacker. If the + web application relies only on cookies for authentication, or on other + credentials that are automatically included in the request, then this + request will appear as legitimate to the server. +

+ +

+ A common countermeasure for CSRF is to generate a unique token to be + included in the HTML sent from the server to a user. This token can be + used as a hidden field to be sent back with requests to the server, where + the server can then check that the token is valid and associated with the + relevant user session. +

+
+ + +

+ In many web frameworks, CSRF protection is enabled by default. In these + cases, using the default configuration is sufficient to guard against most + CSRF attacks. +

+
+ + +

+ The following example shows a case where CSRF protection is disabled by + overriding the default middleware stack and not including the one protecting against CSRF. +

+ + + +

+ The protecting middleware was probably commented out during a testing phase, when server-side token generation was not set up. + Simply commenting it back in (or remove the custom middleware stack) will enable CSRF protection. +

+ +
+ + +
  • Wikipedia: Cross-site request forgery
  • +
  • OWASP: Cross-site request forgery
  • +
    + +
    diff --git a/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql b/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql new file mode 100644 index 00000000000..00f2cad5050 --- /dev/null +++ b/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql @@ -0,0 +1,19 @@ +/** + * @name CSRF protection weakened or disabled + * @description Disabling or weakening CSRF protection may make the application + * vulnerable to a Cross-Site Request Forgery (CSRF) attack. + * @kind problem + * @problem.severity warning + * @security-severity 8.8 + * @precision high + * @id py/csrf-protection-disabled + * @tags security + * external/cwe/cwe-352 + */ + +import python +import semmle.python.Concepts + +from CSRFProtectionSetting s +where s.getVerificationSetting() = false +select s, "Potential CSRF vulnerability due to forgery protection being disabled or weakened." diff --git a/python/ql/src/Security/CWE-352/examples/setting.py b/python/ql/src/Security/CWE-352/examples/setting.py new file mode 100644 index 00000000000..d1f1f983cef --- /dev/null +++ b/python/ql/src/Security/CWE-352/examples/setting.py @@ -0,0 +1,9 @@ +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +]