ext/pgsql: route pg_copy_from table_name through build_tablename#21985
ext/pgsql: route pg_copy_from table_name through build_tablename#21985iliaal wants to merge 1 commit intophp:masterfrom
Conversation
|
Thanks for starting the work, but as hinted. it is preferable you really go through. I ll put some comments. |
|
I want to see $null_as parameter being challenged too, e.g. pg_query($db, "DROP TABLE IF EXISTS table1");
pg_query($db, "DROP TABLE IF EXISTS injected");
pg_query($db, "CREATE TABLE table1 (v text)");
$null_as = "X'; CREATE TABLE injected (v text); --";
pg_copy_from($db, 'victim', ["row\n"], "\t", $null_as);
$r = pg_query($db, "SELECT 1 FROM pg_tables WHERE tablename='injected'");
var_dump(pg_num_rows($r)); => int(1) |
|
The COPY query embedded the table_name argument with a raw "%s" and the delimiter and null marker inside literal E'..' wrappers, so caller-supplied strings could break out and run side queries. Route bare table names through build_tablename (the same helper pg_insert/update/select/delete have used since bug #62978), and pass the delimiter and null marker through PQescapeLiteral. pg_copy_to keeps the parenthesised (query) source form documented in bug 73498, but wraps it in an extra paren pair so a string like (SELECT 1); DROP TABLE x; -- becomes a syntax error inside the outer parens instead of escaping out into a second statement. Closes phpGH-21985
1f0a1fa to
bc6f630
Compare
|
Both |
| char *pg_null_as = "\\\\N"; | ||
| size_t pg_null_as_len = 0; | ||
| char *query; | ||
| size_t pg_null_as_len = sizeof("\\\\N") - 1; |
There was a problem hiding this comment.
hmmmm ... can you try a test that looks like this ?
--TEST--
pg_copy_to() / pg_copy_from() default null marker is "\N"
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php
include('inc/config.inc');
$t = "pg_copy_default_null";
$db = pg_connect($conn_str);
pg_query($db, "DROP TABLE IF EXISTS {$t}");
pg_query($db, "CREATE TABLE {$t} (id int, v text)");
pg_query($db, "INSERT INTO {$t} VALUES (1, 'hello'), (2, NULL)");
$rows = pg_copy_to($db, $t);
var_dump($rows);
pg_query($db, "DELETE FROM {$t}");
var_dump(pg_copy_from($db, $t, $rows));
var_dump(pg_fetch_all(pg_query($db, "SELECT v FROM {$t} ORDER BY id")));
?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, "DROP TABLE IF EXISTS pg_copy_default_null");
?>
--EXPECT--
array(2) {
[0]=>
string(8) "1 hello
"
[1]=>
string(4) "2 \N
"
}
bool(true)
array(2) {
[0]=>
array(1) {
["v"]=>
string(5) "hello"
}
[1]=>
array(1) {
["v"]=>
NULL
}
}
The COPY query embedded the table_name argument with a raw "%s" and the delimiter and null marker inside literal E'..' wrappers, so caller-supplied strings could break out and run side queries. Route bare table names through build_tablename (the same helper pg_insert/update/select/delete have used since bug #62978), and pass the delimiter and null marker through PQescapeLiteral. pg_copy_to keeps the parenthesised (query) source form documented in bug 73498, but wraps it in an extra paren pair so a string like (SELECT 1); DROP TABLE x; -- becomes a syntax error inside the outer parens instead of escaping out into a second statement. Closes phpGH-21985
bc6f630 to
6fbe705
Compare
|
Ah yes, good catch. Just |
|
other things to think about (no rush, won t be committed today), what happens when query has already parens, your test need stricter output expectations ; i.e. pg_copy_from(): %s ... your new error messages need to be more specific, aka which parameter had failed. |
| if (!escaped_delimiter || !escaped_null_as) { | ||
| php_error_docref(NULL, E_WARNING, "Failed to escape COPY parameters"); | ||
| if (escaped_delimiter) PQfreemem(escaped_delimiter); | ||
| if (escaped_null_as) PQfreemem(escaped_null_as); |
There was a problem hiding this comment.
nit: code style in php-src is new line and braces even with just 1 line block
There was a problem hiding this comment.
Bah 😆 ok something to tweak
If already has parens shouldn't be an issue would just be double parens, I tested locally but let me double check and add to test. |
The COPY query embedded the table_name argument with a raw "%s" and the delimiter and null marker inside literal E'..' wrappers, so caller-supplied strings could break out and run side queries. Route bare table names through build_tablename (the same helper pg_insert/update/select/delete have used since bug #62978), and pass the delimiter and null marker through PQescapeLiteral. pg_copy_to keeps the parenthesised (query) source form documented in bug 73498, but wraps it in an extra paren pair so a string like (SELECT 1); DROP TABLE x; -- becomes a syntax error inside the outer parens instead of escaping out into a second statement. Closes phpGH-21985
6fbe705 to
7ff2c22
Compare
|
|
||
| char *escaped_delimiter = PQescapeLiteral(pgsql, ZSTR_VAL(pg_delimiter), 1); | ||
| if (!escaped_delimiter) { | ||
| php_error_docref(NULL, E_WARNING, "Failed to escape delimiter"); |
There was a problem hiding this comment.
as mentioned, you have the specific information available, e.g. here pg_delimiter.
There was a problem hiding this comment.
Maybe I didn't quite get the ask here, you want the error message to include the delimiter itself, seems of limited value to me, but can be added I suppose
There was a problem hiding this comment.
yes just for clarification's sake.
There was a problem hiding this comment.
or even better...
zend_string *msgbuf = _php_pgsql_trim_message(PQerrorMessage(pgsql));
php_error_docref...
zend_string_release(msgbuf);
There was a problem hiding this comment.
Applied PQerrorMessage across all four errors and added params where needed, also for consistency promoted table escape error from notice to warning
|
You covered most of the points I ll have a fresh look in the following days. Cheers ! |
The COPY query embedded the table_name argument with a raw "%s" and the delimiter and null marker inside literal E'..' wrappers, so caller-supplied strings could break out and run side queries. Route bare table names through build_tablename (the same helper pg_insert/update/select/delete have used since bug #62978), and pass the delimiter and null marker through PQescapeLiteral. pg_copy_to keeps the parenthesised (query) source form documented in bug 73498, but wraps it in an extra paren pair so a string like (SELECT 1); DROP TABLE x; -- becomes a syntax error inside the outer parens instead of escaping out into a second statement. Closes phpGH-21985
7ff2c22 to
a6dfbfb
Compare
The COPY query embedded the table_name argument with a raw "%s" and the delimiter and null marker inside literal E'..' wrappers, so caller-supplied strings could break out and run side queries. Route bare table names through build_tablename (the same helper pg_insert/update/select/delete have used since bug #62978), and pass the delimiter and null marker through PQescapeLiteral. pg_copy_to keeps the parenthesised (query) source form documented in bug 73498, but wraps it in an extra paren pair so a string like (SELECT 1); DROP TABLE x; -- becomes a syntax error inside the outer parens instead of escaping out into a second statement. Closes phpGH-21985
a6dfbfb to
29fce0c
Compare
Add the table validation added in bug #62978 (which fixed pg_insert, pg_update, pg_select, pg_delete) but missed pg_copy_from. Not updating pg_copy_to since it allows (query) for the table name per ext/pgsql/tests/06_bug73498.phpt.