Skip to content

Commit cb8d1fc

Browse files
author
Yasuo Ohgaki
committed
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.
2 parents e59143e + f718684 commit cb8d1fc

15 files changed

+184
-62
lines changed

Diff for: ext/pgsql/pgsql.c

+150-28
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,60 @@ static void _free_result(zend_rsrc_list_entry *rsrc TSRMLS_DC)
914914
}
915915
/* }}} */
916916

917+
918+
static int _php_pgsql_detect_identifier_escape(const char *identifier, size_t len)
919+
{
920+
size_t i;
921+
922+
/* Handle edge case. Cannot be a escaped string */
923+
if (len <= 2) {
924+
return FAILURE;
925+
}
926+
/* Detect double qoutes */
927+
if (identifier[0] == '"' && identifier[len-1] == '"') {
928+
/* Detect wrong format of " inside of escaped string */
929+
for (i = 1; i < len-1; i++) {
930+
if (identifier[i] == '"' && (identifier[++i] != '"' || i == len-1)) {
931+
return FAILURE;
932+
}
933+
}
934+
} else {
935+
return FAILURE;
936+
}
937+
/* Escaped properly */
938+
return SUCCESS;
939+
}
940+
941+
#if !HAVE_PQESCAPELITERAL
942+
/* {{{ _php_pgsql_escape_identifier
943+
* Since PQescapeIdentifier() is unavailable (PostgreSQL 9.0 <), idenfifers
944+
* should be escaped by pgsql module.
945+
* Note: this function does not care for encoding. Therefore users should not
946+
* use this with SJIS/BIG5 etc. (i.e. Encoding base injection may possible with
947+
* before PostgreSQL 9.0)
948+
*/
949+
static char *_php_pgsql_escape_identifier(const char *field, size_t field_len)
950+
{
951+
ulong field_escaped_len = field_len*2 + 3;
952+
ulong i, j = 0;
953+
char *field_escaped;
954+
955+
field_escaped = (char *)malloc(field_escaped_len);
956+
field_escaped[j++] = '"';
957+
for (i = 0; i < field_len; i++) {
958+
if (field[i] == '"') {
959+
field_escaped[j++] = '"';
960+
field_escaped[j++] = '"';
961+
} else {
962+
field_escaped[j++] = field[i];
963+
}
964+
}
965+
field_escaped[j++] = '"';
966+
field_escaped[j] = '\0';
967+
return field_escaped;
968+
}
969+
#endif
970+
917971
/* {{{ PHP_INI
918972
*/
919973
PHP_INI_BEGIN()
@@ -5016,8 +5070,9 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
50165070
{
50175071
PGresult *pg_result;
50185072
char *src, *tmp_name, *tmp_name2 = NULL;
5073+
char *escaped;
50195074
smart_str querystr = {0};
5020-
int new_len;
5075+
size_t new_len;
50215076
int i, num_rows;
50225077
zval *elem;
50235078

@@ -5039,20 +5094,29 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
50395094
"SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotnull, a.atthasdef, a.attndims, t.typtype = 'e' "
50405095
"FROM pg_class as c, pg_attribute a, pg_type t, pg_namespace n "
50415096
"WHERE a.attnum > 0 AND a.attrelid = c.oid AND c.relname = '");
5042-
tmp_name2 = php_addslashes(tmp_name2, strlen(tmp_name2), &new_len, 0 TSRMLS_CC);
5043-
smart_str_appendl(&querystr, tmp_name2, new_len);
5044-
5097+
escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1);
5098+
#if HAVE_PQESCAPE_CONN
5099+
new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL);
5100+
#else
5101+
new_len = PQescapeString(escaped, tmp_name2, strlen(tmp_name2));
5102+
#endif
5103+
smart_str_appends(&querystr, escaped);
5104+
efree(escaped);
5105+
50455106
smart_str_appends(&querystr, "' AND c.relnamespace = n.oid AND n.nspname = '");
5046-
tmp_name = php_addslashes(tmp_name, strlen(tmp_name), &new_len, 0 TSRMLS_CC);
5047-
smart_str_appendl(&querystr, tmp_name, new_len);
5107+
escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1);
5108+
#if HAVE_PQESCAPE_CONN
5109+
new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL);
5110+
#else
5111+
new_len = PQescapeString(escaped, tmp_name, strlen(tmp_name));
5112+
#endif
5113+
smart_str_appends(&querystr, escaped);
5114+
efree(escaped);
50485115

50495116
smart_str_appends(&querystr, "' AND a.atttypid = t.oid ORDER BY a.attnum;");
50505117
smart_str_0(&querystr);
5051-
5052-
efree(tmp_name2);
5053-
efree(tmp_name);
5054-
efree(src);
5055-
5118+
efree(src);
5119+
50565120
pg_result = PQexec(pg_link, querystr.c);
50575121
if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) {
50585122
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)
52755339
assert(Z_TYPE_P(src) == IS_STRING);
52765340
assert(should_free == 1 || should_free == 0);
52775341

5342+
smart_str_appendc(&str, 'E');
52785343
smart_str_appendc(&str, '\'');
52795344
smart_str_appendl(&str, Z_STRVAL_P(src), Z_STRLEN_P(src));
52805345
smart_str_appendc(&str, '\'');
@@ -5315,7 +5380,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
53155380
uint field_len = -1;
53165381
ulong num_idx = -1;
53175382
zval *meta, **def, **type, **not_null, **has_default, **is_enum, **val, *new_val;
5318-
int new_len, key_type, err = 0, skip_field;
5383+
int key_type, err = 0, skip_field;
53195384
php_pgsql_data_type data_type;
53205385

53215386
assert(pg_link != NULL);
@@ -5328,6 +5393,8 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
53285393
}
53295394
MAKE_STD_ZVAL(meta);
53305395
array_init(meta);
5396+
5397+
/* table_name is escaped by php_pgsql_meta_data */
53315398
if (php_pgsql_meta_data(pg_link, table_name, meta TSRMLS_CC) == FAILURE) {
53325399
zval_dtor(meta);
53335400
FREE_ZVAL(meta);
@@ -5540,15 +5607,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
55405607
}
55415608
else {
55425609
Z_TYPE_P(new_val) = IS_STRING;
5543-
#if HAVE_PQESCAPE
5610+
#if HAVE_PQESCAPE_CONN
55445611
{
55455612
char *tmp;
5546-
tmp = (char *)safe_emalloc(Z_STRLEN_PP(val), 2, 1);
5547-
Z_STRLEN_P(new_val) = (int)PQescapeString(tmp, Z_STRVAL_PP(val), Z_STRLEN_PP(val));
5613+
tmp = (char *)safe_emalloc(Z_STRLEN_PP(val), 2, 1);
5614+
Z_STRLEN_P(new_val) = (int)PQescapeStringConn(pg_link, tmp, Z_STRVAL_PP(val), Z_STRLEN_PP(val), NULL);
55485615
Z_STRVAL_P(new_val) = tmp;
55495616
}
55505617
#else
5551-
Z_STRVAL_P(new_val) = php_addslashes(Z_STRVAL_PP(val), Z_STRLEN_PP(val), &Z_STRLEN_P(new_val), 0 TSRMLS_CC);
5618+
Z_STRVAL_P(new_val) = (int)PQescapeString(Z_STRVAL_PP(val), Z_STRLEN_PP(val), &Z_STRLEN_P(new_val), 0 TSRMLS_CC);
55525619
#endif
55535620
php_pgsql_add_quotes(new_val, 1 TSRMLS_CC);
55545621
}
@@ -5834,6 +5901,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
58345901
else {
58355902
unsigned char *tmp;
58365903
size_t to_len;
5904+
smart_str s = {0};
58375905
#ifdef HAVE_PQESCAPE_BYTEA_CONN
58385906
tmp = PQescapeByteaConn(pg_link, Z_STRVAL_PP(val), Z_STRLEN_PP(val), &to_len);
58395907
#else
@@ -5845,7 +5913,11 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
58455913
memcpy(Z_STRVAL_P(new_val), tmp, to_len);
58465914
PQfreemem(tmp);
58475915
php_pgsql_add_quotes(new_val, 1 TSRMLS_CC);
5848-
5916+
smart_str_appendl(&s, Z_STRVAL_P(new_val), Z_STRLEN_P(new_val));
5917+
smart_str_0(&s);
5918+
efree(Z_STRVAL_P(new_val));
5919+
Z_STRVAL_P(new_val) = s.c;
5920+
Z_STRLEN_P(new_val) = s.len;
58495921
}
58505922
break;
58515923

@@ -5930,11 +6002,22 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
59306002
FREE_ZVAL(new_val);
59316003
break; /* break out for() */
59326004
}
6005+
/* If field is NULL and HAS DEFAULT, should be skipped */
59336006
if (!skip_field) {
5934-
/* If field is NULL and HAS DEFAULT, should be skipped */
5935-
field = php_addslashes(field, strlen(field), &new_len, 0 TSRMLS_CC);
5936-
add_assoc_zval(result, field, new_val);
5937-
efree(field);
6007+
char *escaped;
6008+
size_t new_len, field_len = strlen(field);
6009+
6010+
if (_php_pgsql_detect_identifier_escape(field, field_len) == SUCCESS) {
6011+
escaped = strndup(field, field_len);
6012+
} else {
6013+
#if HAVE_PQESCAPELITERAL
6014+
escaped = PQescapeIdentifier(pg_link, field, field_len);
6015+
#else
6016+
escaped = _php_pgsql_escape_identifier(field, field_len);
6017+
#endif
6018+
}
6019+
add_assoc_zval(result, escaped, new_val);
6020+
free(escaped);
59386021
}
59396022
} /* for */
59406023
zval_dtor(meta);
@@ -6009,6 +6092,45 @@ static int do_exec(smart_str *querystr, int expect, PGconn *pg_link, ulong opt T
60096092
return -1;
60106093
}
60116094

6095+
static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table)
6096+
{
6097+
char *table_copy, *escaped, *token, *tmp;
6098+
size_t len;
6099+
6100+
/* schame.table should be "schame"."table" */
6101+
table_copy = estrdup(table);
6102+
token = php_strtok_r(table_copy, ".", &tmp);
6103+
len = strlen(token);
6104+
if (_php_pgsql_detect_identifier_escape(token, len) == SUCCESS) {
6105+
escaped = strndup(token, len);
6106+
} else {
6107+
#if HAVE_PQESCAPELITERAL
6108+
escaped = PQescapeIdentifier(pg_link, token, len);
6109+
#else
6110+
escaped = _php_pgsql_escape_identifier(token, len);
6111+
#endif
6112+
}
6113+
smart_str_appends(querystr, escaped);
6114+
free(escaped);
6115+
if (tmp && *tmp) {
6116+
len = strlen(tmp);
6117+
/* "schema"."table" format */
6118+
if (_php_pgsql_detect_identifier_escape(tmp, len) == SUCCESS) {
6119+
escaped = strndup(tmp, len);
6120+
} else {
6121+
#if HAVE_PQESCAPELITERAL
6122+
escaped = PQescapeIdentifier(pg_link, tmp, len);
6123+
#else
6124+
escaped = _php_pgsql_escape_identifier(tmp, len);
6125+
#endif
6126+
}
6127+
smart_str_appendc(querystr, '.');
6128+
smart_str_appends(querystr, escaped);
6129+
free(escaped);
6130+
}
6131+
efree(table_copy);
6132+
}
6133+
60126134
/* {{{ php_pgsql_insert
60136135
*/
60146136
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
60286150

60296151
if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) {
60306152
smart_str_appends(&querystr, "INSERT INTO ");
6031-
smart_str_appends(&querystr, table);
6153+
build_tablename(&querystr, pg_link, table);
60326154
smart_str_appends(&querystr, " DEFAULT VALUES");
60336155

60346156
goto no_values;
@@ -6043,11 +6165,11 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
60436165
}
60446166
var_array = converted;
60456167
}
6046-
6168+
60476169
smart_str_appends(&querystr, "INSERT INTO ");
6048-
smart_str_appends(&querystr, table);
6170+
build_tablename(&querystr, pg_link, table);
60496171
smart_str_appends(&querystr, " (");
6050-
6172+
60516173
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(var_array), &pos);
60526174
while ((key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(var_array), &fld,
60536175
&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
62346356
}
62356357

62366358
smart_str_appends(&querystr, "UPDATE ");
6237-
smart_str_appends(&querystr, table);
6359+
build_tablename(&querystr, pg_link, table);
62386360
smart_str_appends(&querystr, " SET ");
62396361

62406362
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
63356457
}
63366458

63376459
smart_str_appends(&querystr, "DELETE FROM ");
6338-
smart_str_appends(&querystr, table);
6460+
build_tablename(&querystr, pg_link, table);
63396461
smart_str_appends(&querystr, " WHERE ");
63406462

63416463
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
64716593
}
64726594

64736595
smart_str_appends(&querystr, "SELECT * FROM ");
6474-
smart_str_appends(&querystr, table);
6596+
build_tablename(&querystr, pg_link, table);
64756597
smart_str_appends(&querystr, " WHERE ");
64766598

64776599
if (build_assignment_string(&querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1 TSRMLS_CC))

Diff for: ext/pgsql/tests/10pg_convert.phpt

+6-6
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ var_dump($converted);
2020
?>
2121
--EXPECT--
2222
array(3) {
23-
["num"]=>
23+
[""num""]=>
2424
string(4) "1234"
25-
["str"]=>
26-
string(5) "'AAA'"
27-
["bin"]=>
28-
string(5) "'BBB'"
29-
}
25+
[""str""]=>
26+
string(6) "E'AAA'"
27+
[""bin""]=>
28+
string(6) "E'BBB'"
29+
}

Diff for: ext/pgsql/tests/10pg_convert_9.phpt

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ var_dump($converted);
2121
?>
2222
--EXPECT--
2323
array(3) {
24-
["num"]=>
24+
[""num""]=>
2525
string(4) "1234"
26-
["str"]=>
27-
string(5) "'AAA'"
28-
["bin"]=>
29-
string(11) "'\\x424242'"
30-
}
26+
[""str""]=>
27+
string(6) "E'AAA'"
28+
[""bin""]=>
29+
string(12) "E'\\x424242'"
30+
}

Diff for: ext/pgsql/tests/12pg_insert.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ echo pg_insert($db, $table_name, $fields, PGSQL_DML_STRING)."\n";
2020
echo "Ok\n";
2121
?>
2222
--EXPECT--
23-
INSERT INTO php_pgsql_test (num,str,bin) VALUES (1234,'AAA','BBB');
24-
Ok
23+
INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'BBB');
24+
Ok

Diff for: ext/pgsql/tests/12pg_insert_9.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ echo pg_insert($db, $table_name, $fields, PGSQL_DML_STRING)."\n";
2222
echo "Ok\n";
2323
?>
2424
--EXPECT--
25-
INSERT INTO php_pgsql_test (num,str,bin) VALUES (1234,'AAA','\\x424242');
26-
Ok
25+
INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'\\x424242');
26+
Ok

Diff for: ext/pgsql/tests/13pg_select.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ array(1) {
3333
string(3) "BBB"
3434
}
3535
}
36-
SELECT * FROM php_pgsql_test WHERE num=1234;
36+
SELECT * FROM "php_pgsql_test" WHERE "num"=1234;
3737
Ok

Diff for: ext/pgsql/tests/13pg_select_9.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ array(1) {
3535
string(8) "\x424242"
3636
}
3737
}
38-
SELECT * FROM php_pgsql_test WHERE num=1234;
39-
Ok
38+
SELECT * FROM "php_pgsql_test" WHERE "num"=1234;
39+
Ok

Diff for: ext/pgsql/tests/14pg_update.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING)."\n";
2121
echo "Ok\n";
2222
?>
2323
--EXPECT--
24-
UPDATE php_pgsql_test SET num=1234,str='ABC',bin='XYZ' WHERE num=1234;
25-
Ok
24+
UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'XYZ' WHERE "num"=1234;
25+
Ok

Diff for: ext/pgsql/tests/14pg_update_9.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING)."\n";
2323
echo "Ok\n";
2424
?>
2525
--EXPECT--
26-
UPDATE php_pgsql_test SET num=1234,str='ABC',bin='\\x58595a' WHERE num=1234;
27-
Ok
26+
UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'\\x58595a' WHERE "num"=1234;
27+
Ok

0 commit comments

Comments
 (0)