Das Problem war in der Tat, dass aus dem install-Ordner nur das Installationsskript (install.php), nicht jedoch das Upgrade-Skript gelöscht wurde. Das ist natürlich ein peinlicher Fehler, da man ohnehin das gesamte Installationsverzeichnis löschen sollte, bevor man ein Board öffentlich zugänglich macht. Ausserdem war die Schwachstelle öffentlich bekannt, trivial ausnutzbar, und ich kannte sie bereits, bevor sie bekannt wurde. Ich habe die vBulletin-Instanz nicht installiert (deshalb habe ich auch nicht die User-ID 1), allerdings bereits vor Wochen `überprüft`, ob das install-Verzeichnis noch vorhanden ist - unglücklicherweise mit einem `ls isntall` statt `ls install`, was natürlich keine Treffer lieferte.
Über die Schwachstelle im upgrade-Skript lässt sich ein Admin-Account mit beliebigen Zugangsdaten anlegen. Meine Analyse hat gezeigt, dass der Server nicht kompromittiert wurde und die einzige im Admin-CP vorgenommene Aktion das Löschen von drei Kategorien war, sowohl gemäss vB-Admin als auch gemäss Webserver-Access-Log. Dateien wurden keine verändert, die Datenbank per Binlog auf den Zustand nach der letzten Änderung vor dem kompromittierenden `INSERT INTO user ...`-Query zurückgesetzt.
Konket ruft upgrade.php nach dem Laden einiger Bibliotheken
$verify =& vB_Upgrade::fetch_library($vbulletin, $phrases, '', !defined('VBINSTALL'));
auf. Die vB_Upgrade-Klasse lädt und initialisiert dann eine vB_Upgrade_Ajax-Instanz, da sie über einen Webserver aufgerufen wurde (das Skript liesse sich auch auf der Kommandozeile ausführen). Deren init()-Methode benutzt vom Benutzer übermittelte Daten, um festzustellen, an welcher Stelle im Upgrade-Prozess man sich befindet und welcher Schritt als nächster zu erledigen sei:
$this->process_step($this->registry->GPC['version'], $this->registry->GPC['step'], $this->registry->GPC['startat'], $this->registry->GPC['checktable'], $this->registry->GPC_exists['response'] ? $this->registry->GPC['response'] : null, $this->registry->GPC['firstrun'], $this->registry->GPC['only'], $this->registry->GPC['htmlsubmit'], $this->registry->GPC['htmldata'], $this->registry->GPC['options']);
wobei
$this->registry->GPC teilweise bereinigte GET/POST/COOKIE-Daten enthält. Interessant ist der erste Parameter, welcher festlegt, von welcher Version aus das Upgrade stattfinden soll. Im install/includes-Verzeichnis existieren php-Dateien mit jeweils einer Klassendefinition für jede Version (z.B. class_upgrade_4112.php, welche die Klasse vB_Upgrade_4112 implementiert). Allerdings existiert auch eine Datei namens class_upgrade_install.php, welche die für die initiale Installation vorgesehene Klasse vB_Upgrade_install implementiert. Da an keiner Stelle geprüft wird, ob der Wert von
$this->registry->GPC['version'] der tatsächlichen Version entspricht, kann ein Angreifer den Wert `install` übergeben, um die Klasse für die Installation zu laden.
Jede dieser Klassen definiert einen oder mehrere Installations-/Upgradeschritte. Bei welchem Schritt sich der Benutzer gerade befindet, wird in
$this->registry->GPC['step'] übergeben. Auch dieser Wert wird nur in eine Zahl konvertiert, aber keiner weiteren Prüfung unterzogen.
In vB_Upgrade_install ist Schritt #7 von entscheidender Bedeutung:
/**
* Step #7 - Default User Setup...
*
*/
function step_7($data = null)
{
/* ... */
$salt = fetch_user_salt(SALT_LENGTH);
/*insert query*/
$this->db->query_write("
INSERT INTO " . TABLE_PREFIX . "user
(username, salt, password, email, usertitle, joindate, lastvisit, lastactivity, usergroupid, passworddate, options, showvbcode)
VALUES (
'" . $this->db->escape_string(htmlspecialchars_uni($data['htmldata']['username'])) . "',
'" . $this->db->escape_string($salt) . "',
'" . $this->db->escape_string(md5(md5($data['htmldata']['password']) . $salt)) . "',
'" . $this->db->escape_string($data['htmldata']['email']) . "',
'" . $this->db->escape_string($this->phrase['install']['usergroup_admin_usertitle']) . "',
" . TIMENOW . ",
" . TIMENOW . ",
" . TIMENOW . ",
6,
FROM_UNIXTIME(" . TIMENOW . "),
$admin_useroption,
2
)
");
}
In diesem Schritt wird bei der Installation ein Benutzer mit administrativen Rechten angelegt. Das Problem ist, dieser Schritt lässt sich aufgrund der Schwachstelle auch über das upgrade.php-Skript auslösen, indem man per POST
version=install und
step=7 übergibt. Die initialen Zugangsdaten stammen ebenso aus per POST übergebenen Benutzerdaten, lassen sich vom Angreifer also frei wählen.
Es existieren inzwischen auch fertige Exploits, um die Schwachstelle auszunutzen, z.B.
http://www.cyberaon.com/2013/09/vbulletin-4xx-and-5xx-upgrade-0day.html.