fix(manager): run pg backups without a shell - #2451
Open
elcreator wants to merge 1 commit into
Open
Conversation
The PostgreSQL branches of the backup manager built psql and pg_dump command lines by concatenation and handed them to exec(). The snapshot name from the restore form and the table names from the checkbox list went in unquoted, so bk_manager - a permission whose intended reach is arbitrary SQL through the restore form, not arbitrary shell - was enough to append a command of its own. The password rode in argv, where ps and /proc/<pid>/cmdline show it to every local account. All three branches now go through DatabaseBackupService, which invokes the client with an argument list and the password in the environment. No shell parses either, so an argument holding a semicolon stays one argument and nothing depends on escaping being right. This is the same form createSnapshot() already used, which also means these paths start working on Windows, where a leading PGPASSWORD= assignment was a cmd.exe syntax error. The dump no longer lands in assets/backup/temp.php. That path is inside the web root and the default ht.access excludes assets/ from every rule it has, so a full dump sat there readable by anyone who guessed the name; it now goes to the snapshot directory under a name that is not guessable. The deny rule written into that directory gained the Apache 2.4 form - Order/Deny alone is 2.2 syntax and 2.4 without mod_access_compat answers 500, leaving the directory served. The snapshot to restore is resolved against the snapshot directory and rejected if it lands outside, which also closes the traversal on the MySQL and SQLite branches, where the name reached import_sql_from_file() unchecked. The drop-tables checkbox is honoured again; it built an argument the command never used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSZQgDv5ASxQaiYd5C1nJR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The PostgreSQL branches of the backup manager built psql and pg_dump command lines by concatenation and handed them to exec(). The snapshot name from the restore form and the table names from the checkbox list went in unquoted, so bk_manager - a permission whose intended reach is arbitrary SQL through the restore form, not arbitrary shell - was enough to append a command of its own. The password rode in argv, where ps and /proc//cmdline show it to every local account.
All three branches now go through DatabaseBackupService, which invokes the client with an argument list and the password in the environment. No shell parses either, so an argument holding a semicolon stays one argument and nothing depends on escaping being right. This is the same form createSnapshot() already used, which also means these paths start working on Windows, where a leading PGPASSWORD= assignment was a cmd.exe syntax error.
The dump no longer lands in assets/backup/temp.php. That path is inside the web root and the default ht.access excludes assets/ from every rule it has, so a full dump sat there readable by anyone who guessed the name; it now goes to the snapshot directory under a name that is not guessable. The deny rule written into that directory gained the Apache 2.4 form - Order/Deny alone is 2.2 syntax and 2.4 without mod_access_compat answers 500, leaving the directory served.
The snapshot to restore is resolved against the snapshot directory and rejected if it lands outside, which also closes the traversal on the MySQL and SQLite branches, where the name reached import_sql_from_file() unchecked. The drop-tables checkbox is honoured again; it built an argument the command never used.