Skip to content

Latest commit

 

History

History
333 lines (276 loc) · 22 KB

File metadata and controls

333 lines (276 loc) · 22 KB

Here is the combined list of vulnerabilities, formatted as markdown with main paragraphs and subparagraphs for each vulnerability, after removing duplicates:

Combined Vulnerability List

  • Vulnerability Name: Potential DjangoQL Injection in completion_demo view

    • Description: The completion_demo view in /code/test_project/core/views.py is vulnerable to DjangoQL injection. The view uses user-provided input from the q GET parameter to construct and execute DjangoQL queries against the User model. The UserQLSchema used in this view, while intending to limit searchable fields, by default includes all fields of the User and Group models except 'password'. This allows an attacker to craft malicious DjangoQL queries to access sensitive user attributes and related data that are not intended for public exposure.

    • Impact: Information Disclosure. A remote attacker can potentially extract sensitive information about users, such as is_staff, is_superuser, last_login, date_joined, email addresses, and group memberships, by crafting specific DjangoQL queries. This information can be used for further attacks or unauthorized access.

    • Vulnerability Rank: High

    • Currently implemented mitigations:

      • DjangoQL Schema validation: The project uses DjangoQLSchema to define searchable models and fields. However, the default schema in completion_demo is not restrictive enough.
      • Error handling: The code includes error handling for DjangoQLError exceptions, which prevents the application from crashing due to invalid queries, but it does not prevent information disclosure.
    • Missing mitigations:

      • Restrictive DjangoQL Schema: The UserQLSchema in /code/test_project/core/views.py should be modified to explicitly include only non-sensitive fields of the User and Group models that are intended for public demonstration. For example, limit fields to username, first_name, last_name, and groups.name. Sensitive fields like is_staff, is_superuser, last_login, date_joined, and email should be explicitly excluded or not included in the schema.
      • Input sanitization: While DjangoQL is designed to handle query parsing safely, consider adding an additional layer of input sanitization to filter or validate the q parameter before passing it to apply_search. This can act as a defense-in-depth measure.
      • Access control: If the completion_demo view is intended only for internal demonstration purposes, consider removing it from public access or implementing authentication and authorization to restrict access to trusted users.
    • Preconditions:

      • The application must be deployed and publicly accessible, with the completion_demo view exposed through a URL.
      • No Web Application Firewall (WAF) or other security controls are in place to filter or block malicious DjangoQL queries.
    • Source code analysis:

      1. Vulnerable Code Location: /code/test_project/core/views.py:22-37
      2. Vulnerability Step-by-step:
        • The completion_demo view is accessed via a GET request.
        • The view retrieves the value of the q parameter from the request: q = request.GET.get('q', '').
        • A Django queryset for User model is initialized: query = User.objects.all().order_by('username').
        • If the q parameter is not empty, the apply_search function is called to filter the queryset based on the DjangoQL query provided in q: query = apply_search(query, q, schema=UserQLSchema).
        • The UserQLSchema is defined in the same file (/code/test_project/core/views.py:14-19) and includes User and Group models without explicitly excluding any fields.
        • The apply_search function in /code/djangoql/queryset.py parses the DjangoQL query using DjangoQLParser and builds a Django ORM query based on the schema.
        • Because UserQLSchema does not restrict fields, an attacker can query against any field of the User and Group models, including sensitive ones.
        • The filtered queryset is then passed to the template for rendering, potentially displaying sensitive information based on the attacker's query.
      # /code/test_project/core/views.py
      @require_GET
      def completion_demo(request):
          q = request.GET.get('q', '') # [Step 1] User input is taken from 'q' parameter
          error = ''
          query = User.objects.all().order_by('username') # [Step 2] Initialize User queryset
          if q:
              try:
                  query = apply_search(query, q, schema=UserQLSchema) # [Step 3] Apply DjangoQL search with user input and UserQLSchema
              except DjangoQLError as e:
                  query = query.none()
                  error = str(e)
          # ... rest of the view ...
      # /code/test_project/core/views.py
      class UserQLSchema(DjangoQLSchema): # [Step 4] UserQLSchema definition (not restrictive enough)
          include = (User, Group)
          suggest_options = {
              Group: ['name'],
          }
    • Security test case:

      1. Deploy the test_project application to a publicly accessible server.
      2. Access the completion_demo view by navigating to the root URL /.
      3. Construct a malicious URL to test for information disclosure by querying for users with is_staff = True. For example, append the following query string to the URL: ?q=is_staff=True. The full URL would look like: http://<your-deployed-app-url>/?q=is_staff=True.
      4. Examine the search results displayed on the page. If the results only show users who are staff members (i.e., is_staff is True), this indicates that the query was successfully executed and sensitive information based on is_staff is accessible.
      5. Further test by querying other sensitive fields like is_superuser=True, last_login > "2024-01-01", or even accessing related fields like groups.name = "Administrators" to confirm the extent of information disclosure.
      6. Observe the response. If you can filter users based on these sensitive attributes, the application is vulnerable to DjangoQL injection and information disclosure.
  • Vulnerability Name: Hardcoded Django SECRET_KEY Exposure

    • Description: The settings file (/code/test_project/test_project/settings.py) defines the Django SECRET_KEY as a literal static string:

      SECRET_KEY = 'o56l!!8q!rwqy13optbmnycu97^tvh@bsk1t!-^$&7o@t4nsbg'
      

      An external attacker who reviews the publicly available source code (or who gains access to a production instance deployed with these defaults) can extract the secret key. With this knowledge, the attacker can forge or tamper with session cookies, CSRF tokens, and other signed data. This could then be exploited—for example, by creating forged session cookies to impersonate legitimate users (including administrative users) or tampering with security-critical data.

    • Impact:

      • Session hijacking and impersonation of users (including admin accounts).
      • Tampering with Django’s signed cookies may bypass authentication/authorization controls.
      • Undermines cryptographic integrity of all features that rely on the secret key.
    • Vulnerability Rank: Critical

    • Currently implemented mitigations:

      • None. The key is hardcoded directly in the settings file.
    • Missing mitigations:

      • Externalize the secret key by loading it from an environment variable or a secure configuration management system.
      • Ensure that production deployments override the default key with a securely generated, random value.
    • Preconditions:

      • The application is deployed without overriding the hardcoded default and an attacker can view the repository or deduce the key from a misconfigured production environment.
    • Source code analysis:

      • In /code/test_project/test_project/settings.py, the secret key is assigned directly:
        SECRET_KEY = 'o56l!!8q!rwqy13optbmnycu97^tvh@bsk1t!-^$&7o@t4nsbg'
      • There is no logic to override or replace this value at runtime.
    • Security test case:

      1. Retrieve the source code or configuration (for example, via an unintended repository exposure).
      2. Confirm that the SECRET_KEY is the hardcoded string.
      3. Using Django’s signing utility (or a crafted session cookie based on Django’s algorithm), attempt to forge a session cookie or CSRF token.
      4. Submit the forged token/cookie to a secure endpoint and verify whether the application accepts it (indicating a successful exploitation of the key).
  • Vulnerability Name: Public Exposure of User Data via Completion Demo Endpoint

    • Description: The view at /code/test_project/core/views.py—named completion_demo—is registered as the root URL (^$) in the URL patterns. This view unconditionally retrieves a full list of users via:

      query = User.objects.all().order_by('username')

      and passes it directly to the template as part of the context. Because the view only requires a GET request (enforced by @require_GET) and does not restrict access via authentication or authorization, any external attacker can simply access the homepage to receive a complete list of user records.

    • Impact:

      • Exposure of potentially sensitive user information (usernames and group associations).
      • Enables account enumeration, facilitating targeted phishing attacks, brute-force login attempts, or other methods to compromise user accounts.
    • Vulnerability Rank: High

    • Currently implemented mitigations:

      • None. There is no authentication or authorization check before returning user data.
    • Missing mitigations:

      • Restrict access to endpoints that return user information (for example, by requiring authentication or moving demo functionality behind a protected area).
      • Limit the fields returned to only non-sensitive information or employ pagination and filtering with strict access controls.
    • Preconditions:

      • The application is deployed with the public demo/search endpoint accessible at the root URL without login.
    • Source code analysis:

      • In /code/test_project/core/views.py, the completion_demo view starts by doing:
        query = User.objects.all().order_by('username')
      • No checks (such as login_required) or sanitization measures are implemented before passing the complete queryset to the renderer.
      • The URL configuration in /code/test_project/test_project/urls.py maps the root URL (r'^$') to completion_demo, making it publicly accessible.
    • Security test case:

      1. As an unauthenticated external attacker, navigate to the application’s root URL (e.g., http://example.com/).
      2. Observe that the rendered page contains a list of users (including usernames and group affiliations).
      3. Verify that no authentication is required to access this data.
  • Vulnerability Name: Excessive Error Information Disclosure via Malformed Search Queries

    • Description: The same completion_demo view accepts an optional GET parameter q intended for search queries. When an attacker supplies an invalid or malformed query string, the view calls:

      try:
          query = apply_search(query, q, schema=UserQLSchema)
      except DjangoQLError as e:
          query = query.none()
          error = str(e)

      The resulting error message (obtained via str(e)) is then passed to the template and rendered to the client. Since the underlying query language (via DjangoQL) raises errors that include internal schema details (such as valid field names and operator expectations), an attacker can trigger these errors to obtain detailed information about the internal data model.

    • Impact:

      • Attackers gain insight into backend model structure, field names, and valid query parameters.
      • Facilitates further exploitation by revealing internal logic that can inform injection, enumeration, or more targeted attacks.
    • Vulnerability Rank: High

    • Currently implemented mitigations:

      • The view catches the exception and does not crash; however, it simply passes the raw error string along to the user.
    • Missing mitigations:

      • Sanitize error messages shown to the client so that they do not reveal internal schema details.
      • Log the detailed error internally while returning a generic error message to the user.
    • Preconditions:

      • The application is publicly accessible at the completion demo endpoint.
      • No input validation or generic error handling is applied to hide sensitive exception details.
    • Source code analysis:

      • In /code/test_project/core/views.py, when q is provided, the block:
        try:
            query = apply_search(query, q, schema=UserQLSchema)
        except DjangoQLError as e:
            query = query.none()
            error = str(e)
        assigns the exact error string (which in cases of an unknown field, for example, will include a list of valid field names) to the variable error.
      • In the schema resolution (in /code/djangoql/schema.py within the resolve_name method), if an unknown field is referenced, the error message lists the possible valid field names.
    • Security test case:

      1. As an external attacker, access the completion demo endpoint by sending a GET request with a clearly invalid query, for example:
        http://example.com/?q=nonexistentField=foo
        
      2. Note that the response displays an error message including details like:
        Unknown field: nonexistentField. Possible choices are: author, content_type, genre, id, is_published, name, object_id, price, rating, similar_books, written, …
        
      3. Confirm that the error message contains internal schema details that should not have been exposed.
      4. Verify that modifying the search query (e.g. omitting or changing field names) continues to reveal similar internal information.
  • Vulnerability Name: SQL Injection via crafted string comparison in DjangoQL query

    • Description: An attacker can inject raw SQL into the database query through a crafted DjangoQL query string, specifically when using string comparison operators like startswith, endswith, ~ (contains), and their negated forms (not startswith, not endswith, !~). This is due to insufficient sanitization of string literals within the DjangoQL parser before they are used in raw SQL LIKE clauses.

      Step-by-step trigger:

      1. Identify a Django application using DjangoQL and exposing a DjangoQL search interface to external users (e.g., via Django Admin or a custom view like completion_demo).
      2. Construct a malicious DjangoQL query that includes a string comparison operator (startswith, endswith, ~, !~, not startswith, not endswith, !~).
      3. Inject raw SQL within the string literal part of the query. For example, using startswith "') OR 1=1 --" within a user-controlled input field that is processed by DjangoQL.
      4. Submit the crafted query to the application.
      5. The DjangoQL parser will generate an AST.
      6. The build_filter function will construct a Django ORM query using this AST, embedding the malicious SQL string directly into a raw SQL LIKE clause.
      7. The database will execute the query with the injected SQL, potentially leading to data extraction, modification, or other malicious actions.
    • Impact: Critical. Successful SQL injection can lead to:

      • Data Breach: Unauthorized access to sensitive data stored in the database.
      • Data Manipulation: Modification or deletion of data, leading to data integrity issues.
      • Account Takeover: In some cases, it might be possible to escalate privileges or gain access to other user accounts.
      • System Compromise: In severe cases, depending on database permissions and application setup, it could potentially lead to broader system compromise.
    • Vulnerability Rank: Critical

    • Currently implemented mitigations: None. The provided code does not implement any sanitization or escaping of string literals specifically to prevent SQL injection in LIKE clauses.

    • Missing mitigations:

      • Input Sanitization/Escaping: String literals used in LIKE clauses within DjangoQL queries should be properly sanitized or escaped before being incorporated into raw SQL queries. This could involve escaping special characters that have meaning in SQL LIKE patterns (e.g., %, _, \).
      • Parameterized Queries (for LIKE): Ideally, even for LIKE clauses, parameterized queries should be used to separate SQL code from user-provided data. However, directly parameterizing the pattern in LIKE might not be straightforward across all database backends. Escaping is a more practical immediate mitigation for LIKE.
      • Content Security Policy (CSP): While not a direct mitigation for SQL injection, a strong CSP can help limit the impact of successful attacks by restricting the actions an attacker can take even if they manage to inject malicious code.
    • Preconditions:

      • A Django application is using the DjangoQL library.
      • The application exposes a DjangoQL search interface to external users, either through Django Admin or custom views.
      • The application uses string comparison operators (startswith, endswith, ~, !~, not startswith, not endswith, !~) in its DjangoQL queries.
    • Source code analysis:

      1. djangoql/lexer.py: The lexer correctly identifies string literals, including escaped characters, but does not perform any sanitization for SQL LIKE context.
      @TOKEN(r'\"(' + re_escaped_char +
             '|' + re_escaped_unicode +
             '|' + re_string_char + r')*\"')
      def t_STRING_VALUE(self, t):
          t.value = t.value[1:-1]  # cut leading and trailing quotes ""
          return t

      The string literal value is extracted but no SQL escaping is applied.

      1. djangoql/parser.py: The parser constructs the AST, including string literals, without modification.
      def p_string(self, p):
          """
          string : STRING_VALUE
          """
          p[0] = Const(value=unescape(p[1]))

      The unescape function handles Python string escapes but not SQL LIKE escapes.

      1. djangoql/schema.py: The schema validation focuses on data types and field existence but does not validate the content of string literals for SQL injection risks.

      2. djangoql/queryset.py: The build_filter function constructs Django ORM Q objects. For string comparisons (~, !~, startswith, endswith, and their negated forms), it uses icontains, istartswith, iendswith lookups, which translate to LIKE in SQL. The string literal is directly passed into the ORM lookup without escaping for LIKE context.

      def get_operator(self, operator):
          """
          Get a comparison suffix to be used in Django ORM & inversion flag for it
          ...
          """
          op = {
              '=': '',
              '>': '__gt',
              '>=': '__gte',
              '<': '__lt',
              '<=': '__lte',
              '~': '__icontains', # <--- icontains, istartswith, iendswith use LIKE
              'in': '__in',
              'startswith': '__istartswith', # <--- icontains, istartswith, iendswith use LIKE
              'endswith': '__iendswith', # <--- icontains, istartswith, iendswith use LIKE
          }.get(operator)
          if op is not None:
              return op, False
          op = {
              '!=': '',
              '!~': '__icontains', # <--- icontains, istartswith, iendswith use LIKE
              'not in': '__in',
              'not startswith': '__istartswith', # <--- icontains, istartswith, iendswith use LIKE
              'not endswith': '__iendswith', # <--- icontains, istartswith, iendswith use LIKE
          }[operator]
          return op, True
      
      def get_lookup(self, path, operator, value):
          """
          Performs a lookup for this field with given path, operator and value.
          ...
          """
          search = '__'.join(path + [self.get_lookup_name()])
          op, invert = self.get_operator(operator)
          q = models.Q(**{'%s%s' % (search, op): self.get_lookup_value(value)}) # <--- value is used directly in Q object
          return ~q if invert else q

      The value from the parsed string literal is directly placed into the Q object, which will be used by Django ORM to construct the SQL query. Django ORM generally parameterizes values, but for LIKE patterns, the pattern itself (including %, _) is treated as part of the SQL command and not fully parameterized in all database backends, making it vulnerable if not escaped.

    • Security test case:

      Pre-requisites:

      • Set up the test_project from the provided files.
      • Create a superuser and log in to the Django admin panel.
      • Ensure there are Book objects in the database.

      Steps:

      1. Access the Django Admin changelist view for Book objects (/admin/core/book/).
      2. Locate the DjangoQL search field.
      3. Enter the following malicious DjangoQL query into the search field:
        name startswith "') OR 1=1 --"
        
        or
        name ~ "') OR 1=1 --"
        
        or
        name endswith "') OR 1=1 --"
        
      4. Submit the search query.
      5. Observe the results. If the vulnerability is present, the query 1=1 will always be true, and the -- comment will comment out the rest of the original query condition. This could result in all Book objects being returned, regardless of the actual intended search criteria. This behavior would indicate successful SQL injection.
      6. (Advanced Test - Requires Database Logging): Examine the raw SQL queries executed by Django in the database logs. You should see the injected SQL being directly embedded within the LIKE clause, confirming the SQL injection vulnerability. For example, the logged query might look something like:
        SELECT ... FROM core_book WHERE core_book.name LIKE '%) OR 1=1 --%';

This test case demonstrates a basic SQL injection. More sophisticated injection payloads can be crafted to extract data or perform other database operations.