Here is the combined list of vulnerabilities, formatted as markdown:
-
Vulnerability Name: Path Traversal Write via Filename Template / Arbitrary File Path Injection in Backup File Naming
-
Description:
- An attacker with administrative privileges (or who has compromised an admin account) can exploit a path traversal vulnerability by manipulating the
DBBACKUP_FILENAME_TEMPLATE
orDBBACKUP_MEDIA_FILENAME_TEMPLATE
settings. These templates are used to generate backup filenames. - Step 1: The attacker gains access to the Django admin panel or any interface that allows modification of Django settings, specifically
DBBACKUP_FILENAME_TEMPLATE
orDBBACKUP_MEDIA_FILENAME_TEMPLATE
. - Step 2: The attacker modifies either
DBBACKUP_FILENAME_TEMPLATE
orDBBACKUP_MEDIA_FILENAME_TEMPLATE
to include path traversal characters, such as../
. For example, settingDBBACKUP_FILENAME_TEMPLATE
to../../../../tmp/evil_backup_{datetime}.db
or"../malicious-{datetime}.bak"
. - Step 3: The attacker triggers a database or media backup operation, for instance, by using the
dbbackup
ormediabackup
Django management command. - Step 4: The
filename_generate
function indbbackup/utils.py
uses the maliciously crafted template to generate the backup filename. Due to the path traversal characters, the backup file will be written to an unintended location on the server's filesystem. - Later, when cleanup routines (which use the same filename) run, they may delete files in unintended directories.
- An attacker with administrative privileges (or who has compromised an admin account) can exploit a path traversal vulnerability by manipulating the
-
Impact:
- By writing files to arbitrary locations, an attacker could overwrite critical system files, potentially leading to system instability or denial of service.
- Alternatively, the attacker could write malicious files (e.g., web shells, scripts) to directories accessible by the webserver, potentially leading to arbitrary code execution and full system compromise.
- By writing backup files outside the controlled directory, an attacker might overwrite or delete critical system files or sensitive application data. This could lead to data loss, denial of service, or even privilege escalation if key files are altered or removed.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- The project includes Django system checks (W007 and W008 in
dbbackup/checks.py
) that issue a warning if the filename templates contain slashes ('/'). These checks are passive warnings during Django'scheck
command execution and do not actively prevent path traversal during runtime or enforce input validation. - The project implements warning checks in
dbbackup/checks.py
(warnings W007 and W008) that notify the administrator if the filename templates contain slashes. However, these warnings do not block the use of unsafe templates.
- The project includes Django system checks (W007 and W008 in
-
Missing Mitigations:
- Input Validation: The project lacks runtime input validation for
DBBACKUP_FILENAME_TEMPLATE
andDBBACKUP_MEDIA_FILENAME_TEMPLATE
. It should sanitize or strictly validate these settings to disallow path traversal sequences (e.g.,../
,..\\
, absolute paths starting with/
orC:\
). Enforce strict validation of filename templates (reject templates containing “/”, “\”, or relative path components such as ".."). - Path Sanitization: While
REG_FILENAME_CLEAN.sub("-", filename)
infilename_generate
removes redundant hyphens, it does not prevent path traversal sequences. A robust path sanitization function should be implemented to remove or neutralize path traversal components from the generated filename before file system operations. Sanitize or canonicalize the generated file paths to ensure they always reside within a designated safe backup directory. - Add configuration validation that prevents unsafe backup path definitions at startup.
- Input Validation: The project lacks runtime input validation for
-
Preconditions:
- Administrative access to the Django application's settings configuration (either through Django admin panel or other configuration interfaces) or the backup filename templates are misconfigured or can be influenced by an attacker (for example, via an exposed administrative interface or unprotected environment variables).
- The application using
django-dbbackup
must allow modification ofDBBACKUP_FILENAME_TEMPLATE
orDBBACKUP_MEDIA_FILENAME_TEMPLATE
settings without proper validation.
-
Source Code Analysis:
dbbackup/settings.py
: DefinesFILENAME_TEMPLATE
andMEDIA_FILENAME_TEMPLATE
settings, which are read directly from Django'ssettings
.dbbackup/utils.py
: Thefilename_generate
function uses these templates to construct the backup filename.dbbackup/checks.py
: Includes system checks to warn about slashes in templates, but does not enforce validation or prevention.dbbackup/management/commands/dbbackup.py
&dbbackup/management/commands/mediabackup.py
: These commands useutils.filename_generate
to create backup filenames.
# File: dbbackup/management/commands/dbbackup.py class Command(BaseDbBackupCommand): # ... def _save_new_backup(self, database): # ... # Get backup, schema and name filename = self.connector.generate_filename(self.servername) # Filename generated by connector, but template comes from settings # ... filename = self.filename or filename # Output filename can override generated filename # ... if self.path is None: self.write_to_storage(outputfile, filename) # Write to storage using potentially attacker-controlled filename else: self.write_local_file(outputfile, self.path) # Write to local path, attacker-controlled path is not used here, but local path might still be misused # File: dbbackup/management/commands/mediabackup.py class Command(BaseDbBackupCommand): # ... def backup_mediafiles(self): # ... if self.filename: filename = self.filename # Output filename can override generated filename else: extension = f"tar{'.gz' if self.compress else ''}" filename = utils.filename_generate( # Filename generated using template from settings extension, servername=self.servername, content_type=self.content_type ) # ... if self.path is None: self.write_to_storage(tarball, filename) # Write to storage using potentially attacker-controlled filename else: self.write_local_file(tarball, self.path) # Write to local path, attacker-controlled path is not used here, but local path might still be misused
- In
dbbackup/utils.py
, the functionfilename_generate
retrieves the template from settings and uses it without further sanitization. -
def filename_generate( short_name, timestamp=None, servername=None, extension=None, template=FILENAME_TEMPLATE, # FILENAME_TEMPLATE is taken from settings **kwargs ): if not timestamp: timestamp = timezone.now().strftime("%Y-%m-%d-%H%M%S") if not servername: servername = socket.gethostname() if not extension: extension = 'bak' filename = template.format( # template is used directly without sanitization short_name=short_name, timestamp=timestamp, servername=servername, extension=extension, datetime=timestamp, # datetime is also timestamp **kwargs ) return filename
- Although the helper
_check_filename_template
(called indbbackup/checks.py
) flags templates that contain a forward slash, it “only” issues a warning. - The resulting filename is later passed directly to file write and delete methods in
dbbackup/storage.py
without additional path validation. - The commands
dbbackup
andmediabackup
use the filename generated byutils.filename_generate
which uses the potentially vulnerable templatesDBBACKUP_FILENAME_TEMPLATE
andDBBACKUP_MEDIA_FILENAME_TEMPLATE
from Django settings. If an attacker can modify these settings, they can control the output filename and potentially write files outside the intended backup directory.
-
Security Test Case:
- Set up a Django project using
django-dbbackup
. Ensure you have access to Django admin panel or a similar settings configuration interface. - Log in as a superuser or an administrator who has permissions to modify Django settings.
- Navigate to the settings configuration interface for
django-dbbackup
. This might be a custom admin page or direct access to settings.py if applicable for your test setup. - Locate the setting for
DBBACKUP_FILENAME_TEMPLATE
and change its value to:../../../../tmp/evil_backup_{datetime}.db
or"../malicious-{datetime}.bak"
. - Execute the database backup command, for example, using Django's
manage.py dbbackup
. - After the backup command completes, check the
/tmp/
directory on the server. - Verify if a file named
evil_backup_<datetime>.db
or a file whose name starts with “malicious-” (with a timestamp in the filename) has been created in the/tmp/
directory. - If the file exists in
/tmp/
, it confirms the Path Traversal Write vulnerability. The backup file was written outside the intended backup location due to the manipulated filename template. - Optionally, trigger a cleanup command and verify that it deletes files in the unintended location.
- Confirm that an attacker could leverage this behavior to overwrite or erase critical files on the server.
- Set up a Django project using
-
Vulnerability Name: Insecure Backup File Storage Exposure
-
Description:
- By default the project uses a Django storage backend for saving backup files. If the settings
DBBACKUP_STORAGE
andDBBACKUP_STORAGE_OPTIONS
are not explicitly configured to point to a secured, nonpublic location, the fallback is to use the defaultdjango.core.files.storage.FileSystemStorage
. In many deployments the default storage location may be web‑accessible. - An attacker who visits a known URL (or is able to probe for backup files) might be able to download backup files containing sensitive information such as database dumps and media files.
- By default the project uses a Django storage backend for saving backup files. If the settings
-
Impact:
- Sensitive data—including potentially confidential user or application information stored in database backups—can be exposed publicly. This leakage can lead to data breaches, legal repercussions, and reputation damage.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- The project allows administrators to configure both the storage backend and its options via settings. In tests (see
dbbackup/tests/settings.py
), backups are directed to temporary, nonpublic directories. However, the default behavior in production environments may simply fall back to the standard file system storage with no additional access restrictions.
- The project allows administrators to configure both the storage backend and its options via settings. In tests (see
-
Missing Mitigations:
- Enforce that backup files are stored by default in a secure, non‑public directory that is inaccessible from the web.
- Validate storage configuration during application startup, ensuring that file permissions and access controls are correctly set.
- Consider adding explicit access checks for backup file endpoints if the storage backend is served somehow over HTTP.
-
Preconditions:
- The deployment does not override default storage settings (i.e.
DBBACKUP_STORAGE
andDBBACKUP_STORAGE_OPTIONS
) to point to a secure location, leaving backups stored in a web‑accessible location.
- The deployment does not override default storage settings (i.e.
-
Source Code Analysis:
dbbackup/settings.py
: The code sets the backupSTORAGE
to the value ofDBBACKUP_STORAGE
or falls back todjango.core.files.storage.FileSystemStorage
.-
from django.conf import settings as django_settings from django.core.files.storage import FileSystemStorage STORAGE = getattr(django_settings, 'DBBACKUP_STORAGE', 'dbbackup.storage.FileSystemStorage') # default is FileSystemStorage STORAGE_OPTIONS = getattr(django_settings, 'DBBACKUP_STORAGE_OPTIONS', {})
- The
Storage
class indbbackup/storage.py
merely instantiates the given storage class and uses it for file operations without adding additional security measures. -
def get_storage(self): storage_cls = import_string(settings.STORAGE) # STORAGE from dbbackup.settings return storage_cls(**settings.STORAGE_OPTIONS) # STORAGE_OPTIONS from dbbackup.settings
- No further restrictions (such as permissions or authentication checks) are applied to the backup file access.
-
Security Test Case:
- Deploy the application with default settings (i.e. without overriding
DBBACKUP_STORAGE
andDBBACKUP_STORAGE_OPTIONS
). - Run a backup command (for example,
python manage.py dbbackup
) to create a backup file. - Identify the physical location or the URL where the backup file is stored. For
FileSystemStorage
default location isMEDIA_ROOT
. IfMEDIA_ROOT
is within webserver document root, then files are accessible. - Using a web browser or HTTP client, attempt to access and download the backup file without any credentials. For example, if
MEDIA_URL
is/media/
and backup file is stored inMEDIA_ROOT/backups/
, then attacker can try to access/media/backups/<backup_filename>
. - Confirm that the backup file is accessible and review its contents for sensitive data.
- Deploy the application with default settings (i.e. without overriding
-
Vulnerability Name: Potential Subprocess Command Injection in Database Backup Commands
-
Description:
- The connectors for various databases (e.g. MySQL, PostgreSQL, MongoDB) build shell command strings by concatenating configuration parameters such as database names, hostnames, port numbers, user names, and table names.
- Although some parameters (e.g. passwords) are run through escaping routines like
utils.get_escaped_command_arg
, many other fields are inserted directly into command strings using f‑strings. - If an attacker can tamper with any of these configuration values—through misconfiguration, tainted environment variables, or insecure admin interfaces—they may inject malicious shell commands into the string.
- For example, if the database name were set to:
dbname; rm -rf /
, the constructed command might include an extra command that executes destructive actions.
-
Impact:
- If successful, this vulnerability could allow an attacker to execute arbitrary shell commands on the host. The impact ranges from data exfiltration to complete system compromise, resulting in data destruction or full takeover of the server.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- The code uses
shlex.split
to break the constructed command string into a list before passing it tosubprocess.Popen
, and helper functions (likeget_escaped_command_arg
) are used for certain parameters (e.g. passwords). - However, many other parameters (such as the database name, host, port, and table names extracted from
self.exclude
) are concatenated directly without thorough sanitization.
- The code uses
-
Missing Mitigations:
- Rigorously validate and sanitize all configuration parameters that are inserted into shell commands.
- Avoid the use of unsanitized f‑string concatenation for command construction. Prefer to build the command as a list of arguments or use safer subprocess interfaces that do not require shell parsing.
- Ensure that the application’s configuration cannot be tampered with by external users (for example, by restricting environment variable access or protecting admin interfaces).
-
Preconditions:
- An attacker must be able to influence one or more database configuration parameters (even if indirectly through an insecurely managed admin interface or misconfigured environment).
- The vulnerable command construction logic (in connectors such as those in
dbbackup/db/mysql.py
anddbbackup/db/postgresql.py
) is executed as part of a backup or restore operation.
-
Source Code Analysis:
- MySQL Connector Example: In
dbbackup/db/mysql.py
, the_create_dump
method builds a command string using various settings: -
def _create_dump(self): args = [self.dump_cmd] # e.g. ['mysql'] if self.settings.get('HOST'): args += ['-h', self.settings['HOST']] # HOST setting is added without sanitization if self.settings.get('PORT'): args += ['-P', str(self.settings['PORT'])] # PORT setting is added without sanitization if self.settings.get('USER'): args += ['-u', self.settings['USER']] # USER setting is added without sanitization if self.settings.get('PASSWORD'): password = get_escaped_command_arg(self.settings['PASSWORD']) # PASSWORD setting is escaped args += ['-p{}'.format(password)] args += [self.settings['NAME']] # NAME setting (database name) is added without sanitization if self.exclude: args += ['--ignore-table={}'.format(self.settings['NAME'] + '.' + table) for table in self.exclude] # table names from exclude are added without sanitization if self.include_tables: args += self.include_tables cmd = args process = subprocess.Popen(cmd, env=env, **self.popen_kwargs) # cmd is passed as list to subprocess.Popen process.wait()
- The database name (
self.settings['NAME']
), HOST, PORT, USER, and table names fromself.exclude
are concatenated directly without thorough sanitization. While the password is escaped usingget_escaped_command_arg
, other parameters are not sanitized.
- The database name (
- PostgreSQL Connector Example: In
dbbackup/db/postgresql.py
, thecreate_postgres_uri
function constructs a connection URI: -
def create_postgres_uri(self): host = self.settings.get("HOST", "localhost") dbname = self.settings.get("NAME", "") user = quote(self.settings.get("USER") or "") password = self.settings.get("PASSWORD", "") password = f":{quote(password)}" if password else "" if not user: password = "" else: host = "@" + host port = ":{}".format(self.settings.get("PORT")) if self.settings.get("PORT") else "" dbname = f"--dbname=postgresql://{user}{password}{host}{port}/{dbname}" return dbname
- The
dbname
variable, derived fromself.settings.get("NAME", "")
, is directly incorporated into the command string without proper sanitization beyond URL encoding for username and password. Similar patterns appear in the PostgreSQL and MongoDB connector implementations.
- The
- Ultimately, the command is passed as a list to
subprocess.Popen
without using a shell, yet unsanitized input in arguments can still lead to unintended command execution depending on howmysql
,pg_dump
,psql
or other database tools parse arguments.
- MySQL Connector Example: In
-
Security Test Case:
- In a controlled test environment, adjust one configuration parameter—for example, set the database name to a value like:
dbname; echo HACKED
(ensure this test is done on a disposable system). This can be achieved via environment variables or Django admin if accessible. For example, setting environment variableDBBACKUP_DATABASE_NAME="dbname; echo HACKED"
might influence database name. - Run the backup command that uses this configuration (e.g.,
python manage.py dbbackup
). - Monitor the executed command output or the system logs to detect whether the additional command fragment (e.g. “HACKED”) was executed outside the intended backup command context. Redirecting standard output and standard error of the backup command to files can help in monitoring.
- Repeat with other parameters (such as elements in the exclusion list, HOST, PORT, USER) to confirm that unsanitized input can lead to the execution of arbitrary commands. Check if injecting options like
-v
or--help
still works and if it's possible to inject more dangerous options or commands.
Example Test Case for PostgreSQL Command Injection via Database Name:
- Step 1: Modify Django settings to set a malicious database name. In
dbbackup/tests/settings.py
, add or modify theDATABASES
setting to include a database with a malicious name:
DATABASES = { 'default': { 'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'), 'NAME': os.environ.get('DB_NAME', ':memory:'), # ... other settings }, 'vulndb': { # Add a new database with a malicious name 'ENGINE': 'django.db.backends.postgresql', # or 'django.contrib.gis.db.backends.postgis' 'NAME': '"; touch /tmp/pwned_db_name #"', # Malicious database name 'USER': 'testuser', 'PASSWORD': 'testpassword', 'HOST': 'localhost', } }
- Step 2: Execute the
dbbackup
management command, targeting the vulndb database:
python manage.py dbbackup --database=vulndb
- Step 3: Check for command execution. After running the command, check if the file
/tmp/pwned_db_name
has been created on the server.
ls -l /tmp/pwned_db_name
- If the file
/tmp/pwned_db_name
exists, it indicates that the command injection vulnerability is present indbbackup
command. - Step 4: Execute the
dbrestore
management command, targeting the vulndb database:
python manage.py dbrestore --database=vulndb
- Step 5: Check for command execution. After running the command, check if the file
/tmp/pwned_db_name
has been created on the server.
ls -l /tmp/pwned_db_name
- If the file
/tmp/pwned_db_name
exists, it indicates that the command injection vulnerability is present indbrestore
command as well.
- In a controlled test environment, adjust one configuration parameter—for example, set the database name to a value like: