# PYTHON-DJANGO-SEC-050: Django XSS via Direct HttpResponse with User Input

> **Severity:** HIGH | **CWE:** CWE-79 | **OWASP:** A03:2021

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

## Description

This rule detects Cross-Site Scripting (XSS) vulnerabilities in Django applications
where untrusted user input from HTTP request parameters flows directly into HttpResponse()
without HTML escaping.

Django's template engine provides automatic HTML escaping by default, which is why
rendering user input through templates is safe. However, when developers bypass the
template system and construct HTML strings manually in views, passing them to
HttpResponse(), they take on the responsibility of escaping user input that the
template engine would otherwise handle automatically.

XSS vulnerabilities allow attackers to inject malicious JavaScript that executes in
the victim's browser context, enabling session theft, credential harvesting, keylogging,
DOM manipulation, and phishing within the legitimate application's origin.


## Vulnerable Code

```python
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.safestring import mark_safe, SafeString
from django.utils.html import html_safe

# SEC-050: HttpResponse with request data
def vulnerable_httpresponse(request):
    name = request.GET.get('name')
    return HttpResponse(f"Hello {name}")


def vulnerable_httpresponse_bad(request):
    msg = request.GET.get('error')
    return HttpResponseBadRequest(msg)
```

## Secure Code

```python
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.html import escape

def greet_user(request):
    name = request.GET.get('name', '')
    # SECURE OPTION 1: Use Django template system with automatic auto-escaping
    return render(request, 'greet.html', {'name': name})
    # In greet.html: <h1>Hello, {{ name }}</h1> -- auto-escaped by Django template

def search_results(request):
    query = request.GET.get('q', '')
    # SECURE OPTION 2: Explicitly escape before including in HttpResponse
    escaped_query = escape(query)
    html = f'<h1>Results for: {escaped_query}</h1>'
    return HttpResponse(html)

def api_view(request):
    name = request.GET.get('name', '')
    # SECURE OPTION 3: Return JSON for API responses -- no HTML rendering needed
    from django.http import JsonResponse
    return JsonResponse({'greeting': f'Hello, {name}'})

```

## 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-050",
    name="Django Direct HttpResponse Usage",
    severity="MEDIUM",
    category="django",
    cwe="CWE-79",
    tags="python,django,xss,httpresponse,OWASP-A03,CWE-79",
    message="Direct HttpResponse with user input detected. Use templates with auto-escaping.",
    owasp="A03:2021",
)
def detect_django_httpresponse_xss():
    """Detects user input flowing to HttpResponse without escaping."""
    return flows(
        from_sources=_DJANGO_SOURCES,
        to_sinks=[
            calls("HttpResponse"),
            calls("HttpResponseBadRequest"),
            calls("HttpResponseNotFound"),
            calls("HttpResponseForbidden"),
            calls("HttpResponseServerError"),
        ],
        sanitized_by=[
            calls("escape"),
            calls("django.utils.html.escape"),
            calls("conditional_escape"),
        ],
        propagates_through=PropagationPresets.standard(),
        scope="global",
    )
```

## How to Fix

- Use Django's template system (render() or TemplateResponse) for all HTML responses; templates auto-escape variables by default.
- When HttpResponse must be used with dynamic content, escape all user-controlled values with django.utils.html.escape() before inclusion.
- For API responses that do not render HTML, use JsonResponse which escapes HTML entities in JSON values.
- Never concatenate request parameters directly into HTML strings, even for "simple" values like names or IDs.
- Ensure response Content-Type is set appropriately (text/html for templates, application/json for APIs) to prevent MIME sniffing-based XSS.

## Security Implications

- **Session Cookie Theft and Account Takeover:** Injected JavaScript can access document.cookie and send session cookies to
attacker-controlled servers. If the application's session cookies do not have
the HttpOnly flag set, this leads to complete account takeover without
requiring the user's credentials.

- **Credential Harvesting via DOM Manipulation:** Injected scripts can dynamically modify the page DOM to add fake login forms
or overlay phishing content within the trusted application domain. Users filling
out these fake forms send credentials directly to the attacker, bypassing
browser security warnings about fake sites.

- **Stored XSS as a Secondary Vector:** If the user-controlled value eventually flows from the database to HttpResponse
(second-order XSS), the injection payload persists and executes every time any
user views the affected page, amplifying the attack across the entire user base.

- **CSRF Token Exfiltration:** XSS can extract CSRF tokens from the page and use them to submit state-changing
requests with full CSRF protection bypassed, enabling fund transfers, account
changes, and other sensitive operations on behalf of authenticated users.


## FAQ

**Q: Why does Django template auto-escaping not protect my HttpResponse code?**

Django's template engine escapes variables when rendering template files ({{ variable }}).
When you construct HTML strings in Python code and pass them to HttpResponse() directly,
the template engine never sees the data. Auto-escaping only applies within the template
rendering system. Raw HttpResponse() with concatenated strings receives no automatic
escaping.


**Q: Does escape() from django.utils.html escape all XSS vectors?**

django.utils.html.escape() converts the five HTML special characters: <, >, &,
single quote, and double quote to their HTML entity equivalents. This is sufficient
to prevent XSS in HTML content and attribute values when applied to user input.
It does not prevent XSS in JavaScript contexts (inside <script> tags) or CSS
contexts -- for those, use different escaping strategies or avoid including
user input in those contexts entirely.


**Q: Can I mark a string as safe with mark_safe() after escaping it?**

Yes, if you have properly escaped the string first. The pattern escape(user_input)
followed by mark_safe() of the result is safe because escape() has already
converted all dangerous characters. However, applying mark_safe() first and then
concatenating unescaped user input is unsafe -- mark_safe() on a partial string
does not protect subsequent concatenation.


**Q: Is this rule triggered by admin views or Django's built-in views?**

Django's built-in views and the admin interface use the template system for all
HTML rendering, so they do not trigger this rule. The rule specifically targets
cases where user input reaches HttpResponse() through code paths that bypass
the template engine, which typically means custom views that return inline HTML.


**Q: How do I fix this if my view returns HTML constructed from multiple user inputs?**

Either migrate to a template (create an HTML template file, pass all dynamic
values as context, and use render()) or escape each user-controlled value with
escape() before incorporating it into the HTML string. If the HTML structure
itself is complex, the template approach is safer and more maintainable.


**Q: Does using Content-Security-Policy headers eliminate the need to fix this?**

CSP is a defense-in-depth measure that can mitigate XSS impact but is not a
substitute for fixing the underlying vulnerability. CSP can be misconfigured,
bypassed through allowed script sources, or absent in older browsers. Fix the
source of the vulnerability by escaping output, and add CSP as an additional
protection layer.


## References

- [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [Django Template Auto-escaping](https://docs.djangoproject.com/en/stable/ref/templates/language/#automatic-html-escaping)
- [Django Security - Cross-site Scripting Protection](https://docs.djangoproject.com/en/stable/topics/security/#cross-site-scripting-xss-protection)
- [django.utils.html module](https://docs.djangoproject.com/en/stable/ref/utils/#module-django.utils.html)
- [OWASP XSS Attack](https://owasp.org/www-community/attacks/xss/)

---

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