Skip to content

Latest commit

 

History

History
285 lines (236 loc) · 19.8 KB

File metadata and controls

285 lines (236 loc) · 19.8 KB

Here is the combined list of vulnerabilities, formatted in markdown, with duplicates removed and sections merged for similar vulnerabilities:

Combined Vulnerability List for django-waffle Project

1. Information Exposure via waffle_status and wafflejs endpoints

  • Description:

    • The waffle application exposes endpoints /waffle_status (named URL waffle_status) and /wafflejs (named URL wafflejs) that return information about all defined flags, switches, and samples. /waffle_status returns a JSON response, while /wafflejs returns JavaScript code embedding the same data.
    • An external attacker can access these endpoints without any authentication.
    • By accessing these endpoints, the attacker can enumerate all feature flags, switches, and samples configured in the application, including their names, active status, and last modified timestamps.
    • This information can reveal details about upcoming features, internal application logic, and potentially sensitive configuration if flag, switch, or sample names are chosen descriptively or contain sensitive keywords.
    • Step-by-step trigger:
      1. Identify the publicly accessible URLs for the waffle_status and wafflejs endpoints (typically /waffle_status and /wafflejs).
      2. Send an HTTP GET request to either of these URLs using a web browser or a tool like curl.
      3. Observe the JSON response from /waffle_status or the JavaScript code from /wafflejs, which lists all flags, switches, and samples with their active status and last modified timestamps.
  • Impact:

    • Information Leakage: Exposure of feature flag, switch, and sample names, their status, and last modified timestamps.
    • Security Misconfiguration: Revealing internal application configuration details through feature toggle names, potentially aiding further attacks.
  • Vulnerability Rank: High

  • Currently Implemented Mitigations:

    • None. The /waffle_status and /wafflejs endpoints are publicly accessible without any authentication or authorization checks. They are only decorated with @never_cache to prevent caching.
  • Missing Mitigations:

    • Access Control: Implement access control to the /waffle_status and /wafflejs endpoints. Restrict access to authenticated administrators or internal services only. This can be achieved by:
      • Requiring authentication for access to these endpoints.
      • Implementing IP-based whitelisting to allow access only from trusted networks.
      • Using Django's permission system to restrict access based on user roles.
    • Consider removing or limiting the exposure of this data in production environments.
  • Preconditions:

    • The waffle.urls are included in the project's urlpatterns.
    • The application is deployed and publicly accessible.
  • Source Code Analysis:

    • File: /code/waffle/urls.py
    from django.urls import path
    
    from waffle.views import wafflejs, waffle_json
    
    urlpatterns = [
        path('wafflejs', wafflejs, name='wafflejs'), # Vulnerable endpoint
        path('waffle_status', waffle_json, name='waffle_status'), # Vulnerable endpoint
    ]
    • File: /code/waffle/views.py
    @never_cache
    def waffle_json(request): # Function handling the /waffle_status endpoint
        return JsonResponse(_generate_waffle_json(request))
    
    @never_cache
    def wafflejs(request): # Function handling the /wafflejs endpoint
        data = _generate_waffle_json(request)
        js = 'var waffle = %s;' % json.dumps(data)
        return HttpResponse(js, 'application/javascript')
    
    
    def _generate_waffle_json(request: HttpRequest) -> dict[str, dict[str, Any]]:
        flags = get_waffle_flag_model().get_all() # Retrieves all flags
        flag_values = {
            f.name: {
                'is_active': f.is_active(request),
                'last_modified': f.modified,
            }
            for f in flags
        }
    
        switches = get_waffle_switch_model().get_all() # Retrieves all switches
        switch_values = {
            s.name: {
                'is_active': s.is_active(),
                'last_modified': s.modified,
            }
            for s in switches
        }
    
        samples = get_waffle_sample_model().get_all() # Retrieves all samples
        sample_values = {
            s.name: {
                'is_active': s.is_active(),
                'last_modified': s.modified,
            }
            for s in samples
        }
    
        return {
            'flags': flag_values,
            'switches': switch_values,
            'samples': sample_values,
        }
    • The code shows that both waffle_json and wafflejs views, mapped to /waffle_status and /wafflejs URLs respectively, retrieve all flags, switches, and samples and return their names, active status, and last modified timestamps in a JSON response (or embedded in Javascript) without any access control.
  • Security Test Case:

    1. Deploy the django-waffle example application or an application using django-waffle with at least one Flag, Switch, and Sample defined.
    2. Access the /waffle_status endpoint using a web browser or curl from outside the application's network (as an external attacker). For example: curl http://<your-application-url>/waffle_status
    3. Verify that the response is a JSON object with HTTP response code 200 and Content-Type header indicates JSON.
    4. Examine the JSON response and confirm that it contains keys such as "flags", "switches", and "samples".
    5. For each item in the lists, verify that the name, is_active status and last_modified timestamp are exposed.
    6. Repeat steps 2-5 for /wafflejs endpoint and check that the returned JavaScript embeds the same information.
    7. Expected Result: The test should confirm that an unauthenticated external attacker can successfully retrieve a list of all flags, switches, and samples along with their status and last modified timestamps by accessing the /waffle_status and /wafflejs endpoints. Conclude that internal configuration details have been exposed without access control.

2. Insecure Cookie Settings for Testing Flags

  • Description:

    • When a Flag is created with testing=True, the waffle middleware sets a cookie named dwft_%s (where %s is the flag name) to enable testing override of the flag's behavior.
    • This cookie, used for testing purposes, is set without the HttpOnly and Secure flags by default.
    • If an attacker can perform a Cross-Site Scripting (XSS) attack on the application, they can potentially access this cookie via JavaScript.
    • If an attacker performs a Man-in-the-Middle (MITM) attack over an insecure HTTP connection, they could potentially intercept this cookie.
    • By obtaining or manipulating this cookie, an attacker could potentially enable or disable features for a victim user in a testing scenario, potentially leading to unexpected application behavior or bypassing intended feature restrictions.
    • Step-by-step trigger:
      1. Create a Flag in the Django admin panel with testing option enabled.
      2. Access a page in the application where this flag might be evaluated. Observe the dwft_<flag_name> cookie being set in the browser's developer tools (if the flag logic is executed).
      3. Using browser developer tools or a network intercepting proxy, inspect the attributes of the dwft_<flag_name> cookie.
      4. Verify that the HttpOnly and Secure flags are not set for this cookie.
  • Impact:

    • Session Hijacking/Manipulation: Potential for an attacker to manipulate the testing cookie if XSS or MITM is possible.
    • Feature Bypass: Attacker might be able to bypass intended feature restrictions or enable features not meant for them in a testing context.
  • Vulnerability Rank: High

  • Currently Implemented Mitigations:

    • The WaffleMiddleware sets cookies for both regular flags (dwf_%s) and testing flags (dwft_%s). However, only the regular flag cookies respect the SECURE setting. The testing cookies do not explicitly set HttpOnly or Secure flags.
  • Missing Mitigations:

    • Set HttpOnly flag: Set the HttpOnly flag to True for the dwft_%s cookies to prevent client-side JavaScript from accessing the cookie, mitigating XSS-based attacks targeting this cookie.
    • Set Secure flag: Set the Secure flag to True for the dwft_%s cookies to ensure they are only transmitted over HTTPS, mitigating MITM attacks if the application uses HTTPS.
  • Preconditions:

    • A Flag is created with testing=True.
    • The application evaluates this flag, causing the dwft_%s cookie to be set.
    • The application is vulnerable to XSS or is used over insecure HTTP connections, making MITM attacks possible.
  • Source Code Analysis:

    • File: /code/waffle/middleware.py
    class WaffleMiddleware(MiddlewareMixin):
        def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
            secure = get_setting('SECURE') # SECURE setting is retrieved
            max_age = get_setting('MAX_AGE')
    
            if hasattr(request, 'waffles'):
                for k in request.waffles:
                    name = smart_str(get_setting('COOKIE') % k)
                    active, rollout = request.waffles[k]
                    if rollout and not active:
                        age = None
                    else:
                        age = max_age
                    response.set_cookie(name, value=active, max_age=age,
                                        secure=secure, httponly=True) # secure and httponly flag used here for dwf_%s cookies
            if hasattr(request, 'waffle_tests'):
                for k in request.waffle_tests:
                    name = smart_str(get_setting('TEST_COOKIE') % k)
                    value = request.waffle_tests[k]
                    response.set_cookie(name, value=value) # secure and httponly flags are NOT used for dwft_%s cookies
    
            return response
    • The code shows that response.set_cookie for testing cookies (in waffle_tests block) does not include secure=secure or httponly=True arguments, making them insecure by default, while regular waffle cookies (dwf_%s) correctly use secure=secure and httponly=True.
  • Security Test Case:

    1. Deploy the django-waffle example application or an application using django-waffle.
    2. Create a Flag named test_flag_xss in the Django admin panel and set Testing to Yes.
    3. Access any page in the application.
    4. Using browser's developer tools, inspect the cookies set for the domain. Look for the dwft_test_flag_xss cookie.
    5. Check the attributes of the dwft_test_flag_xss cookie.
    6. Verify that the HttpOnly flag is False or not present.
    7. Verify that the Secure flag is False or not present.
    8. Expected Result: The test should confirm that the dwft_test_flag_xss cookie is set without HttpOnly and Secure flags, making it potentially vulnerable to XSS and MITM attacks.

3. Insecure Test Configuration Used in Production

  • Description:

    • The project includes a settings file (test_settings.py) that is used by the provided startup script (run.sh) and CI/CD workflows. This file sets critical values insecurely for a production environment—it enables debugging (DEBUG = True) and uses a weak, hardcoded secret key (SECRET_KEY = 'foobar').
    • An attacker could exploit the exposed debug information and easily guess or forge cryptographic tokens if this configuration is deployed in a public production environment.
    • Step-by-step trigger:
      1. Deploy the application using the provided run.sh script (which exports DJANGO_SETTINGS_MODULE="test_settings").
      2. As an unauthenticated user, trigger an error (for example, by accessing a non-existent route) so that Django’s debug error page is displayed.
      3. Examine the error page for sensitive internal details (such as stack traces and settings).
      4. Optionally, attempt to tamper with or forge session cookies knowing that they are signed using the weak secret key.
  • Impact:

    • Running with DEBUG = True in production can lead to detailed error messages being displayed to attackers; these messages may reveal sensitive information (such as file paths, configuration details, and even portions of source code).
    • A weak secret key undermines security measures including session signing and cryptographic tokens, making session hijacking or other forgery attacks feasible.
  • Vulnerability Rank: High

  • Currently Implemented Mitigations:

    • None. The repository’s default configuration in test_settings.py is intended only for testing but is used by default in the startup script and workflows.
  • Missing Mitigations:

    • Provide a production-ready settings file: Create a separate settings file (e.g., production_settings.py) that sets DEBUG = False and uses a strong, unpredictable SECRET_KEY.
    • Modify startup script: Update run.sh to use the production settings file by default or provide clear instructions and mechanisms to switch to production settings for deployment.
    • Prevent accidental use of test settings: Ensure that the production deployment environment cannot accidentally use the insecure test_settings.py, possibly by checking environment variables or using different deployment scripts for different environments.
  • Preconditions:

    • The deployed instance must be using test_settings.py (or otherwise misconfigured with debugging enabled and a weak secret key) and be publicly accessible.
  • Source Code Analysis:

    • File: /code/test_settings.py
    DEBUG = True
    SECRET_KEY = 'foobar'
    • The shell script run.sh unconditionally sets DJANGO_SETTINGS_MODULE="test_settings", meaning that even in a production environment, the insecure settings might be used by default.
  • Security Test Case:

    1. Deploy the application using the provided run.sh script without modifications.
    2. As an external user, trigger an error (for example, by browsing to a non-existent URL) and verify that a detailed Django debug error page is shown with a stack trace and internal configuration details.
    3. Check that the session cookies (or any cryptographically signed cookies) are being generated with a known value (i.e., that the secret key is the weak string “foobar”). This might require inspecting cookie values or application logs depending on how session management is implemented.
    4. Confirm that sensitive debugging information is visible and that cryptographic protections may be easily bypassed.
    5. Expected Result: The test should confirm that deploying with default settings exposes sensitive debugging information and uses a weak secret key, making the application highly vulnerable in a production environment. Recommend that production deployments never use these settings.

4. Unintended Feature Flag Control via URL Parameter when WAFFLE_OVERRIDE is enabled

  • Description:

    • The application uses django-waffle for feature flagging and has the WAFFLE_OVERRIDE setting enabled (set to True).
    • When WAFFLE_OVERRIDE is True, the is_active method of a Flag model checks for URL parameters matching the flag name.
    • An attacker can craft a URL with a parameter like flag_name=1 to activate the flag or flag_name=0 to deactivate it for their session.
    • By manipulating these URL parameters, an attacker can bypass the intended feature flag logic and potentially access features that should be disabled for them or disable features that should be enabled.
    • Step-by-step trigger:
      1. Ensure WAFFLE_OVERRIDE = True is set in the application's settings.
      2. Identify a feature flag in the application (e.g., test_flag).
      3. Access a URL in the application, and observe the default behavior related to the feature flag.
      4. Modify the URL by appending a query parameter with the flag name and value '1' to activate it (e.g., ?test_flag=1) or '0' to deactivate it (e.g., ?test_flag=0).
      5. Access the modified URL and observe the change in application behavior, reflecting the overridden flag state.
  • Impact:

    • Unauthorized access to features: Attackers can enable flags intended for specific user groups (e.g., staff, superusers) or future features not yet meant for public access.
    • Unauthorized disabling of features: Attackers can disable flags that are essential for normal application functionality for their session.
    • Security bypass: Depending on the features controlled by flags, this could lead to significant security bypasses, such as accessing admin functionalities, bypassing payment checks, or viewing sensitive data.
  • Vulnerability Rank: High

  • Currently Implemented Mitigations:

    • None. The project provides the WAFFLE_OVERRIDE setting but relies on developers to disable it in production environments.
  • Missing Mitigations:

    • Strongly discourage WAFFLE_OVERRIDE in production: Document clearly against enabling WAFFLE_OVERRIDE in production, emphasizing it's for development/testing only and must be disabled to prevent unauthorized feature access control.
    • Restrict WAFFLE_OVERRIDE functionality: Consider removing WAFFLE_OVERRIDE from production code paths or restrict its usage to authenticated superusers only, even when enabled.
    • Implement a warning for production use: The application could check WAFFLE_OVERRIDE at startup and log a warning or refuse to start if enabled in a non-development environment.
  • Preconditions:

    • The WAFFLE_OVERRIDE setting in settings.py is set to True.
    • The application is deployed in a publicly accessible environment.
    • At least one Flag is defined in the waffle system.
  • Source Code Analysis:

    1. File: waffle/models.py
    2. Class: AbstractBaseFlag
    3. Method: is_active(self, request: HttpRequest, read_only: bool = False) -> bool | None
    def is_active(self, request: HttpRequest, read_only: bool = False) -> bool | None:
        # ... other checks ...
    
        if get_setting('OVERRIDE'): # [POINT OF VULNERABILITY]
            if self.name in request.GET:
                return request.GET[self.name] == '1'
        # ... rest of the logic ...
    • The is_active method checks if the OVERRIDE setting is enabled using get_setting('OVERRIDE').
    • If OVERRIDE is True, it directly checks if the flag's name exists as a key in request.GET.
    • If the flag name is in request.GET, it returns True if the value is '1' and False otherwise.
    • This logic allows controlling the flag's active state directly through URL parameters if WAFFLE_OVERRIDE is enabled, bypassing all other intended flag activation logic.
  • Security Test Case:

    1. Pre-setup:
      • Ensure WAFFLE_OVERRIDE = True is set in test_settings.py or a similar settings file used for testing the vulnerability.
      • Start the Django development server or deploy the application to a test instance.
      • Create a Flag named test_flag in the Django admin panel, with default settings (e.g., Everyone: Unknown).
    2. Test Steps:
      • Access a URL that is protected by the test_flag (e.g., /flag-on view decorated with @waffle_flag('test_flag')). Observe the expected behavior when the flag is not active by default (e.g., 404 response).
      • Modify the URL to include the query parameter test_flag=1 (e.g., /flag-on?test_flag=1).
      • Access the modified URL in the browser.
    3. Expected Result:
      • The application should now exhibit the behavior associated with the flag being active (e.g., return a 200 OK response with specific content), demonstrating that the test_flag has been activated by the URL parameter, bypassing the default flag logic.
    4. Cleanup:
      • Revert WAFFLE_OVERRIDE setting to False after testing.