-
Vulnerability Name: Incomplete cache invalidation for TRUNCATE TABLE
-
Description: When a
TRUNCATE TABLE
command is executed via raw SQL, cachalot's raw SQL invalidation mechanism fails to detect it because the regular expression used to identify data-modifying SQL commands does not include theTRUNCATE TABLE
keyword. This results in stale cache entries for queries involving the truncated table.Steps to trigger vulnerability:
- Application executes a database query through Django ORM that is cached by cachalot.
- An attacker, or an internal process, executes a raw SQL query
TRUNCATE TABLE <table_name>;
targeting the table involved in the cached query. This can be done through a raw SQL execution vulnerability in the application or directly to the database if attacker has access. - Application executes the same database query again through Django ORM.
-
Impact: Stale cache data. Subsequent queries might return cached results that are inconsistent with the database state after the
TRUNCATE TABLE
operation. This could lead to incorrect application behavior, data integrity issues, and potential information disclosure if cached data is sensitive or if application logic relies on up-to-date data. -
Vulnerability Rank: High
-
Currently Implemented Mitigations: None for
TRUNCATE TABLE
. Cachalot attempts to invalidate cache for raw SQL data modification using a regular expression incachalot/monkey_patch.py
, but this regex is incomplete and does not includeTRUNCATE TABLE
. -
Missing Mitigations: Modify the regular expression
SQL_DATA_CHANGE_RE
incachalot/monkey_patch.py
to include thetruncate
keyword. This will ensure thatTRUNCATE TABLE
commands executed via raw SQL are detected and trigger cache invalidation for the affected tables. -
Preconditions:
- Cachalot is enabled in the Django project.
- Queries involving a specific database table are being cached.
- There is a way for an attacker or internal process to execute raw SQL queries against the database, specifically
TRUNCATE TABLE
commands, targeting tables that are being cached by cachalot.
-
Source Code Analysis:
- File:
/code/cachalot/monkey_patch.py
- Locate the
_patch_cursor
function and theSQL_DATA_CHANGE_RE
regular expression:
SQL_DATA_CHANGE_RE = re.compile( '|'.join([ fr'(\W|\A){re.escape(keyword)}(\W|\Z)' for keyword in ['update', 'insert', 'delete', 'alter', 'create', 'drop'] ]), flags=re.IGNORECASE, ) def _patch_cursor_execute(original): @wraps(original) def inner(cursor, sql, *args, **kwargs): try: return original(cursor, sql, *args, **kwargs) finally: connection = cursor.db if getattr(connection, 'raw', True): if isinstance(sql, bytes): sql = sql.decode('utf-8') sql = sql.lower() if SQL_DATA_CHANGE_RE.search(sql): # Vulnerability: 'truncate' is missing in regex tables = filter_cachable( _get_tables_from_sql(connection, sql)) if tables: invalidate( *tables, db_alias=connection.alias, cache_alias=cachalot_settings.CACHALOT_CACHE)
- The
SQL_DATA_CHANGE_RE
regex is intended to detect data-modifying SQL commands in raw SQL queries. However, the keyword list['update', 'insert', 'delete', 'alter', 'create', 'drop']
does not includetruncate
. - When a raw SQL query like
TRUNCATE TABLE <table_name>;
is executed,SQL_DATA_CHANGE_RE.search(sql.lower())
will returnNone
because 'truncate' is not in the regex. - As a result, the
invalidate()
function is not called, and the cache related to<table_name>
is not invalidated, leading to stale cache data.
- File:
-
Security Test Case:
- Setup:
- Ensure Django project is set up with cachalot enabled and a cache backend configured (e.g., locmem).
- Define a Django model, for example, the
Test
model from/code/cachalot/tests/models.py
. - Create an instance of the
Test
model in the database.
- Test Steps:
- Execute a query that should be cached. For example, in Django shell:
from cachalot.tests.models import Test list(Test.objects.all()) # First execution, should hit database
- Verify that the query is cached by executing it again and checking the number of database queries.
from django.test import TestCase class MyTest(TestCase): def test_cache_hit(self): with self.assertNumQueries(0): # Expecting 0 queries as it's cached list(Test.objects.all())
- Execute a raw SQL
TRUNCATE TABLE
command targeting the table of theTest
model. Get the table name fromTest._meta.db_table
. In Django shell:from django.db import connection table_name = Test._meta.db_table with connection.cursor() as cursor: cursor.execute(f'TRUNCATE TABLE {table_name};')
- Execute the cached query again:
class MyTest(TestCase): def test_stale_cache_after_truncate(self): with self.assertNumQueries(0): # Still expecting 0 queries - STALE CACHE! result = list(Test.objects.all()) self.assertNotEqual(len(result), 0) # Expecting stale data, not empty result
- Assert that the result of the query is still served from the cache (0 database queries) and that the result is not empty, indicating stale data despite the table being truncated.
- Execute a query that should be cached. For example, in Django shell:
- Expected Result (Vulnerability): The test
test_stale_cache_after_truncate
should pass, demonstrating that the cache is stale afterTRUNCATE TABLE
. The number of queries should be 0, and the result should not be empty, proving the vulnerability. - Mitigation (To fix the vulnerability): Modify
cachalot/monkey_patch.py
and add'truncate'
to thekeywords
list inSQL_DATA_CHANGE_RE
definition. - Retest after Mitigation: After applying the mitigation, rerun the security test case. The test
test_stale_cache_after_truncate
should now fail in the intended way, or the assertion should be adjusted to expect 1 query and an empty result, confirming that the cache is correctly invalidated afterTRUNCATE TABLE
.
- Setup:
- Vulnerability Name: Arbitrary Code Execution via Unsanitized Eval in the Benchmark Module
- Description:
- The benchmark module (in the previously reviewed
benchmark.py
file) constructs a Python lambda function by concatenating a fixed string with a parameter (query_str
). - This concatenated string is then passed unchecked to an unsanitized
eval
call. - If an adversary can somehow supply or influence the value of
query_str
—for example, via an accidental exposure of the module as an HTTP endpoint or misuse in a production-like configuration—they will be able to inject and execute arbitrary Python code. Steps to trigger vulnerability:
- Attacker finds a way to influence or supply the
query_str
parameter, potentially through a misconfigured endpoint or exposed benchmark functionality. - The benchmark module's code uses
eval
to execute a lambda function constructed using the attacker-controlledquery_str
. - Arbitrary Python code injected within
query_str
is executed on the server.
- The benchmark module (in the previously reviewed
- Impact:
- Successful exploitation would allow an attacker to execute arbitrary code on the server, potentially reading, modifying, or deleting sensitive data and even leading to full system takeover.
- Vulnerability Rank: Critical
- Currently Implemented Mitigations:
- In the current source, the benchmark queries are hard‑coded inside the
execute_benchmark()
method, and the module is intended only for performance testing (not production use).
- In the current source, the benchmark queries are hard‑coded inside the
- Missing Mitigations:
- No input sanitization or safe parsing (for example, using a restricted evaluation context or a safe expression parser) is applied to the dynamic string before it is evaluated.
- There is no access control preventing this module from being accidentally exposed via a public endpoint.
- Preconditions:
- The benchmark module must be deployed or inadvertently exposed in a production environment.
- An attacker must be able to supply a manipulated value for
query_str
(for example, through a misconfigured view or API endpoint that calls the benchmarking code).
- Source Code Analysis:
- In the method that builds the query, the code simply prepends
"Test.objects.using(using)"
to the providedquery_str
. - If the query is wrapped (e.g. via
list( …)
when a flag is true), the resulting string is still formed directly from user-controlled input. - This string is then concatenated into an expression passed to
eval("lambda using: " + query_str)
without any filtering or sanitization, meaning an attacker could insert arbitrary Python code.
- In the method that builds the query, the code simply prepends
- Security Test Case:
- Deploy the application in a secure test environment where the benchmark module is accessible (for example, via a debug endpoint).
- Craft an HTTP request or simulate the call by supplying a suspect
query_str
payload such as:".count() or __import__('os').system('echo vulnerable > /tmp/owned.txt')"
- Invoke the benchmark method and check the system for the creation of
/tmp/owned.txt
or the execution of other measurable side‐effects to confirm that injected code is executed.
- Description:
- Vulnerability Name: Default Database Credentials Vulnerability
- Description:
- In the project’s configuration (as seen in the previously reviewed
settings.py
), insecure defaults are specified for database connections. - For PostgreSQL, the password is hard‑coded as
"password"
, and for MySQL an empty password is allowed. - Although environment variables (e.g.
POSTGRES_PASSWORD
andMYSQL_PASSWORD
) can override these defaults, if a deployment uses the default configurations the insecure credentials remain in effect. Steps to trigger vulnerability:
- Application is deployed using default settings without overriding environment variables for database credentials.
- An attacker gains network access to the database server due to lax network restrictions or misconfiguration.
- Attacker attempts to connect to the PostgreSQL instance using the default password
"password"
or to the MySQL instance with an empty password.
- In the project’s configuration (as seen in the previously reviewed
- Impact:
- An external attacker able to reach the database (for instance, if network restrictions are lax) can leverage these default credentials to gain unauthorized access.
- The attacker could then exfiltrate or tamper with sensitive backend data, potentially leading to a full compromise of the backend database infrastructure.
- Vulnerability Rank: High
- Currently Implemented Mitigations:
- The project permits credentials to be overridden through environment variables; however, nothing enforces that these defaults must be changed in production deployments.
- Missing Mitigations:
- There is no runtime check or warning to ensure that insecure default credentials are not used.
- No deployment‐time configuration management is in place to enforce the use of secure credentials on publicly accessible instances.
- Preconditions:
- The application is deployed using the default settings without environment variable overrides.
- The underlying database servers are accessible to external attackers (for example, via misconfigured network/firewall settings).
- Source Code Analysis:
- In
settings.py
, the PostgreSQL configuration is hard-coded withPASSWORD: 'password'
and MySQL is configured to allow an empty password (as indicated by the flagMYSQL_ALLOW_EMPTY_PASSWORD: yes
). - Although the code checks for environment variable overrides, no mechanism enforces that these insecure defaults are replaced before a production deployment.
- In
- Security Test Case:
- In a controlled test environment, deploy the application without setting the overriding environment variables.
- From an external system, attempt to connect to the PostgreSQL and MySQL instances using the default credentials.
- Verify that the connection succeeds and that the attacker can read or list databases, confirming the vulnerability.
- Description:
- Vulnerability Name: Debug Toolbar Information Disclosure Vulnerability
- Description:
- The project configuration (including entries in
INSTALLED_APPS
and URL routing in, for example,runtests_urls.py
) enables the Django debug toolbar unconditionally when DEBUG mode is active. - Although access is nominally restricted by setting
INTERNAL_IPS = ['127.0.0.1']
, if the application is deployed withDEBUG=True
or if a reverse proxy or network mis‑configuration permits spoofing of the internal IP check, an external attacker could access the debug toolbar. - In the project’s test file (
debug_toolbar.py
), the toolbar is rendered on the root URL, and its panels (which include detailed runtime and SQL query information) are accessible. Steps to trigger vulnerability:
- Application is deployed in production with
DEBUG=True
. - An attacker accesses the application through a web browser.
- Attacker navigates to debug toolbar URLs (e.g.,
/__debug__/
). - If
INTERNAL_IPS
check is bypassed due to misconfiguration or header spoofing, the debug toolbar is rendered.
- The project configuration (including entries in
- Impact:
- Disclosure of the debug toolbar exposes sensitive runtime details, such as SQL queries, settings, and cache states.
- This detailed internal information can help an attacker craft further attacks by revealing application structure and behavior.
- Vulnerability Rank: High
- Currently Implemented Mitigations:
- The configuration restricts toolbar access by setting
INTERNAL_IPS
to['127.0.0.1']
, which under normal conditions should only permit local requests.
- The configuration restricts toolbar access by setting
- Missing Mitigations:
- There is no explicit enforcement that
DEBUG
(and the debug toolbar) is disabled in production environments. - No advanced access controls (e.g. proper handling of proxy headers or additional authentication) are implemented to ensure that remote requests cannot bypass the internal IP check.
- There is no explicit enforcement that
- Preconditions:
- The application must be deployed with
DEBUG=True
(or with the debug toolbar enabled) in a production environment. - An attacker must be able to bypass or spoof the INTERNAL_IPS restriction (for example, via a misconfigured reverse proxy or by manipulating request headers like
X-Forwarded-For
).
- The application must be deployed with
- Source Code Analysis:
- In the project’s settings, the debug toolbar is added to both
INSTALLED_APPS
andMIDDLEWARE
, and the URL configuration (as exemplified byruntests_urls.py
) routes requests beginning with/__debug__/
to the toolbar. - The simplistic use of an internal IP check means that if an attacker can present a spoofed internal IP address, the detailed debugging interface becomes available.
- In the project’s settings, the debug toolbar is added to both
- Security Test Case:
- Deploy the application in a staging environment with
DEBUG=True
. - From an external machine, attempt to access the
/__debug__/
URL. - Then modify request headers (for example, setting
X-Forwarded-For
to127.0.0.1
) and repeat the request. - Verify whether the debug toolbar is rendered and examine the page for detailed internal information (such as SQL logs, setting values, and cache details).
- Successful access confirms the vulnerability and highlights the need to disable the toolbar outside of secure local development.
- Deploy the application in a staging environment with
- Description: