VNX-352: Cross-Site Request Forgery protection disabled (Django, Flask-WTF, Node.js, Rails)
Reports a web framework's built-in CSRF protection switched off: Django @csrf_exempt and a MIDDLEWARE list without CsrfViewMiddleware, Flask-WTF WTF_CSRF_ENABLED = False and csrf exemptions, Node.js csrf: false / checkOrigin: false / Apollo csrfPrevention: false / csurf ignoreMethods with POST, and Rails skip_before_action :verify_authenticity_token, protect_from_forgery except:, allow_forgery_protection = false or an ApplicationController with no protect_from_forgery.
| Field | Value |
|---|---|
| Rule ID | VNX-352 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python, Ruby |
| CWE | CWE-352 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | csrf cwe-352 |
| Source | internal/sast/rules/vnx-352.rego |
Overview
VNX-352 maps to CWE-352: Cross-Site Request Forgery (CSRF). It reports the places where an application turns off the CSRF defence its framework already provides:
- Django:
@csrf_exempt,method_decorator(csrf_exempt, ...),csrf_exempt(view), and aMIDDLEWARE/MIDDLEWARE_CLASSESlist withoutdjango.middleware.csrf.CsrfViewMiddleware(a commented-out entry counts as absent;MIDDLEWARE += [...CsrfViewMiddleware]elsewhere in the file counts as present); - Flask-WTF:
WTF_CSRF_ENABLED = False,WTF_CSRF_CHECK_DEFAULT = False,app.config["WTF_CSRF_ENABLED"] = False,app.config.update(WTF_CSRF_ENABLED=False),@csrf.exempt, a formclass Meta: csrf = Falseandmeta={"csrf": False}; - Node.js:
csrf: falsein an object literal (lusca, Sailsconfig/security.js), SvelteKit/AstrocheckOrigin: false, Apollo ServercsrfPrevention: false, and csurfignoreMethodslisting POST, PUT, PATCH or DELETE; - Rails:
skip_before_action(or Rails 4skip_before_filter):verify_authenticity_token,protect_from_forgery except:/only:,skip_forgery_protection,allow_forgery_protection = falseoutsideconfig/environments/test.rb, and anApplicationController < ActionController::Basewith noprotect_from_forgerywhenconfig/application.rbneither callsload_defaults 5.2or later nor setsdefault_protect_from_forgery.
Not reported: settings in a class whose name contains Test or under an if ... testing branch, test files, the import of csrf_exempt, csrf_protect/ensure_csrf_cookie, text in string literals and comments, and ActionController::API controllers. Spring Security is covered by VNX-JAVA-010 and ASP.NET MVC by VNX-CS-005.
Why This Matters
With the CSRF check off, any page the victim visits can submit a form or a fetch to the application, and the browser attaches the victim’s session cookie. Transfers, password changes and deletions then run as the victim.
What Gets Flagged
# FLAGGED
@csrf_exempt
def cancel_order(request, order_id): ...
class Config:
WTF_CSRF_ENABLED = False
// FLAGGED
app.use(lusca({ csrf: false }));
app.use(csurf({ cookie: true, ignoreMethods: ['GET', 'HEAD', 'OPTIONS', 'POST'] }));
# FLAGGED
class PaymentsController < ApplicationController
skip_before_action :verify_authenticity_token
end
# NOT FLAGGED: the switch-off is confined to the test configuration
class TestingConfig(Config):
WTF_CSRF_ENABLED = False
Remediation
- Remove the exemption and send the CSRF token from the client (Django
{% csrf_token %}, Flask-WTFform.hidden_tag(), Railscsrf_meta_tags). - Keep
CsrfViewMiddlewareinMIDDLEWAREandprotect_from_forgery with: :exception(orload_defaults5.2+) in Rails. - For an endpoint that genuinely cannot carry a token (a signed webhook), verify the request’s signature instead, and keep the exemption to that single view.
- Set session cookies
SameSite=LaxorStrictas an additional layer.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-352
// nosec vnx-352
Or record an ignore rule: vulnetix ignore add --rule vnx-352 --reason "...". See Suppressing findings.