Skip to content

Commit

Permalink
security fix - by default 'local infile' is disabled:
Browse files Browse the repository at this point in the history
- set default for mysqli.allow_local_infile=0
- explicitly disable PDO::MYSQL_ATTR_LOCAL_INFILE in case of lack of driver options
- add getAttribute support for PDO::MYSQL_ATTR_LOCAL_INFILE
- update existing tests where needed
- add new tests [checking default value and setting on] the 'local infile' in ext/mysqli and ext/pdo_mysql
  • Loading branch information
marinesovitch committed Feb 11, 2019
1 parent 65d8183 commit 2eaabf0
Show file tree
Hide file tree
Showing 18 changed files with 148 additions and 6 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? 2019, PHP 7.1.27

- MySQL
. Disabled LOCAL INFILE by default, can be enabled using php.ini directive
mysqli.allow_local_infile for mysqli, or PDO::MYSQL_ATTR_LOCAL_INFILE
attribute for pdo_mysql. (Darek Slusarczyk)

10 Jan 2019, PHP 7.1.26

Expand Down
4 changes: 2 additions & 2 deletions ext/mysqli/mysqli.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("mysqli.default_socket", NULL, PHP_INI_ALL, OnUpdateStringUnempty, default_socket, zend_mysqli_globals, mysqli_globals)
#endif
STD_PHP_INI_BOOLEAN("mysqli.reconnect", "0", PHP_INI_SYSTEM, OnUpdateLong, reconnect, zend_mysqli_globals, mysqli_globals)
STD_PHP_INI_BOOLEAN("mysqli.allow_local_infile", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_local_infile, zend_mysqli_globals, mysqli_globals)
STD_PHP_INI_BOOLEAN("mysqli.allow_local_infile", "0", PHP_INI_SYSTEM, OnUpdateLong, allow_local_infile, zend_mysqli_globals, mysqli_globals)
PHP_INI_END()
/* }}} */

Expand All @@ -549,7 +549,7 @@ static PHP_GINIT_FUNCTION(mysqli)
mysqli_globals->reconnect = 0;
mysqli_globals->report_mode = 0;
mysqli_globals->report_ht = 0;
mysqli_globals->allow_local_infile = 1;
mysqli_globals->allow_local_infile = 0;
#ifdef HAVE_EMBEDDED_MYSQLI
mysqli_globals->embedded = 1;
#else
Expand Down
2 changes: 2 additions & 0 deletions ext/mysqli/tests/061.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ if ($msg = check_local_infile_support($link, $engine))

mysqli_close($link);
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require_once("connect.inc");
Expand Down
2 changes: 2 additions & 0 deletions ext/mysqli/tests/bug36745.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Bug #36745 (LOAD DATA LOCAL INFILE doesn't return correct error message)
require_once('skipif.inc');
require_once('skipifconnectfailure.inc');
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require_once("connect.inc");
Expand Down
2 changes: 2 additions & 0 deletions ext/mysqli/tests/bug53503.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ if ($msg = check_local_infile_support($link, $engine))
mysqli_close($link);

?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require_once("connect.inc");
Expand Down
3 changes: 3 additions & 0 deletions ext/mysqli/tests/bug68077.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ if ($msg = check_local_infile_support($link, $engine))
mysqli_close($link);
?>
--INI--
mysqli.allow_local_infile=1
mysqli.allow_persistent=1
mysqli.max_persistent=1
open_basedir=
--FILE--
<?php
Expand Down
2 changes: 2 additions & 0 deletions ext/mysqli/tests/mysqli_constants.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require_once('skipif.inc');
require_once('skipifemb.inc');
require_once('skipifconnectfailure.inc');
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require("connect.inc");
Expand Down
1 change: 1 addition & 0 deletions ext/mysqli/tests/mysqli_get_client_stats.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if (!function_exists('mysqli_get_client_stats')) {
--INI--
mysqlnd.collect_statistics=1
mysqlnd.collect_memory_statistics=1
mysqli.allow_local_infile=1
--FILE--
<?php
/*
Expand Down
2 changes: 2 additions & 0 deletions ext/mysqli/tests/mysqli_info.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require_once('skipif.inc');
require_once('skipifemb.inc');
require_once('skipifconnectfailure.inc');
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require_once("connect.inc");
Expand Down
26 changes: 26 additions & 0 deletions ext/mysqli/tests/mysqli_local_infile_default_off.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
ensure default for local infile is off
--SKIPIF--
<?php
require_once('skipif.inc');
require_once('skipifconnectfailure.inc');
?>
--FILE--
<?php
require_once("connect.inc");

$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
$res = mysqli_query($link, 'SHOW VARIABLES LIKE "local_infile"');
$row = mysqli_fetch_assoc($res);
echo "server: ", $row['Value'], "\n";
mysqli_free_result($res);
mysqli_close($link);

echo "connector: ", ini_get("mysqli.allow_local_infile"), "\n";

print "done!\n";
?>
--EXPECTF--
server: %s
connector: 0
done!
28 changes: 28 additions & 0 deletions ext/mysqli/tests/mysqli_local_infile_set_on.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
enable local infile
--SKIPIF--
<?php
require_once('skipif.inc');
require_once('skipifconnectfailure.inc');
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
require_once("connect.inc");

$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
$res = mysqli_query($link, 'SHOW VARIABLES LIKE "local_infile"');
$row = mysqli_fetch_assoc($res);
echo "server: ", $row['Value'], "\n";
mysqli_free_result($res);
mysqli_close($link);

echo "connector: ", ini_get("mysqli.allow_local_infile"), "\n";

print "done!\n";
?>
--EXPECTF--
server: %s
connector: 1
done!
2 changes: 2 additions & 0 deletions ext/mysqli/tests/mysqli_real_connect.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require_once('skipif.inc');
require_once('skipifemb.inc');
require_once('skipifconnectfailure.inc');
?>
--INI--
mysqli.allow_local_infile=1
--FILE--
<?php
include("connect.inc");
Expand Down
1 change: 1 addition & 0 deletions ext/mysqli/tests/mysqli_real_connect_pconn.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ if (!$IS_MYSQLND)
die("skip mysqlnd only test");
?>
--INI--
mysqli.allow_local_infile=1
mysqli.allow_persistent=1
mysqli.max_persistent=10
--FILE--
Expand Down
3 changes: 2 additions & 1 deletion ext/mysqlnd/mysqlnd_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,8 @@ MYSQLND_METHOD(mysqlnd_conn_data, get_updated_connect_flags)(MYSQLND_CONN_DATA *
MYSQLND_VIO * vio = conn->vio;

DBG_ENTER("mysqlnd_conn_data::get_updated_connect_flags");
/* we allow load data local infile by default */
/* allow CLIENT_LOCAL_FILES capability, although extensions basing on mysqlnd
shouldn't allow 'load data local infile' by default due to security issues */
mysql_flags |= MYSQLND_CAPABILITIES;

mysql_flags |= conn->options->flags; /* use the flags from set_client_option() */
Expand Down
15 changes: 15 additions & 0 deletions ext/pdo_mysql/mysql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,12 @@ static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_
case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
ZVAL_LONG(return_value, H->max_buffer_size);
break;
#else
case PDO_MYSQL_ATTR_LOCAL_INFILE:
ZVAL_BOOL(
return_value,
(H->server->data->options->flags & CLIENT_LOCAL_FILES) == CLIENT_LOCAL_FILES);
break;
#endif

default:
Expand Down Expand Up @@ -746,6 +752,15 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
}
}
#endif
} else {
#if defined(MYSQL_OPT_LOCAL_INFILE) || defined(PDO_USE_MYSQLND)
// in case there are no driver options disable 'local infile' explicitly
zend_long local_infile = 0;
if (mysql_options(H->server, MYSQL_OPT_LOCAL_INFILE, (const char *)&local_infile)) {
pdo_mysql_error(dbh);
goto cleanup;
}
#endif
}

