Skip to content

Latest commit

 

History

History
258 lines (224 loc) · 20.9 KB

File metadata and controls

258 lines (224 loc) · 20.9 KB

Combined Vulnerability List

1. Cross-Site Scripting (XSS) via Attribute Injection

  • Vulnerability Name: Cross-Site Scripting (XSS) via Attribute Injection

  • Description: The django-widget-tweaks library, through its template filters (attr, set_attr, append_attr) and tags (render_field), allows developers to dynamically set HTML attributes on form fields directly within Django templates. This functionality is implemented by modifying the as_widget method of form fields to inject the specified attributes. However, the library fails to properly sanitize or validate attribute names and values before injecting them into the HTML output.

    This lack of sanitization creates a Cross-Site Scripting (XSS) vulnerability. An attacker who can control or influence the attribute strings passed to django-widget-tweaks template tags or filters can inject arbitrary HTML attributes, including those that execute JavaScript code (e.g., event handlers like onclick, onmouseover) or attributes that can be abused for XSS (e.g., style, svg attributes). When a user views a page containing a form field rendered using django-widget-tweaks with maliciously crafted attributes, the injected JavaScript code can be executed in their browser.

    Steps to trigger the vulnerability:

    1. An attacker crafts a malicious string containing JavaScript code intended to be used as an HTML attribute value. Examples include: " onclick='alert(\"XSS\")'" or " onmouseover='alert(\"XSS\")' ", or even attribute names like "onmouseover".
    2. The attacker identifies a Django template that uses django-widget-tweaks template tags or filters (attr, set_attr, append_attr, render_field) to render form fields.
    3. The attacker finds a way to inject this malicious string into a Django template context variable that is used as an argument to django-widget-tweaks template tags or filters. This can occur if user input is directly incorporated into template context without proper sanitization, or if the template is dynamically generated based on user-controlled data.
    4. The Django template, using django-widget-tweaks, renders a form field and applies the attribute modification using the malicious string as the attribute name or value. For example, using {% render_field form.field attr='attribute_name:"' + malicious_string + '"' %}, {{ form.field|attr:'attribute_name:"' + malicious_string + '"' }}, or directly injecting an event handler like {% render_field form.field onmouseover='malicious_string' %}.
    5. The rendered HTML form field will now contain the injected JavaScript code within the specified HTML attribute.
    6. When a user views the page and interacts with the form field in a way that triggers the injected attribute (e.g., clicking if onclick was injected, hovering if onmouseover), the JavaScript code executes in the user's browser.
  • Impact: Successful exploitation of this XSS vulnerability allows an attacker to execute arbitrary JavaScript code in the context of a victim's browser session when they interact with the form field. This can lead to severe security consequences, including:

    • Account hijacking: Stealing session cookies, localStorage data, or other authentication tokens to impersonate the user and gain unauthorized access to their account.
    • Data theft: Accessing sensitive information displayed on the page or making requests on behalf of the user to exfiltrate data, including personal details, financial information, and confidential documents.
    • Defacement: Modifying the content of the web page visible to the user, potentially damaging the application's reputation or spreading misinformation.
    • Redirection: Redirecting the user to a malicious website, which could host phishing attacks, malware, or further exploit the user's system.
    • Phishing: Displaying fake login forms or other deceptive content to steal user credentials or sensitive information.
    • Further attacks: Using the XSS vulnerability as a stepping stone for more complex attacks against the user or the application, potentially compromising the user's machine or the application's backend systems.
  • Vulnerability Rank: High

  • Currently implemented mitigations: The django-widget-tweaks library does not implement any explicit HTML escaping, sanitization, or validation of attribute names or values. It relies on Django's default template rendering and auto-escaping mechanisms. However, Django's auto-escaping is context-agnostic and not designed to prevent XSS when dynamically setting HTML attributes using libraries like django-widget-tweaks. The library trusts that the attribute values provided by the developer or through template context are already safe, which is not the case when these values are derived from untrusted sources.

  • Missing mitigations: To effectively mitigate this XSS vulnerability, the following mitigations are necessary within the django-widget-tweaks library:

    • Context-aware output encoding: Implement context-aware HTML escaping for attribute values before they are injected into the HTML output. Specifically, values intended for HTML attributes should be passed through Django's HTML escaping function (e.g., django.utils.html.escape). For event handler attributes, more robust JavaScript escaping might be required, or ideally, avoid dynamic event handler injection altogether.
    • Input validation and sanitization: While primarily the responsibility of the application developer, the library could provide options or guidelines for validating and sanitizing attribute names and values. At a minimum, the documentation should strongly emphasize the risks of using user-controlled data directly as attribute values and recommend secure coding practices.
    • Attribute name validation (Whitelisting): Consider implementing a whitelist or strict regular expression to restrict attribute names to a set of known safe HTML attributes. This would prevent the injection of potentially dangerous attributes like event handlers or attributes known to be exploitable in certain contexts.
  • Preconditions:

    1. A Django application is using a vulnerable version of the django-widget-tweaks library.
    2. The application uses django-widget-tweaks template tags or filters (attr, set_attr, append_attr, render_field) to dynamically set HTML attributes of form fields.
    3. There is a scenario where an attacker can control or influence the values of template context variables that are used as attribute values via django-widget-tweaks, or directly control the arguments to attr filter or render_field tag in templates. This could be due to:
      • Unsanitized user input being directly passed into the template context and used in attribute values.
      • Dynamic template generation where attribute values are constructed based on user-provided data.
      • Developer mistake in directly embedding user-provided data into attribute strings within templates.
    4. The affected form field is rendered and accessible to untrusted users.
  • Source code analysis:

    1. File: /code/widget_tweaks/templatetags/widget_tweaks.py
    2. Function: _process_field_attributes(field, attr, process): This function is the core of attribute processing. It parses the attribute string and prepares the field for attribute modification.
    def _process_field_attributes(field, attr, process):
        params = re.split(r"(?<!:):(?!:)", attr, 1)
        attribute = params[0].replace("::", ":")
        value = params[1] if len(params) == 2 else True
        field = copy(field)
        old_as_widget = field.as_widget
    
        def as_widget(self, widget=None, attrs=None, only_initial=False):
            attrs = attrs or {}
            process(widget or self.field.widget, attrs, attribute, value) # Vulnerable line - value is not escaped
            if attribute == "type":
                self.field.widget.input_type = value
                del attrs["type"]
            html = old_as_widget(widget, attrs, only_initial)
            self.as_widget = old_as_widget
            return html
    
        field.as_widget = types.MethodType(as_widget, field)
        return field
    • The _process_field_attributes function splits the attribute string using a regular expression to separate the attribute name and value.
    • It then defines a wrapper function as_widget that is temporarily assigned to the form field's as_widget method.
    • Inside the as_widget wrapper, the process function (provided by filters like set_attr and append_attr) is called. Crucially, the value variable, which originates from template input and can be attacker-controlled, is passed directly to the process function without any HTML escaping or sanitization.
    • The process function directly sets or appends the value to the attrs dictionary: attrs[attribute] = value or attrs[attribute] += " " + value. This dictionary is then used by Django's form rendering to generate HTML attributes.
    1. Functions: set_attr(field, attr) and append_attr(field, attr): These filters use _process_field_attributes and their respective process functions to modify attributes.
    def set_attr(field, attr):
        def process(widget, attrs, attribute, value):
            attrs[attribute] = value # Vulnerable line - value is not escaped
        return _process_field_attributes(field, attr, process)
    
    def append_attr(field, attr):
        def process(widget, attrs, attribute, value):
            if attrs.get(attribute):
                attrs[attribute] += " " + value # Vulnerable line - value is not escaped
            elif widget.attrs.get(attribute):
                attrs[attribute] = widget.attrs[attribute] + " " + value # Vulnerable line - value is not escaped
            else:
                attrs[attribute] = value # Vulnerable line - value is not escaped
        return _process_field_attributes(field, attr, process)
    • In both set_attr and append_attr, the process functions directly assign the value to the attrs dictionary without any escaping.
    1. Tag: render_field(parser, token): This tag uses set_attr and append_attr filters to apply attributes defined in the template tag arguments.
    class FieldAttributeNode(Node):
        # ...
        def render(self, context):
            # ...
            for k, v in self.set_attrs:
                if k == "type":
                    bounded_field.field.widget.input_type = v.resolve(context)
                else:
                    bounded_field = set_attr(bounded_field, f"{k}:{v.resolve(context)}") # set_attr is called with resolved value
            for k, v in self.append_attrs:
                bounded_field = append_attr(bounded_field, f"{k}:{v.resolve(context)}") # append_attr is called with resolved value
            return str(bounded_field)
    • The render_field tag resolves attribute values from the template context using v.resolve(context) and passes these resolved values directly to set_attr and append_attr, making it vulnerable if these context variables are attacker-controlled.
  • Security test case:

    1. Set up a Django project with django-widget-tweaks installed and a configured Django app.
    2. Define a simple form in forms.py (e.g., tests/forms.py):
      from django import forms
      
      class TestForm(forms.Form):
          name = forms.CharField()
    3. Create a Django view in views.py (e.g., tests/views.py):
      from django.shortcuts import render
      from .forms import TestForm
      
      def test_xss_view(request):
          form = TestForm()
          malicious_attribute_value = ' onclick="alert(\'XSS\')"'
          context = {'form': form, 'xss_attr_value': malicious_attribute_value}
          return render(request, 'test_xss.html', context)
    4. Create a Django template test_xss.html in your templates directory (e.g., tests/templates/test_xss.html):
      {% load widget_tweaks %}
      <!DOCTYPE html>
      <html>
      <head>
          <title>XSS Test</title>
      </head>
      <body>
          <form method="post">
              {% csrf_token %}
              {% render_field form.name attr='id:"name-field"' attr='class:"form-control"' attr='type:"text"' attr='name:"user_name"' attr='value:"test value"' attr='placeholder:"Enter your name"' attr='onclick:"alert(\'XSS-render_field-attr\')"' %}
              {{ form.name|attr:'onclick:"alert(\'XSS-attr-filter\')"' }}
              <button type="submit">Submit</button>
          </form>
      </body>
      </html>
    5. Configure URLs in urls.py to access the view (e.g., tests/urls.py):
      from django.urls import path
      from . import views
      
      urlpatterns = [
          path('xss_test/', views.test_xss_view, name='xss_test'),
      ]
    6. Run the Django development server.
    7. Access the URL /xss_test/ in a web browser.
    8. Click on the input field.
    9. Observe that alert boxes with "XSS-render_field-attr" and "XSS-attr-filter" are displayed. This confirms that the JavaScript code injected via the onclick attribute using both render_field tag and attr filter was executed, demonstrating the XSS vulnerability.

2. Race Condition in Monkey‑Patching of Form Field Rendering Methods

  • Vulnerability Name: Race Condition in Monkey‑Patching of Form Field Rendering Methods

  • Description: The django-widget-tweaks library modifies form field attributes by temporarily monkey-patching the as_widget method of form fields. In the _process_field_attributes function, the original as_widget method is saved, a new wrapper as_widget is defined to inject attributes, and then this new method is assigned to field.as_widget. After rendering, the original method is restored.

    This in-place modification of the as_widget method on the form field instance is not thread-safe. In a multi-threaded Django application, if a form field instance is shared across multiple requests (e.g., due to caching or reuse of form instances), concurrent requests can interfere with each other. One thread's temporary modification of as_widget can be overwritten or interact unexpectedly with another thread's modifications, leading to race conditions.

  • Impact: The race condition in monkey-patching can lead to several issues:

    • Inconsistent or corrupted rendering: Attributes intended for one request might be applied to another request's form field rendering. This can result in form fields being rendered with incorrect or unexpected attributes.
    • Exposure of sensitive data: Attributes meant to be specific to a user or request (e.g., user-specific IDs or dynamic values) could leak into the rendering of form fields for other users or requests.
    • Unpredictable application behavior: In extreme cases, if an attacker can intentionally trigger concurrent requests against a shared form field object, they might be able to manipulate the presentation or behavior of form fields in unpredictable ways, potentially leading to further vulnerabilities or application malfunctions.
  • Vulnerability Rank: High

  • Currently implemented mitigations: The library attempts to "reset" the monkey-patched as_widget method back to its original value after each render call. However, this reset is performed without any thread-safety mechanisms (like locks or thread-local storage) and assumes that form instances and their fields are used strictly within a single request context. This assumption is flawed in multi-threaded environments if form instances are shared.

  • Missing mitigations: To address the race condition, the library needs to implement thread-safe mechanisms for modifying widget rendering behavior. Missing mitigations include:

    • Thread-safe attribute modification: Instead of in-place monkey-patching, the library should operate on a copy of the form field or widget for each request. This could involve creating a deep copy of the field or using thread-local storage to manage request-specific attribute modifications.
    • Clear documentation and defensive coding: The library's documentation should explicitly warn developers against sharing form instances or widgets across threads and highlight the thread-safety issues. Defensive coding practices could also be implemented to detect or prevent unsafe usage patterns, or at least fail gracefully in concurrent scenarios.
  • Preconditions:

    1. The Django application is deployed in a multi-threaded environment (e.g., using a threaded WSGI server like gunicorn with multiple workers).
    2. A form field instance (or a form instance containing fields) is shared concurrently across multiple requests. This can occur if developers:
      • Cache form instances or widgets in global variables or application-level caches for performance optimization.
      • Reuse the same form instance across different user sessions or requests within a view.
      • Inadvertently share form instances in threaded contexts.
  • Source code analysis:

    1. File: /code/widget_tweaks/templatetags/widget_tweaks.py
    2. Function: _process_field_attributes(field, attr, process):
    def _process_field_attributes(field, attr, process):
        # ...
        old_as_widget = field.as_widget  # Store original method
        def as_widget(self, widget=None, attrs=None, only_initial=False):
            # ...
            html = old_as_widget(widget, attrs, only_initial) # Call original method
            self.as_widget = old_as_widget # Restore original method - NOT THREAD-SAFE
            return html
        field.as_widget = types.MethodType(as_widget, field) # Monkey-patching
        return field
    • The code stores the original as_widget method in old_as_widget.
    • A new as_widget method is defined as a closure that wraps the original method and injects attribute modification logic.
    • field.as_widget is then replaced with this new method, effectively monkey-patching the form field.
    • After calling the original as_widget to render the HTML, the code attempts to restore the original method by reassigning self.as_widget = old_as_widget.
    • The crucial point is that this entire process occurs on the shared field instance without any locking or synchronization. In a multi-threaded environment, concurrent requests can race to modify and restore the as_widget method, leading to inconsistent states.
  • Security test case:

    1. Set up a Django application in a multi-threaded WSGI server environment (e.g., using gunicorn with multiple workers).
    2. Create a Django view that intentionally shares a form instance across requests. This can be done by caching a form instance globally or at the view level. For example:
      # tests/views.py
      from django.shortcuts import render
      from .forms import TestForm
      
      cached_form = TestForm() # Globally cached form instance
      
      def test_race_condition_view(request):
          # Use the same cached form instance for all requests
          form = cached_form
          attr_value = f"data-request-id-{id(request)}" # Unique attribute value per request
          return render(request, 'test_race_condition.html', {'form': form, 'attr_value': attr_value})
    3. Create a Django template test_race_condition.html (e.g., tests/templates/test_race_condition.html) that uses widget-tweaks to modify attributes, using a request-specific attribute value:
      {% load widget_tweaks %}
      <!DOCTYPE html>
      <html>
      <head>
          <title>Race Condition Test</title>
      </head>
      <body>
          <form method="post">
              {% csrf_token %}
              {% render_field form.name attr='data-request-id:"{{ attr_value }}"' %}
              <button type="submit">Submit</button>
          </form>
          <p>Request ID: {{ attr_value }}</p>
      </body>
      </html>
    4. Configure URLs to access the view.
    5. Use a load testing tool (e.g., ab, locust, or a simple Python script with threading) to send concurrent requests to the test_race_condition_view.
    6. Analyze the rendered HTML output from multiple concurrent requests. Observe if the data-request-id attribute in the rendered HTML sometimes contains values from different requests than expected based on the request ID displayed on the page.
    7. If inconsistencies are observed (e.g., a request shows one request ID in the paragraph but a different request ID in the form field's data-request-id attribute), it confirms the race condition. This indicates that attribute modifications from one request have leaked into the rendering of another concurrent request due to the thread-unsafe monkey-patching.