Merge branch 'PHP-5.4' into PHP-5.5

* PHP-5.4:
  Fixed bug #62978. pg_select()/etc may allow SQL injection when table name is user parameter, users are able to control table names.
This commit is contained in:
Yasuo Ohgaki 2013-08-05 18:24:07 +09:00
commit cb8d1fc7f9
15 changed files with 184 additions and 62 deletions

View File

@ -914,6 +914,60 @@ static void _free_result(zend_rsrc_list_entry *rsrc TSRMLS_DC)
}
/* }}} */
static int _php_pgsql_detect_identifier_escape(const char *identifier, size_t len)
{
size_t i;
/* Handle edge case. Cannot be a escaped string */
if (len <= 2) {
return FAILURE;
}
/* Detect double qoutes */
if (identifier[0] == '"' && identifier[len-1] == '"') {
/* Detect wrong format of " inside of escaped string */
for (i = 1; i < len-1; i++) {
if (identifier[i] == '"' && (identifier[++i] != '"' || i == len-1)) {
return FAILURE;
}
}
} else {
return FAILURE;
}
/* Escaped properly */
return SUCCESS;
}
#if !HAVE_PQESCAPELITERAL
/* {{{ _php_pgsql_escape_identifier
* Since PQescapeIdentifier() is unavailable (PostgreSQL 9.0 <), idenfifers
* should be escaped by pgsql module.
* Note: this function does not care for encoding. Therefore users should not
* use this with SJIS/BIG5 etc. (i.e. Encoding base injection may possible with
* before PostgreSQL 9.0)
*/
static char *_php_pgsql_escape_identifier(const char *field, size_t field_len)
{
ulong field_escaped_len = field_len*2 + 3;
ulong i, j = 0;
char *field_escaped;
field_escaped = (char *)malloc(field_escaped_len);
field_escaped[j++] = '"';
for (i = 0; i < field_len; i++) {
if (field[i] == '"') {
field_escaped[j++] = '"';
field_escaped[j++] = '"';
} else {
field_escaped[j++] = field[i];
}
}
field_escaped[j++] = '"';
field_escaped[j] = '\0';
return field_escaped;
}
#endif
/* {{{ PHP_INI
*/
PHP_INI_BEGIN()
@ -5016,8 +5070,9 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
{
PGresult *pg_result;
char *src, *tmp_name, *tmp_name2 = NULL;
char *escaped;
smart_str querystr = {0};
int new_len;
size_t new_len;
int i, num_rows;
zval *elem;
@ -5039,20 +5094,29 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
"SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotnull, a.atthasdef, a.attndims, t.typtype = 'e' "
"FROM pg_class as c, pg_attribute a, pg_type t, pg_namespace n "
"WHERE a.attnum > 0 AND a.attrelid = c.oid AND c.relname = '");
tmp_name2 = php_addslashes(tmp_name2, strlen(tmp_name2), &new_len, 0 TSRMLS_CC);
smart_str_appendl(&querystr, tmp_name2, new_len);
escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1);
#if HAVE_PQESCAPE_CONN
new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL);
#else
new_len = PQescapeString(escaped, tmp_name2, strlen(tmp_name2));
#endif
smart_str_appends(&querystr, escaped);
efree(escaped);
smart_str_appends(&querystr, "' AND c.relnamespace = n.oid AND n.nspname = '");
tmp_name = php_addslashes(tmp_name, strlen(tmp_name), &new_len, 0 TSRMLS_CC);
smart_str_appendl(&querystr, tmp_name, new_len);
escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1);
#if HAVE_PQESCAPE_CONN
new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL);
#else
new_len = PQescapeString(escaped, tmp_name, strlen(tmp_name));
#endif
smart_str_appends(&querystr, escaped);
efree(escaped);
smart_str_appends(&querystr, "' AND a.atttypid = t.oid ORDER BY a.attnum;");
smart_str_0(&querystr);
efree(tmp_name2);
efree(tmp_name);
efree(src);
efree(src);
pg_result = PQexec(pg_link, querystr.c);
if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Table '%s' doesn't exists", table_name);
@ -5275,6 +5339,7 @@ static int php_pgsql_add_quotes(zval *src, zend_bool should_free TSRMLS_DC)
assert(Z_TYPE_P(src) == IS_STRING);
assert(should_free == 1 || should_free == 0);
smart_str_appendc(&str, 'E');
smart_str_appendc(&str, '\'');
smart_str_appendl(&str, Z_STRVAL_P(src), Z_STRLEN_P(src));
smart_str_appendc(&str, '\'');
@ -5315,7 +5380,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
uint field_len = -1;
ulong num_idx = -1;
zval *meta, **def, **type, **not_null, **has_default, **is_enum, **val, *new_val;
int new_len, key_type, err = 0, skip_field;
int key_type, err = 0, skip_field;
php_pgsql_data_type data_type;
assert(pg_link != NULL);
@ -5328,6 +5393,8 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
}
MAKE_STD_ZVAL(meta);
array_init(meta);
/* table_name is escaped by php_pgsql_meta_data */
if (php_pgsql_meta_data(pg_link, table_name, meta TSRMLS_CC) == FAILURE) {
zval_dtor(meta);
FREE_ZVAL(meta);
@ -5540,15 +5607,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
}
else {
Z_TYPE_P(new_val) = IS_STRING;
#if HAVE_PQESCAPE
#if HAVE_PQESCAPE_CONN
{
char *tmp;
tmp = (char *)safe_emalloc(Z_STRLEN_PP(val), 2, 1);
Z_STRLEN_P(new_val) = (int)PQescapeString(tmp, Z_STRVAL_PP(val), Z_STRLEN_PP(val));
tmp = (char *)safe_emalloc(Z_STRLEN_PP(val), 2, 1);
Z_STRLEN_P(new_val) = (int)PQescapeStringConn(pg_link, tmp, Z_STRVAL_PP(val), Z_STRLEN_PP(val), NULL);
Z_STRVAL_P(new_val) = tmp;
}
#else
Z_STRVAL_P(new_val) = php_addslashes(Z_STRVAL_PP(val), Z_STRLEN_PP(val), &Z_STRLEN_P(new_val), 0 TSRMLS_CC);
Z_STRVAL_P(new_val) = (int)PQescapeString(Z_STRVAL_PP(val), Z_STRLEN_PP(val), &Z_STRLEN_P(new_val), 0 TSRMLS_CC);
#endif
php_pgsql_add_quotes(new_val, 1 TSRMLS_CC);
}
@ -5834,6 +5901,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
else {
unsigned char *tmp;
size_t to_len;
smart_str s = {0};
#ifdef HAVE_PQESCAPE_BYTEA_CONN
tmp = PQescapeByteaConn(pg_link, Z_STRVAL_PP(val), Z_STRLEN_PP(val), &to_len);
#else
@ -5845,7 +5913,11 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
memcpy(Z_STRVAL_P(new_val), tmp, to_len);
PQfreemem(tmp);
php_pgsql_add_quotes(new_val, 1 TSRMLS_CC);
smart_str_appendl(&s, Z_STRVAL_P(new_val), Z_STRLEN_P(new_val));
smart_str_0(&s);
efree(Z_STRVAL_P(new_val));
Z_STRVAL_P(new_val) = s.c;
Z_STRLEN_P(new_val) = s.len;
}
break;
@ -5930,11 +6002,22 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
FREE_ZVAL(new_val);
break; /* break out for() */
}
/* If field is NULL and HAS DEFAULT, should be skipped */
if (!skip_field) {
/* If field is NULL and HAS DEFAULT, should be skipped */
field = php_addslashes(field, strlen(field), &new_len, 0 TSRMLS_CC);
add_assoc_zval(result, field, new_val);
efree(field);
char *escaped;
size_t new_len, field_len = strlen(field);
if (_php_pgsql_detect_identifier_escape(field, field_len) == SUCCESS) {
escaped = strndup(field, field_len);
} else {
#if HAVE_PQESCAPELITERAL
escaped = PQescapeIdentifier(pg_link, field, field_len);
#else
escaped = _php_pgsql_escape_identifier(field, field_len);
#endif
}
add_assoc_zval(result, escaped, new_val);
free(escaped);
}
} /* for */
zval_dtor(meta);
@ -6009,6 +6092,45 @@ static int do_exec(smart_str *querystr, int expect, PGconn *pg_link, ulong opt T
return -1;
}
static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table)
{
char *table_copy, *escaped, *token, *tmp;
size_t len;
/* schame.table should be "schame"."table" */
table_copy = estrdup(table);
token = php_strtok_r(table_copy, ".", &tmp);
len = strlen(token);
if (_php_pgsql_detect_identifier_escape(token, len) == SUCCESS) {
escaped = strndup(token, len);
} else {
#if HAVE_PQESCAPELITERAL
escaped = PQescapeIdentifier(pg_link, token, len);
#else
escaped = _php_pgsql_escape_identifier(token, len);
#endif
}
smart_str_appends(querystr, escaped);
free(escaped);
if (tmp && *tmp) {
len = strlen(tmp);
/* "schema"."table" format */
if (_php_pgsql_detect_identifier_escape(tmp, len) == SUCCESS) {
escaped = strndup(tmp, len);
} else {
#if HAVE_PQESCAPELITERAL
escaped = PQescapeIdentifier(pg_link, tmp, len);
#else
escaped = _php_pgsql_escape_identifier(tmp, len);
#endif
}
smart_str_appendc(querystr, '.');
smart_str_appends(querystr, escaped);
free(escaped);
}
efree(table_copy);
}
/* {{{ php_pgsql_insert
*/
PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var_array, ulong opt, char **sql TSRMLS_DC)
@ -6028,7 +6150,7 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) {
smart_str_appends(&querystr, "INSERT INTO ");
smart_str_appends(&querystr, table);
build_tablename(&querystr, pg_link, table);
smart_str_appends(&querystr, " DEFAULT VALUES");
goto no_values;
@ -6043,11 +6165,11 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
}
var_array = converted;
}
smart_str_appends(&querystr, "INSERT INTO ");
smart_str_appends(&querystr, table);
build_tablename(&querystr, pg_link, table);
smart_str_appends(&querystr, " (");
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(var_array), &pos);
while ((key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(var_array), &fld,
&fld_len, &num_idx, 0, &pos)) != HASH_KEY_NON_EXISTENT) {
@ -6234,7 +6356,7 @@ PHP_PGSQL_API int php_pgsql_update(PGconn *pg_link, const char *table, zval *var
}
smart_str_appends(&querystr, "UPDATE ");
smart_str_appends(&querystr, table);
build_tablename(&querystr, pg_link, table);
smart_str_appends(&querystr, " SET ");
if (build_assignment_string(&querystr, Z_ARRVAL_P(var_array), 0, ",", 1 TSRMLS_CC))
@ -6335,7 +6457,7 @@ PHP_PGSQL_API int php_pgsql_delete(PGconn *pg_link, const char *table, zval *ids
}
smart_str_appends(&querystr, "DELETE FROM ");
smart_str_appends(&querystr, table);
build_tablename(&querystr, pg_link, table);
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(&querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1 TSRMLS_CC))
@ -6471,7 +6593,7 @@ PHP_PGSQL_API int php_pgsql_select(PGconn *pg_link, const char *table, zval *ids
}
smart_str_appends(&querystr, "SELECT * FROM ");
smart_str_appends(&querystr, table);
build_tablename(&querystr, pg_link, table);
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(&querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1 TSRMLS_CC))

View File

@ -20,10 +20,10 @@ var_dump($converted);
?>
--EXPECT--
array(3) {
["num"]=>
[""num""]=>
string(4) "1234"
["str"]=>
string(5) "'AAA'"
["bin"]=>
string(5) "'BBB'"
}
[""str""]=>
string(6) "E'AAA'"
[""bin""]=>
string(6) "E'BBB'"
}

View File

@ -21,10 +21,10 @@ var_dump($converted);
?>
--EXPECT--
array(3) {
["num"]=>
[""num""]=>
string(4) "1234"
["str"]=>
string(5) "'AAA'"
["bin"]=>
string(11) "'\\x424242'"
}
[""str""]=>
string(6) "E'AAA'"
[""bin""]=>
string(12) "E'\\x424242'"
}

View File

@ -20,5 +20,5 @@ echo pg_insert($db, $table_name, $fields, PGSQL_DML_STRING)."\n";
echo "Ok\n";
?>
--EXPECT--
INSERT INTO php_pgsql_test (num,str,bin) VALUES (1234,'AAA','BBB');
Ok
INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'BBB');
Ok

View File

@ -22,5 +22,5 @@ echo pg_insert($db, $table_name, $fields, PGSQL_DML_STRING)."\n";
echo "Ok\n";
?>
--EXPECT--
INSERT INTO php_pgsql_test (num,str,bin) VALUES (1234,'AAA','\\x424242');
Ok
INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'\\x424242');
Ok

View File

@ -33,5 +33,5 @@ array(1) {
string(3) "BBB"
}
}
SELECT * FROM php_pgsql_test WHERE num=1234;
SELECT * FROM "php_pgsql_test" WHERE "num"=1234;
Ok

View File

@ -35,5 +35,5 @@ array(1) {
string(8) "\x424242"
}
}
SELECT * FROM php_pgsql_test WHERE num=1234;
Ok
SELECT * FROM "php_pgsql_test" WHERE "num"=1234;
Ok

View File

@ -21,5 +21,5 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING)."\n";
echo "Ok\n";
?>
--EXPECT--
UPDATE php_pgsql_test SET num=1234,str='ABC',bin='XYZ' WHERE num=1234;
Ok
UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'XYZ' WHERE "num"=1234;
Ok

View File

@ -23,5 +23,5 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING)."\n";
echo "Ok\n";
?>
--EXPECT--
UPDATE php_pgsql_test SET num=1234,str='ABC',bin='\\x58595a' WHERE num=1234;
Ok
UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'\\x58595a' WHERE "num"=1234;
Ok

View File

@ -52,8 +52,8 @@ array(2) {
string(1) "2"
}
}
DELETE FROM test_47199 WHERE null_field IS NULL AND not_null_field=2;
UPDATE test_47199 SET null_field=NULL,not_null_field=0 WHERE not_null_field=1 AND null_field IS NULL;
DELETE FROM "test_47199" WHERE "null_field" IS NULL AND "not_null_field"=2;
UPDATE "test_47199" SET "null_field"=NULL,"not_null_field"=0 WHERE "not_null_field"=1 AND "null_field" IS NULL;
array(1) {
[0]=>
array(2) {

View File

@ -25,6 +25,6 @@ var_dump($converted);
?>
--EXPECT--
array(1) {
["a"]=>
string(4) "'ok'"
}
[""a""]=>
string(5) "E'ok'"
}

View File

@ -2,7 +2,7 @@
// These vars are used to connect db and create test table.
// values can be set to meet your environment
$conn_str = "host=localhost dbname=test"; // connection string
$conn_str = "host=localhost dbname=test port=5432"; // connection string
$table_name = "php_pgsql_test"; // test table that should be exist
$num_test_record = 1000; // Number of records to create

View File

@ -45,8 +45,8 @@ pg_query('DROP SCHEMA phptests');
?>
--EXPECTF--
string(37) "DELETE FROM foo WHERE id=1 AND id2=2;"
string(46) "DELETE FROM phptests.foo WHERE id=2 AND id2=3;"
string(43) "DELETE FROM "foo" WHERE "id"=1 AND "id2"=2;"
string(54) "DELETE FROM "phptests"."foo" WHERE "id"=2 AND "id2"=3;"
array(2) {
[0]=>
array(2) {

View File

@ -28,7 +28,7 @@ pg_query('DROP SCHEMA phptests');
--EXPECTF--
Warning: pg_insert(): Table 'foo' doesn't exists in %s on line %d
string(47) "INSERT INTO phptests.foo (id,id2) VALUES (1,2);"
string(55) "INSERT INTO "phptests"."foo" ("id","id2") VALUES (1,2);"
array(1) {
[0]=>
array(2) {

View File

@ -35,8 +35,8 @@ pg_query('DROP SCHEMA phptests');
?>
--EXPECT--
string(32) "UPDATE foo SET id=10 WHERE id=1;"
string(43) "UPDATE phptests.foo SET id=100 WHERE id2=2;"
string(38) "UPDATE "foo" SET "id"=10 WHERE "id"=1;"
string(51) "UPDATE "phptests"."foo" SET "id"=100 WHERE "id2"=2;"
array(2) {
["id"]=>
string(2) "10"