Expand Down
5 changes: 2 additions & 3 deletions ext/pdo_mysql/tests/pdo_mysql___construct_options.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ MySQLPDOTest::skip();
try {
$db = new PDO($dsn, $user, $pass, array($option => $value));
if (!is_object($db) || ($value !== ($tmp = @$db->getAttribute($option))))
printf("[%03d] Execting '%s'/%s got '%s'/%s' for options '%s'\n",
printf("[%03d] Expecting '%s'/%s got '%s'/%s' for options '%s'\n",
$offset,
$value, gettype($value),
$tmp, gettype($tmp),
Expand Down Expand Up @@ -172,8 +172,7 @@ MySQLPDOTest::skip();
[016] PDO::MYSQL_ATTR_DIRECT_QUERY should be on
[017] PDO::ATTR_EMULATE_PREPARES should be off
[018] PDO::MYSQL_ATTR_DIRECT_QUERY should be off
[021] Execting '1'/boolean got ''/boolean' for options 'PDO::MYSQL_ATTR_LOCAL_INFILE'
[023] Execting 'SET @a=1'/string got ''/boolean' for options 'PDO::MYSQL_ATTR_INIT_COMMAND'
[023] Expecting 'SET @a=1'/string got ''/boolean' for options 'PDO::MYSQL_ATTR_INIT_COMMAND'
[024] SQLSTATE[42000] [1065] Query was empty
[025] SQLSTATE[42S02] [1146] Table '%s.nonexistent' doesn't exist
done!
26 changes: 26 additions & 0 deletions ext/pdo_mysql/tests/pdo_mysql_local_infile_default_off.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
ensure default for local infile is off
--SKIPIF--
<?php
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
MySQLPDOTest::skip();
if (!MYSQLPDOTest::isPDOMySQLnd())
die("skip mysqlnd only test");
?>
--FILE--
<?php
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'config.inc');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');

$dsn = MySQLPDOTest::getDSN();
$user = PDO_MYSQL_TEST_USER;
$pass = PDO_MYSQL_TEST_PASS;

$db = new PDO($dsn, $user, $pass);
echo var_export($db->getAttribute(PDO::MYSQL_ATTR_LOCAL_INFILE)), "\n";
echo "done!\n";
?>
--EXPECTF--
false
done!
26 changes: 26 additions & 0 deletions ext/pdo_mysql/tests/pdo_mysql_local_infile_set_on.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
enable local infile
--SKIPIF--
<?php
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
MySQLPDOTest::skip();
if (!MYSQLPDOTest::isPDOMySQLnd())
die("skip mysqlnd only test");
?>
--FILE--
<?php
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'config.inc');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');

$dsn = MySQLPDOTest::getDSN();
$user = PDO_MYSQL_TEST_USER;
$pass = PDO_MYSQL_TEST_PASS;

$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_LOCAL_INFILE => true));
echo var_export($db->getAttribute(PDO::MYSQL_ATTR_LOCAL_INFILE)), "\n";
echo "done!\n";
?>
--EXPECTF--
true
done!

0 comments on commit 2eaabf0

Please sign in to comment.