# PYTHON-DJANGO-SEC-070: Django Insecure Cookie Settings via set_cookie()

> **Severity:** MEDIUM | **CWE:** CWE-614, CWE-1004 | **OWASP:** A05:2021

- **Language:** Python
- **Category:** Django
- **URL:** https://codepathfinder.dev/registry/python/django/PYTHON-DJANGO-SEC-070
- **Detection:** `pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-070 --project .`

## Description

This rule detects insecure cookie configurations in Django applications by auditing
calls to response.set_cookie() that may be missing critical security flags: Secure,
HttpOnly, and SameSite.

Cookies without the Secure flag are transmitted over unencrypted HTTP connections,
exposing them to network eavesdropping. Cookies without the HttpOnly flag are
accessible via document.cookie in JavaScript, enabling session cookie theft through
XSS attacks. Cookies without the SameSite flag can be sent in cross-site requests,
facilitating Cross-Site Request Forgery (CSRF) attacks.

This rule uses pattern matching on calls to *.set_cookie() to audit all cookie
setting calls and flag those missing the recommended security attributes. For session
cookies specifically, Django's global settings SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY,
and SESSION_COOKIE_SAMESITE should also be set.


## Vulnerable Code

```python
import pickle
import yaml
from django.views.decorators.csrf import csrf_exempt

# SEC-070: insecure cookies
def set_insecure_cookie(request):
    response = HttpResponse("OK")
    response.set_cookie("session_id", "abc123")
    return response
```

## Secure Code

```python
from django.http import HttpResponse, JsonResponse

def set_preferences(request):
    response = HttpResponse("Preferences saved")
    # SECURE: All three security flags set for sensitive cookies
    response.set_cookie(
        'user_pref',
        'dark_mode',
        secure=True,      # Only sent over HTTPS
        httponly=True,    # Not accessible from JavaScript
        samesite='Lax',   # Sent on top-level navigation, blocked on cross-site sub-requests
        max_age=86400,    # 1 day expiry
    )
    return response

def set_session_tracking(request):
    response = JsonResponse({'status': 'ok'})
    # SECURE: Analytics cookie with Strict SameSite for enhanced CSRF protection
    response.set_cookie(
        'analytics_id',
        request.session.session_key,
        secure=True,
        httponly=False,  # Analytics cookies may need JS access -- document this
        samesite='Strict',
        max_age=30 * 24 * 3600,  # 30 days
    )
    return response

# ALSO configure Django settings for session cookies:
# SESSION_COOKIE_SECURE = True
# SESSION_COOKIE_HTTPONLY = True
# SESSION_COOKIE_SAMESITE = 'Lax'
# CSRF_COOKIE_SECURE = True
# CSRF_COOKIE_HTTPONLY = True

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets

_DJANGO_SOURCES = [
    calls("request.GET.get"),
    calls("request.POST.get"),
    calls("request.GET"),
    calls("request.POST"),
    calls("request.COOKIES.get"),
    calls("request.FILES.get"),
    calls("*.GET.get"),
    calls("*.POST.get"),
]


@python_rule(
    id="PYTHON-DJANGO-SEC-070",
    name="Django Insecure Cookie Settings",
    severity="MEDIUM",
    category="django",
    cwe="CWE-614",
    tags="python,django,cookies,security,OWASP-A05,CWE-614",
    message="set_cookie() without secure/httponly flags. Set secure=True, httponly=True, samesite='Lax'.",
    owasp="A05:2021",
)
def detect_django_insecure_cookies():
    """Audit: detects set_cookie() calls that may lack security attributes."""
    return calls("*.set_cookie")
```

## How to Fix

- Set secure=True on all cookies that should only be transmitted over HTTPS connections.
- Set httponly=True on all session and authentication cookies to prevent JavaScript access.
- Set samesite='Lax' as the minimum SameSite policy; use samesite='Strict' for cookies not needed in cross-site top-level navigation.
- Configure Django's global session and CSRF cookie settings: SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SAMESITE, CSRF_COOKIE_SECURE.
- Set appropriate max_age or expires values to limit the lifetime of cookies and reduce the window of exposure for stolen cookies.

## Security Implications

- **Session Hijacking via Network Interception:** Cookies without the Secure flag are transmitted in plaintext over HTTP. An
attacker on the same network (coffee shop WiFi, corporate network with a
compromised switch) can intercept session cookies using passive eavesdropping,
enabling session hijacking without any interaction with the application.

- **Session Cookie Theft via XSS:** Cookies without the HttpOnly flag are readable by JavaScript via document.cookie.
Any XSS vulnerability in the application can be used to exfiltrate session
cookies to attacker-controlled servers, enabling account takeover. HttpOnly
prevents this specific attack path even when XSS is present.

- **Cross-Site Request Forgery via Unprotected Cookies:** Cookies without SameSite=Lax or SameSite=Strict are sent by browsers in
cross-site requests. Combined with insufficient CSRF token protection, this
enables attackers to craft pages that trigger state-changing actions in the
application on behalf of authenticated users.

- **Cookie Theft via Mixed Content:** Even on HTTPS pages, cookies without the Secure flag can be exposed if any
resource on the page is loaded over HTTP (mixed content). Browsers may send
non-Secure cookies with HTTP requests to the same domain, creating an
exposure window even on otherwise HTTPS deployments.


## FAQ

**Q: Does setting secure=True on individual cookies replace the Django settings?**

Both are needed for complete coverage. Django's SESSION_COOKIE_SECURE and
CSRF_COOKIE_SECURE settings protect the built-in session and CSRF cookies.
Custom cookies set via response.set_cookie() require the secure parameter to
be passed explicitly, as they are not covered by the session cookie settings.
Set both: the global Django settings for built-in cookies and explicit flags
for custom cookies.


**Q: What is the difference between SameSite=Lax and SameSite=Strict?**

SameSite=Lax sends cookies on top-level GET navigation from other sites (e.g.,
clicking a link from an email). SameSite=Strict never sends the cookie on
cross-site requests, even top-level navigation. Lax is the recommended default
for session cookies because it allows users to follow external links and arrive
at the site already logged in. Strict provides better CSRF protection but may
confuse users who click links from external sites and appear logged out.


**Q: Can I omit httponly=True for cookies that need to be read by JavaScript?**

For cookies that genuinely need JavaScript access (analytics, user preferences
accessed by client-side code), httponly=False is acceptable -- but document this
explicitly in code comments and ensure those cookies do not contain session tokens
or authentication state. Authentication and session cookies must always have
httponly=True.


**Q: Does setting SameSite=Strict eliminate the need for Django's CSRF middleware?**

No. SameSite cookies are a defense-in-depth measure but not a complete CSRF
replacement. Some browsers do not fully support SameSite, older browsers ignore
it entirely, and some legitimate cross-site requests (OAuth, payment provider
callbacks) may need to carry cookies. Django's CSRF middleware should remain
enabled. SameSite cookies reduce the CSRF attack surface but do not eliminate it.


**Q: How do Django's global cookie security settings interact with set_cookie()?**

Django's global settings (SESSION_COOKIE_SECURE, etc.) apply only to cookies
set through Django's session framework and CSRF middleware. They have no effect
on cookies set directly via response.set_cookie(). Each set_cookie() call must
include the security flags explicitly.


**Q: What should I do about third-party Django packages that call set_cookie() without security flags?**

Check the package's documentation for cookie security configuration options.
Many packages support settings like COOKIE_SECURE or allow wrapping their
response processing. If the package does not support security flags, submit
a bug report. In the interim, you can wrap the package's views with middleware
that adds security flags to all Set-Cookie headers.


## References

- [CWE-614: Sensitive Cookie Without Secure Flag](https://cwe.mitre.org/data/definitions/614.html)
- [CWE-1004: Sensitive Cookie Without HttpOnly Flag](https://cwe.mitre.org/data/definitions/1004.html)
- [Django Cookie Security Settings](https://docs.djangoproject.com/en/stable/ref/settings/#session-cookie-secure)
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [Mozilla Cookie Security documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security)
- [Django Security - CSRF Protection](https://docs.djangoproject.com/en/stable/topics/security/#cross-site-request-forgery-csrf-protection)

---

Source: https://codepathfinder.dev/registry/python/django/PYTHON-DJANGO-SEC-070
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
