Explaining the Drupal < 7.16 Installer vulnerability
Heine Wed, 2012/10/24 - 11:24
Background
SA-CORE-2012-003 fixes an issue in the Drupal installer that enables an attacker to cause the site to use a different attacker-controlled database. This database can be an external server or an SQLite file. The vulnerability also causes the installer to leak database information such as the database type, name, host and the username used to connect to the database. The only item not leaked is the database password.
The installer vulnerability was found while preparing my DrupalJam presentation (NL) on security audits and reported via the SecuriTeam Secure Disclosure program (also via ssd@beyondsecurity.com). As promised on IRC & Reddit, here's some additional information on the root cause(s).
Installer system
The vulnerability is caused by an assumption of the install system combined with a bug. The assumption is that errors generated while contacting the database indicate a system that still needs installation. To aid in understanding the additional bug, some global information on the installer follows first.
The problem revolves around the function install_begin_request
. It verifies the validity of the current settings.php via install_verify_settings
, then determines what the install state is in order to decide if installation should continue or if the site has already been installed.
Here’s the function (elided for brevity):
// ...
$install_state['settings_verified'] = install_verify_settings();
if ($install_state['settings_verified']) {
// Initialize the database system. Note that the connection
// won't be initialized until it is actually requested.
require_once DRUPAL_ROOT . '/includes/database/database.inc';
// Verify the last completed task in the database, if there is one.
$task = install_verify_completed_task();
}
else {
$task = NULL;
// Since previous versions of Drupal stored database connection information
// in the 'db_url' variable, we should never let an installation proceed if
// this variable is defined and the settings file was not verified above
// (otherwise we risk installing over an existing site whose settings file
// has not yet been updated).
if (!empty($GLOBALS['db_url'])) {
throw new Exception(install_already_done_error());
}
}
// Modify the installation state as appropriate.
$install_state['completed_task'] = $task;
$install_state['database_tables_exist'] = !empty($task);
}
When install_verify_settings
does not succeed in verifying settings.php the else path is taken, and installation will proceed, provided there's no global db_url
set (this global indicates a D6 settings.php and marks a system that is to be updated).
install_verify_settings
will ultimately run db_run_tasks (a wrapper for the DatabaseTasks->runTasks
method). The tasks executed in db_run_tasks
are listed in install.inc in the abstract class DatabaseTasks
and consist of table creation, insertion, deletions and dropping.
Any exceptions generated during db_run_tasks
will be reported as errors to install_verify_settings
. This means that when errors of any kind (eg database is down) are generated by the db, the settings.php file fails verification and installation will proceed by displaying a partially filled in Database configuration page.
If required information is send in a POST request, the installer will mark settings.php as writable (if possible) and then update settings.php with this information.
Bug
Being able to do a new install when the database is down, or settings.php is temporarily broken is bad enough, but not what makes SA-CORE-2012-003 so grave. The big problem is the possibility to force the verifier to error out on a working database.
To see how, let's look at the database tasks install_verify_settings
runs:
CREATE TABLE {drupal_install_test} (id int NULL)
- …
DROP TABLE {drupal_install_test}
You probably see the problem now. If not, consider two POST requests fired nearly simultaneously at install.php:
- Request 1 executes the db-task
CREATE TABLE {drupal_install_test} (id int NULL)
; The table will be created. - Request 2 executes the db-task
CREATE TABLE {drupal_install_test} (id int NULL)
; Oops… - Request 1 executes the rest of the db-tasks and then deletes
drupal_install_test
Trying to create an already existing table in Request 2 will generate an exception that will be reported as a db/settings error to install_begin_request
and thus a signal for a continued installation. By sending the right information in the POST request, a settings.php is written that points to the attacker controlled database.
Notes
I'm comfortable releasing this information after a week, because the mitigation step is pretty easy and without consequence: delete install.php or block access to it. If you didn't upgrade or follow the mitigation steps, do so now, then review your update procedures. I conciously did not provide a weaponized example of the exploit; While it's easy to create such an exploit based on this article, it is a step many attackers cannot or will not take.
Should you learn of a serious vulnerability in Drupal or another piece of software, consider the SecuriTeam Secure Disclosure program (also via ssd@beyondsecurity.com). I had a great experience.
Comments
Thanks
Submitted by Bas (not verified) on Thu, 2012/10/25 - 10:46Heine, thanks for your explanation. I saw you earlier talk at a Drupal Tech Talk. You're doing a lot for the community.
I have updated all of our websites to the new Drupal version but if I understand it correctly I wasn't really in danger because of 2 things:
- All sites are running in a multisite configuration, and the default site is 'unused'.
- My settings.php files are all write protected.
But if I understand correctly, some Ajax script posting install.php requests very quickly and often would cause ANY stand-alone Drupal 7.15 (or an earlier version) site to crash when settings.php is not write-protected?
That's a seriously good find and a nice bugfix! And another example of how important it is to keep your websites up to date.
Regards,
Bas
Mitigation
Submitted by Heine on Thu, 2012/10/25 - 11:44Hello Bas. Thank you for your kind words. I should join the Drupal Tech Talks more often.
Exactly, as long as this posting is done in parallel. Sites with a settings.php that is write-protected AND not owned by the webserver will leak info instead. When settings.php can be written, an attacker can use this to point your Drupal installation to another database and subsequently execute arbitrary PHP code on your server.
As to the danger your sites were in:
File permissions for install.php and update.php
Submitted by kalabro (not verified) on Thu, 2012/11/01 - 15:50I usually restrict access to authorize.php, install.php and update.php on production.
Why there are no recomendations about install.php and update.php files in docs (http://drupal.org/node/244924) or INSTALL.txt?
Thanks!