Check quoted function names like bare ones in the SQL mode

PostgreSQL accepts a function name in double quotes, so
SELECT "pg_read_file"('/etc/passwd') passed the forbidden-construct
check and SELECT "setval"('seq', 1) skipped the confirmation: the
patterns expect the name right before the parenthesis. The scanner now
unquotes a plain identifier when a parenthesis follows it, so a quoted
call is checked like the bare function name; quoted column names stay
as they are.
This commit is contained in:
2026-09-30 16:56:53 +02:00
parent d7b093812d
commit 5c7fbcbbfc
2 changed files with 13 additions and 1 deletions
+8 -1
View File
@@ -122,7 +122,14 @@ class SqlGuard
while ($i < $len) {
$end = self::literalEnd($sql, $i);
if ($end !== null) {
$result .= $replaceLiterals ? ($sql[$i] === '"' ? '""' : "''") : substr($sql, $i, $end - $i);
$literal = substr($sql, $i, $end - $i);
if (preg_match('/^"([A-Za-z_][A-Za-z0-9_$]*)"$/', $literal, $m) && preg_match('/\G\s*\(/', $sql, $unused, 0, $end)) {
// A quoted function name ("setval"(...)) calls the same
// function as the bare word, so check it as one
$result .= $m[1];
} else {
$result .= $replaceLiterals ? ($sql[$i] === '"' ? '""' : "''") : $literal;
}
$i = $end;
continue;
}
+5
View File
@@ -24,6 +24,8 @@ class SqlGuardTest extends TestCase
["SELECT lo_import('/etc/passwd')"],
["SELECT dblink_connect('host=x')"],
["SELECT dblink_exec('host=x', 'DROP TABLE t')"],
["SELECT \"pg_read_file\"('/etc/passwd')"],
["SELECT pg_catalog.\"pg_read_file\"('/etc/passwd')"],
["SELECT dblink_send_query('c', 'SELECT 1')"],
["SELECT * FROM dblink('host=x', 'SELECT 1') AS t(a int)"],
["SELECT * FROM t INTO OUTFILE '/tmp/x'"],
@@ -122,6 +124,9 @@ class SqlGuardTest extends TestCase
["SELECT set_config('x', 'y', false)"],
["SELECT setval('seq', 1)"],
["SELECT nextval('seq')"],
["SELECT \"setval\"('seq', 1)"],
["SELECT pg_catalog.\"setval\"('seq', 1)"],
["SELECT pg_catalog.setval('seq', 1)"],
['MERGE INTO t USING s ON true WHEN MATCHED THEN DELETE'],
['VACUUM'],
['PRAGMA journal_mode = WAL'],