python.django.security.audit.secure-cookies.django-secure-set-cookie

Community Favorite
profile photo of semgrepsemgrep
Author
49,111
Download Count*

Django cookies should be handled securely by setting secure=True, httponly=True, and samesite='Lax' in response.set_cookie(...). If your situation calls for different settings, explicitly disable the setting. If you want to send the cookie over http, set secure=False. If you want to let client-side JavaScript read the cookie, set httponly=False. If you want to attach cookies to requests for external sites, set samesite=None.

Run Locally

Run in CI

Defintion

rules:
  - id: django-secure-set-cookie
    patterns:
      - pattern-either:
          - pattern-inside: |
              import django.http.HttpResponse
              ...
          - pattern-inside: |
              import django.shortcuts.render
              ...
      - pattern-not-inside: |
          LANGUAGE_QUERY_PARAMETER = 'language'
          ...
          def set_language(request):
              ...
          # Exclude vendored contrib/messages/storage/cookie.py
      - pattern-not-inside: |
          class CookieStorage(django.contrib.messages.storage.base.BaseStorage):
              ...
          # Exclude cookies handled by vendored middleware
      - pattern-not: response.set_cookie(django.conf.settings.SESSION_COOKIE_NAME, ...)
      - pattern-not: response.set_cookie(django.conf.settings.CSRF_COOKIE_NAME, ...)
      - pattern-not: response.set_cookie(django.conf.settings.LANGUAGE_COOKIE_NAME, ...)
      - pattern-not: response.set_cookie(rest_framework_jwt.settings.api_settings.JWT_AUTH_COOKIE,
          ...)
      - pattern-not: response.set_cookie(..., secure=$A, httponly=$B, samesite=$C, ...)
      - pattern-not: response.set_cookie(..., **$A)
      - pattern: response.set_cookie(...)
    message: Django cookies should be handled securely by setting secure=True,
      httponly=True, and samesite='Lax' in response.set_cookie(...). If your
      situation calls for different settings, explicitly disable the setting. If
      you want to send the cookie over http, set secure=False. If you want to
      let client-side JavaScript read the cookie, set httponly=False. If you
      want to attach cookies to requests for external sites, set samesite=None.
    metadata:
      cwe:
        - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute"
      owasp:
        - A05:2021 - Security Misconfiguration
      asvs:
        section: "V3: Session Management Verification Requirements"
        control_id: 3.4 Missing Cookie Attributes
        control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x12-V3-Session-management.md#v34-cookie-based-session-management
        version: "4"
      references:
        - https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpResponse.set_cookie
        - https://semgrep.dev/blog/2020/bento-check-keeping-cookies-safe-in-flask/
        - https://bento.dev/checks/flask/secure-set-cookie/
      category: security
      technology:
        - django
      subcategory:
        - audit
      likelihood: LOW
      impact: LOW
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Cookie Security
    languages:
      - python
    severity: WARNING

Examples

secure-cookies.py

from django.shortcuts import render

from django.conf import settings
from django.contrib.messages.storage.base import BaseStorage, Message

class CookieStorage(BaseStorage):
    def _update_cookie(self, encoded_data, response):
        """
        Either sets the cookie with the encoded data if there is any data to
        store, or deletes the cookie.
        """
        if encoded_data:
            # ok: django-secure-set-cookie
            response.set_cookie(
                self.cookie_name, encoded_data,
                domain=settings.SESSION_COOKIE_DOMAIN,
                secure=settings.SESSION_COOKIE_SECURE or None,
                httponly=settings.SESSION_COOKIE_HTTPONLY or None,
            )
        else:
            response.delete_cookie(self.cookie_name, domain=settings.SESSION_COOKIE_DOMAIN)


def index(request, template):
    response = render(request, template)

    # ok: django-secure-set-cookie
    response.set_cookie("hello", "world", secure=True, httponly=True, samesite="Lax")

    # ok: django-secure-set-cookie
    response.set_cookie("hello", "world", **kwargs)

    # ok: django-secure-set-cookie
    response.set_cookie(
        settings.SESSION_COOKIE_NAME,
        request.session.session_key, max_age=max_age,
        expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
        path=settings.SESSION_COOKIE_PATH,
        secure=settings.SESSION_COOKIE_SECURE or None,
        httponly=settings.SESSION_COOKIE_HTTPONLY or None,
    )

    # ok: django-secure-set-cookie
    response.set_cookie(
        settings.CSRF_COOKIE_NAME,
        request.META['CSRF_COOKIE'],
        max_age=settings.CSRF_COOKIE_AGE,
        domain=settings.CSRF_COOKIE_DOMAIN,
        path=settings.CSRF_COOKIE_PATH,
        secure=settings.CSRF_COOKIE_SECURE,
        httponly=settings.CSRF_COOKIE_HTTPONLY,
    )

    # ruleid: django-secure-set-cookie
    response.set_cookie("hello", "again", httponly=False)

    return response