[TikiWiki-commits] [Git][tikiwiki/tiki][27.x] [FIX] SQL injection in installer DB-user provisioning
"Alfred Syatsukwa \(@alfredsyatsukwa\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a8659c850136_3818cc4862f0@gitlab-sidekiq-low-urgency-cpu-bound-v2-77d778bd8d-x786w.mail> |
Alfred Syatsukwa pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki
Commits:
9946b850 by Alfred Syatsukwa at 2026-08-20T01:26:55+00:00
[FIX] SQL injection in installer DB-user provisioning
---
* [FIX] SQL injection in installer DB-user provisioning
---
See merge request tikiwiki/tiki!11001
(cherry picked from commit 8413104e5c595560ffd29f5eb01edfb290ebb784)
See merge request tikiwiki/tiki!11002
- - - - -
3 changed files:
- installer/installlib.php
- installer/tiki-installer.php
- templates/tiki-install.tpl
Changes:
=====================================
installer/installlib.php
=====================================
@@ -78,6 +78,80 @@ function write_local_php($host_tiki, $user_tiki, $pass_tiki, $dbs_tiki, $client_
}
}
+/**
+ * Validate identifiers used during installer database setup.
+ *
+ * Keep the accepted character set aligned with the installer UI to avoid
+ * surprising users while still rejecting characters that can alter SQL
+ * structure or PHP configuration output.
+ */
+function installer_is_valid_mysql_identifier($value)
+{
+ return is_string($value) && preg_match('/^[A-Za-z0-9$_-]+$/', $value) === 1;
+}
+
+/**
+ * Installer-created database users should only be tied to localhost when the
+ * installation itself comes from localhost. Otherwise keep the existing
+ * wildcard behaviour for remote setup flows.
+ */
+function installer_normalize_mysql_grant_host($host)
+{
+ if (preg_match('/^(127\.0\.\d{1,3}\.\d{1,3}|localhost)(:\d+)?$/', $host)) {
+ return 'localhost';
+ }
+
+ return '%';
+}
+
+/**
+ * Build the CREATE DATABASE statement used during installation.
+ *
+ * Database names are SQL identifiers, so they must be validated separately
+ * before they are interpolated into DDL.
+ */
+function installer_build_create_database_sql($dbname)
+{
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ throw new InvalidArgumentException('Invalid database name.');
+ }
+
+ return "CREATE DATABASE IF NOT EXISTS `$dbname` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;";
+}
+
+/**
+ * Build the ALTER DATABASE statement used to normalize the default charset.
+ */
+function installer_build_alter_database_charset_sql($dbname)
+{
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ throw new InvalidArgumentException('Invalid database name.');
+ }
+
+ return "ALTER DATABASE `$dbname` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
+}
+
+/**
+ * Build the GRANT statement used when the installer creates a dedicated
+ * database user.
+ *
+ * The database name is an SQL identifier, while the user, host pattern, and
+ * password are SQL string literals that must go through qstr()/PDO::quote().
+ */
+function installer_build_grant_database_privileges_sql($dbTiki, $dbname, $user, $host, $pass)
+{
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ throw new InvalidArgumentException('Invalid database name.');
+ }
+
+ $quotedUser = $dbTiki->qstr($user);
+ $quotedHost = $dbTiki->qstr(installer_normalize_mysql_grant_host($host));
+ $quotedPass = $dbTiki->qstr($pass);
+
+ return "GRANT ALL PRIVILEGES ON `$dbname`.* TO "
+ . $quotedUser . '@' . $quotedHost . " IDENTIFIED BY " . $quotedPass . ';';
+}
+
/**
* @param string $domain
* @return string
@@ -410,20 +484,19 @@ function initTikiDB(&$api, $host, $user, $pass, $dbname, $client_charset, &$dbTi
}
$dbcon = ! empty($dbTiki);
// First check that suggested database name will not cause issues
- $dbname_clean = preg_replace('/[^a-zA-Z0-9$_-]/', "", $dbname);
- if ($dbname_clean != $dbname) {
- Feedback::error(tra("Some invalid characters were detected in database name. Please use alphanumeric characters (A-Z a-z 0-9) or underscore (_) or hyphen (-).", '', false, [$dbname_clean]));
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ Feedback::error(tra("Invalid database name. Use only letters, numbers, dollar signs ($), underscores (_), or hyphens (-)."));
$dbcon = false;
} elseif ($dbcon) {
$error = '';
- $sql = "CREATE DATABASE IF NOT EXISTS `$dbname_clean` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;";
+ $sql = installer_build_create_database_sql($dbname);
$dbTiki->queryError($sql, $error);
if (empty($error)) {
// assure the DB has the right default encoding (if the DB already existed)
- $dbTiki->query("ALTER DATABASE `$dbname_clean` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
- Feedback::success(tra("Database `%0` was created.", '', false, [$dbname_clean]));
+ $dbTiki->query(installer_build_alter_database_charset_sql($dbname));
+ Feedback::success(tra("Database `%0` was created.", '', false, [$dbname]));
} else {
- Feedback::error(tra("Database `%0` creation failed. You need to create the database.", '', false, [$dbname_clean]));
+ Feedback::error(tra("Database `%0` creation failed. You need to create the database.", '', false, [$dbname]));
}
try {
@@ -441,7 +514,7 @@ function initTikiDB(&$api, $host, $user, $pass, $dbname, $client_charset, &$dbTi
Feedback::error($e->getMessage());
}
} else {
- Feedback::error(tra("Database `%0`. Unable to connect to database.", '', false, [$dbname_clean]));
+ Feedback::error(tra("Database `%0`. Unable to connect to database.", '', false, [$dbname]));
}
}
@@ -465,14 +538,17 @@ function initTikiDB(&$api, $host, $user, $pass, $dbname, $client_charset, &$dbTi
function createTikiDBUser(&$dbTiki, $host, $user, $pass, $dbname)
{
$error = '';
- if (preg_match('/^(127\.0\.\d{1,3}\.\d{1,3}|localhost)(:\d+)?$/', $host)) {
- $host = 'localhost';
- } else {
- $host = '%';
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ Feedback::error(tra("Invalid database name. Use only letters, numbers, dollar signs ($), underscores (_), or hyphens (-)."));
+ return false;
}
- $pass = addslashes($pass);
- $sql = "GRANT ALL PRIVILEGES ON `$dbname`.* TO `$user`@`$host` IDENTIFIED BY '$pass';";
+ if (! installer_is_valid_mysql_identifier($user)) {
+ Feedback::error(tra("Invalid database user. Use only letters, numbers, dollar signs ($), underscores (_), or hyphens (-)."));
+ return false;
+ }
+
+ $sql = installer_build_grant_database_privileges_sql($dbTiki, $dbname, $user, $host, $pass);
$dbTiki->queryError($sql, $error);
if (empty($error)) {
@@ -490,6 +566,9 @@ function createTikiDBUser(&$dbTiki, $host, $user, $pass, $dbname)
function convert_database_to_utf8($dbname)
{
$db = TikiDb::get();
+ if (! installer_is_valid_mysql_identifier($dbname)) {
+ throw new InvalidArgumentException('Invalid database name.');
+ }
if ($result = $db->fetchAll('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?', $dbname)) {
$db->query("ALTER DATABASE `$dbname` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
=====================================
installer/tiki-installer.php
=====================================
@@ -270,6 +270,7 @@ $smarty->assign('mid', 'tiki-install.tpl');
$smarty->assign('virt', isset($virt) ? $virt : null);
$smarty->assign('multi', isset($multi) ? $multi : null);
$smarty->assign('lang', $language);
+$smarty->assign('allow_create_new_user', ! file_exists($local));
if (isset($multi)) {
$smarty->assign('default_server_domain_name', $multi);
} elseif (isset($_SERVER['HTTP_HOST'])) {
@@ -396,9 +397,21 @@ if (
if (! empty($_POST['user']) && strlen($_POST['user']) > 80) {
$dbconn = false;
Feedback::error(tra('Invalid database user.'));
+ } elseif (empty($_POST['user'])) {
+ $dbconn = false;
+ Feedback::error(tra('No database user specified'));
} elseif (empty($_POST['name'])) {
$dbconn = false;
Feedback::error(tra('No database name specified'));
+ } elseif (! installer_is_valid_mysql_identifier($_POST['name'])) {
+ $dbconn = false;
+ Feedback::error(tra("Invalid database name. Use only letters, numbers, dollar signs ($), underscores (_), or hyphens (-)."));
+ } elseif (! installer_is_valid_mysql_identifier($_POST['user'])) {
+ $dbconn = false;
+ Feedback::error(tra("Invalid database user. Use only letters, numbers, dollar signs ($), underscores (_), or hyphens (-)."));
+ } elseif (! empty($_POST['create_new_user']) && file_exists($local)) {
+ $dbconn = false;
+ Feedback::error(tra('Creating a new database user is only available during initial installation.'));
} else {
if (isset($_POST['force_utf8'])) {
$client_charset = 'utf8mb4';
=====================================
templates/tiki-install.tpl
=====================================
@@ -338,7 +338,7 @@
<div class="mb-3 row">
<label class="col-form-label" for="name">{tr}Database name:{/tr}</label>
<div class="mx-3">
- <input type="text" class="form-control" id="name" name="name" size="40" value="{if isset($smarty.request.name)}{$smarty.request.name|escape:"html"}{elseif isset($preconfigname)}{$preconfigname|escape:"html"}{/if}" placeholder="{tr}Database name{/tr}"/>
+ <input type="text" class="form-control" id="name" name="name" size="40" value="{if isset($smarty.request.name)}{$smarty.request.name|escape:"html"}{elseif isset($preconfigname)}{$preconfigname|escape:"html"}{/if}" placeholder="{tr}Database name{/tr}" pattern="[A-Za-z0-9_$-]+" title="{tr}Use only letters, numbers, dollar signs, underscores, or hyphens.{/tr}"/>
<a href="javascript:void(0)" onclick="flip('name_help');" title="{tr}Help{/tr}">
{icon name="help"}
</a>
@@ -356,7 +356,7 @@
<legend>{tr}Database user{/tr}</legend>
<p>{tr}Enter a database user with administrator permission for the Tiki database.{/tr}</p>
<div style="padding:5px;">
- <label class="col-form-label" for="user">{tr}User name:{/tr}</label> <input type="text" class="form-control" id="user" name="user" value="{if (isset($smarty.request.user))}{$smarty.request.user|escape:"html"}{elseif isset($preconfiguser)}{$preconfiguser|escape:"html"}{/if}" maxlength="80" placeholder="{tr}Database username{/tr}">
+ <label class="col-form-label" for="user">{tr}User name:{/tr}</label> <input type="text" class="form-control" id="user" name="user" value="{if (isset($smarty.request.user))}{$smarty.request.user|escape:"html"}{elseif isset($preconfiguser)}{$preconfiguser|escape:"html"}{/if}" maxlength="80" placeholder="{tr}Database username{/tr}" pattern="[A-Za-z0-9_$-]+" title="{tr}Use only letters, numbers, dollar signs, underscores, or hyphens.{/tr}">
</div>
<div style="padding:5px;">
@@ -367,42 +367,49 @@
{/if}
</div>
- <div style="padding:5px;">
- <input type="checkbox" id="create-new-user" name="create_new_user" />
- <label class="col-form-label" for="create-new-user">{tr}Create the above database user just for this Tiki database.{/tr}</label>
- </div>
- </fieldset>
-
- <br/>
- <fieldset id="new-user-fieldset" style="display: none;">
- <legend>{tr}Administrative database user{/tr}</legend>
- <p>{tr}Enter database administrator user name and password.{/tr}<br>
- <em>{tr}This is a DB admin user which has permission to create new databases and new users.{/tr}</em></p>
- <div style="padding:5px;">
- <label class="col-form-label" for="user">{tr}DB admin user name:{/tr}</label> <input type="text" class="form-control" id="root_user" name="root_user" value="{if (isset($smarty.request.root_user))}{$smarty.request.root_user|escape:"html"}{elseif isset($preconfiguser)}{$preconfiguser|escape:"html"}{/if}" placeholder="{tr}DB admin user name{/tr}">
- </div>
- <div style="padding:5px;">
- <label class="col-form-label" for="pass">{tr}DB admin password:{/tr}</label> <input type="password" class="form-control" id="root_pass" name="root_pass" value="{if (isset($smarty.request.root_pass))}{$smarty.request.root_pass|escape:"html"}{/if}" autocomplete="new-password">
- </div>
+ {if $allow_create_new_user}
+ <div style="padding:5px;">
+ <input type="checkbox" id="create-new-user" name="create_new_user" />
+ <label class="col-form-label" for="create-new-user">{tr}Create the above database user just for this Tiki database.{/tr}</label>
+ </div>
+ {else}
+ <div style="padding:5px;">
+ <em>{tr}Creating a new database user is only available during the initial installation.{/tr}</em>
+ </div>
+ {/if}
</fieldset>
- <script type='text/javascript'><!--//--><![CDATA[//><!--
- ;(function(){
- var user = document.getElementById('user');
- var create_new_user = document.getElementById('create-new-user');
- var new_user_fs = document.getElementById('new-user-fieldset');
- if(create_new_user.checked) {
- new_user_fs.style.display = 'block';
- }
+ {if $allow_create_new_user}
+ <br/>
+ <fieldset id="new-user-fieldset" style="display: none;">
+ <legend>{tr}Administrative database user{/tr}</legend>
+ <p>{tr}Enter database administrator user name and password.{/tr}<br>
+ <em>{tr}This is a DB admin user which has permission to create new databases and new users.{/tr}</em></p>
+ <div style="padding:5px;">
+ <label class="col-form-label" for="user">{tr}DB admin user name:{/tr}</label> <input type="text" class="form-control" id="root_user" name="root_user" value="{if (isset($smarty.request.root_user))}{$smarty.request.root_user|escape:"html"}{elseif isset($preconfiguser)}{$preconfiguser|escape:"html"}{/if}" placeholder="{tr}DB admin user name{/tr}">
+ </div>
+ <div style="padding:5px;">
+ <label class="col-form-label" for="pass">{tr}DB admin password:{/tr}</label> <input type="password" class="form-control" id="root_pass" name="root_pass" value="{if (isset($smarty.request.root_pass))}{$smarty.request.root_pass|escape:"html"}{/if}" autocomplete="new-password">
+ </div>
+ </fieldset>
+ <script type='text/javascript'><!--//--><![CDATA[//><!--
+ ;(function(){
+ var create_new_user = document.getElementById('create-new-user');
+ var new_user_fs = document.getElementById('new-user-fieldset');
- create_new_user.addEventListener('click', function(){
- if(create_new_user.checked) {
+ if (create_new_user.checked) {
new_user_fs.style.display = 'block';
- } else {
- new_user_fs.style.display = 'none';
}
- });
- })();//--><!]]></script>
+
+ create_new_user.addEventListener('click', function(){
+ if (create_new_user.checked) {
+ new_user_fs.style.display = 'block';
+ } else {
+ new_user_fs.style.display = 'none';
+ }
+ });
+ })();//--><!]]></script>
+ {/if}
<br/>
<input type="hidden" name="resetdb" value="y">
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9946b85065f4d5197d0566f307287e9e09247136
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9946b85065f4d5197d0566f307287e9e09247136
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs