From 865337ae925668ac5296955f01949354a0113b01 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:26:10 +0100 Subject: [PATCH] [5.2.x] Added security reporting guidelines. Backport of 59353360590202fab04067e23214a825157c524b from main. --- docs/internals/security.txt | 125 ++++++++++++++++++++++++++++++++++++ docs/topics/security.txt | 10 +++ 2 files changed, 135 insertions(+) diff --git a/docs/internals/security.txt b/docs/internals/security.txt index f0a3e85f64..e4801c19ee 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -43,6 +43,131 @@ the industry-standard 90 days. Confirmed vulnerabilities with a .. _our public Trac instance: https://code.djangoproject.com/query +Reporting guidelines +-------------------- + +Include a runnable proof of concept +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Please privately share a minimal Django project or code snippet that +demonstrates the potential vulnerability. Include clear instructions on how to +set up, run, and reproduce the issue. + +Please do not attach screenshots of code. + +User input must be sanitized +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Reports based on a failure to sanitize user input are not valid security +vulnerabilities. It is the developer's responsibility to properly handle user +input. This principle is explained in our :ref:`security documentation +`. + +For example, the following is **not considered valid** because ``email`` has +not been sanitized:: + + from django.core.mail import send_mail + from django.http import JsonResponse + + + def my_proof_of_concept(request): + email = request.GET.get("email", "") + send_mail("Email subject", "Email body", email, ["admin@example.com"]) + return JsonResponse(status=200) + +Developers must **always validate and sanitize input** before using it. The +correct approach would be to use a Django form to ensure ``email`` is properly +validated:: + + from django import forms + from django.core.mail import send_mail + from django.http import JsonResponse + + + class EmailForm(forms.Form): + email = forms.EmailField() + + + def my_proof_of_concept(request): + form = EmailForm(request.GET) + if form.is_valid(): + send_mail( + "Email subject", + "Email body", + form.cleaned_data["email"], + ["admin@example.com"], + ) + return JsonResponse(status=200) + return JsonResponse(form.errors, status=400) + +Similarly, as Django's raw SQL constructs (such as :meth:`~.QuerySet.extra` and +:class:`.RawSQL` expression) provide developers with full control over the +query, they are insecure if user input is not properly handled. As explained in +our :ref:`security documentation `, it is the +developer's responsibility to safely process user input for these functions. + +For instance, the following is **not considered valid** because ``query`` has +not been sanitized:: + + from django.shortcuts import HttpResponse + from .models import MyModel + + + def my_proof_of_concept(request): + query = request.GET.get("query", "") + q = MyModel.objects.extra(select={"id": query}) + return HttpResponse(q.values()) + +Request headers and URLs must be under 8K bytes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To prevent denial-of-service (DoS) attacks, production-grade servers impose +limits on request header and URL sizes. For example, by default Gunicorn allows +up to roughly: + +* `4k bytes for a URL`_ +* `8K bytes for a request header`_ + +Other web servers, such as Nginx and Apache, have similar restrictions to +prevent excessive resource consumption. + +Consequently, the Django security team will not consider reports that rely on +request headers or URLs exceeding 8K bytes, as such inputs are already +mitigated at the server level in production environments. + +.. admonition:: :djadmin:`runserver` should never be used in production + + Django's built-in development server does not enforce these limits because + it is not designed to be a production server. + +.. _`4k bytes for a URL`: https://docs.gunicorn.org/en/stable/settings.html#limit-request-line +.. _`8k bytes for a request header`: https://docs.gunicorn.org/en/stable/settings.html#limit-request-field-size + +The request body must be under 2.5 MB +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :setting:`DATA_UPLOAD_MAX_MEMORY_SIZE` setting limits the default maximum +request body size to 2.5 MB. + +As this is enforced on all production-grade Django projects by default, a proof +of concept must not exceed 2.5 MB in the request body to be considered valid. + +Issues resulting from large, but potentially reasonable setting values, should +be reported using the `public ticket tracker`_ for hardening. + +.. _public ticket tracker: https://code.djangoproject.com/ + +Code under test must feasibly exist in a Django project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The proof of concept must plausibly occur in a production-grade Django +application, reflecting real-world scenarios and following standard development +practices. + +Django contains many private and undocumented functions that are not part of +its public API. If a vulnerability depends on directly calling these internal +functions in an unsafe way, it will not be considered a valid security issue. + .. _security-report-evaluation: How does Django evaluate a report diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 0f6f05163a..2cc27786d3 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -5,6 +5,16 @@ Security in Django This document is an overview of Django's security features. It includes advice on securing a Django-powered site. +.. _sanitize-user-input: + +Always sanitize user input +========================== + +The golden rule of web application security is to never trust user-controlled +data. Hence, all user input should be sanitized before being used in your +application. See the :doc:`forms documentation ` for +details on validating user inputs in Django. + .. _cross-site-scripting: Cross site scripting (XSS) protection