]> granicus.if.org Git - php/commitdiff
Fixed bug #62978. pg_select()/etc may allow SQL injection when table name is user...
authorYasuo Ohgaki <yohgaki@php.net>
Mon, 5 Aug 2013 09:23:15 +0000 (18:23 +0900)
committerYasuo Ohgaki <yohgaki@php.net>
Mon, 5 Aug 2013 09:23:15 +0000 (18:23 +0900)
15 files changed:
ext/pgsql/pgsql.c
ext/pgsql/tests/10pg_convert.phpt
ext/pgsql/tests/10pg_convert_9.phpt
ext/pgsql/tests/12pg_insert.phpt
ext/pgsql/tests/12pg_insert_9.phpt
ext/pgsql/tests/13pg_select.phpt
ext/pgsql/tests/13pg_select_9.phpt
ext/pgsql/tests/14pg_update.phpt
ext/pgsql/tests/14pg_update_9.phpt
ext/pgsql/tests/bug47199.phpt
ext/pgsql/tests/bug64609.phpt
ext/pgsql/tests/config.inc
ext/pgsql/tests/pg_delete_001.phpt
ext/pgsql/tests/pg_insert_001.phpt
ext/pgsql/tests/pg_update_001.phpt

index 796d8835c7ff19ac3ab4c3662a6814481017cdc4..ded4a62c584d1653d753ee35bbcad51532922f2a 100644 (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()
@@ -5015,8 +5069,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;
        
@@ -5038,20 +5093,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);
@@ -5274,6 +5338,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, '\'');
@@ -5314,7 +5379,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);
@@ -5327,6 +5392,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);
@@ -5539,15 +5606,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);
                                                }
@@ -5833,6 +5900,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
@@ -5844,7 +5912,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;
                                                
@@ -5929,11 +6001,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);
@@ -6008,6 +6091,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)
@@ -6027,7 +6149,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;
@@ -6042,11 +6164,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_EXISTANT) {
@@ -6233,7 +6355,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))
@@ -6334,7 +6456,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))
@@ -6470,7 +6592,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))
index fde4c67d3744c2d5bd717de2268be9cc5dcffefe..adc2756e25cef16ac26a716e261eec6d6b4fff49 100644 (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'"
+}
\ No newline at end of file
index bb2e7e6d2a317c182e2d3b4d8ba18a3bb66aeb32..827c96250db8d6382cc1cd3293a63548ae646ebb 100644 (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'"
+}
\ No newline at end of file
index 66304944b0870d26cd0f93c4b209b80e57298abd..9fd0dd1e3b9b02b9e30bf04d815fd181faade0b6 100644 (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
\ No newline at end of file
index 8afae0df91d7ffc26a8f7961164f80164254aa4f..329364ad6450f27db89a9b45c75c054169bd50c6 100644 (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
\ No newline at end of file
index f1504a8b17996ef0dac15202834649edf1ca56b2..db2ca06bf869a5d3138f366e7b50485e04f9065d 100644 (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
index 422c461b6096df03e4279c7886ccc4d96ccee985..67adc9d21d3ebf8330f0981b5f58901b15ee129f 100644 (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
\ No newline at end of file
index 3260f2b73e1e372b78eb758e02e351a8fdb4f863..347cac94470dc6fad4f1ccdfdbc5bba7c51e18ce 100644 (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
\ No newline at end of file
index bc5cf673e327e89a10e00113486f4377c5f772df..e766c1f3807c299fe1effe3bd929d1c9da9c1f3b 100644 (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
\ No newline at end of file
index 5bfac0b1bb4bcc6001d28f105dc4013a004cde42..faa787fd544c6b0cd21453905c0cbf90a599c3b0 100644 (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) {
index 0df63012dafe510cbcc8ffcf27f64d0ca906c899..72fac7648efd9191534258fd59ecd99497e39681 100644 (file)
@@ -25,6 +25,6 @@ var_dump($converted);
 ?>
 --EXPECT--
 array(1) {
-  ["a"]=>
-  string(4) "'ok'"
-}
+  [""a""]=>
+  string(5) "E'ok'"
+}
\ No newline at end of file
index 2b5f05a71dab9a4e736b9b54f2f4ae6f42802a5f..d4bbb33824912e90301b21b4fcb55290f563320f 100644 (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
 
index abb65be142f7aa37d9012e57c61e31645f4fec1b..a98c95dc4cb589bc739a1949042dc3d999b9ef63 100644 (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) {
index 7d27219187d217d5f4ad71b674162d3a8f1b5794..626d4d0f82cfcffd1163bbd8dca83926e55f2adf 100644 (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) {
index 95fa6925684762bdb11aa6b1f35ce8a756bdaa36..60db35c157b3d6da27059560a3f03f31896c5da8 100644 (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"