Wordpress coincide con las URL con tildes finales

11

Me han entregado un informe de vulnerabilidad (1) que parece estar implicando que puede haber un problema de seguridad en la forma en que Wordpress maneja las URL con las siguientes tildes. Parece que el escáner cree que el sitio web puede estar ofreciendo algunos listados de directorios y tal.

Me sorprendió que mi sitio web todavía ofreciera contenido en esas URL diferentes, así que hice una prueba instalando una instancia de WP totalmente en blanco, cambié a enlaces permanentes "Nombre de publicación" y confirmó que sí, cualquier URL con tilde agregada todavía se interpreta como la URL sin tilde.

De hecho, una url como esta:

https://mywordpresssite.com/my-permalink

También es accesible con las siguientes URL:

https://mywordpresssite.com/my-permalink~
https://mywordpresssite.com/my-permalink~/
https://mywordpresssite.com/my-permalink~~~~~~

Busqué un poco para ver dónde WP analiza los enlaces permanentes, y lo rastreé class-wp.phpen el parse_requestmétodo, pero no pude llegar mucho más allá de eso.

Mi pregunta es si este es el comportamiento previsto para WP, y si es así, ¿hay alguna forma de desactivarlo para que las tildes no coincidan? ¿Por qué WP interpretaría las URL con tildes como una URL sin ellas?

(1) Sí, ahora todos hemos visto un par de hacks importantes y filtraciones de datos en el Reino Unido, es esa vez nuevamente donde todos los "seguridad" fingen que están haciendo su parte al entregarnos a los desarrolladores informes de escaneo de 200 páginas lleno de falsos positivos y problemas genéricos de los que no saben nada en la expectativa si leemos y actuamos sobre dicho informe, nunca pasará nada malo.

dKen
fuente

Respuestas:

13

Vamos simples

Si entiendo bien OP, su problema es que las URL que contienen una tilde coinciden en absoluto.

Todas las demás respuestas se centran en el hecho de que la desinfección de la consulta elimina algunos caracteres antes de realizar la consulta, sin embargo, uno debe ser capaz de evitar que una regla de reescritura no coincida en algunas circunstancias.

Y es factible, no muy fácil, pero factible.

¿Por qué coincide, en primer lugar?

La razón por la que dos urls les gusta example.com/postnamey example.com/postname~coinciden con la misma regla de reescritura es porque la regla de reescritura de WP para publicaciones usa la etiqueta de reescritura %postname%que se reemplaza por la expresión regular ([^/]+)cuando se crean las reglas de reescritura.

El problema es que la expresión regular ([^/]+)también coincide con el nombre de la publicación postname~y, debido a la desinfección, el nombre consultado postnameterminará en un resultado válido.

Esto significa que si somos capaces de cambiar la expresión regular a partir ([^/]+)de ([^~/]+)la tilde no coincidirán más, así evitamos que activa las URL que contienen tilde en nombre de correos para ser igualada.

Como ninguna regla coincidirá, la url terminará siendo un 404, que debería ser el comportamiento esperado, creo.

Prevenir coincidencia

add_rewrite_tages una función que, a pesar de su nombre, se puede usar para actualizar una etiqueta de reescritura existente como %postname%.

Entonces, si usamos el código:

add_action('init', function() {
  add_rewrite_tag( '%postname%', '([^~/]+)', 'name=' );
});

vamos a llegar a nuestro objetivo y example.com/postname~vamos a no coincidir con la regla para example.com/postname.

Entonces, sí, las 3 líneas anteriores son el único código que necesitará .

Sin embargo, antes de que funcione, deberá limpiar las reglas de reescritura visitando la página de configuración de enlaces permanentes en el back-end.

Tenga en cuenta que regex ([^~/]+)evita que una tilde esté en cualquier lugar del nombre de la publicación, no solo como carácter final, sino que dado que los nombres de las publicaciones no pueden contener tilde debido a la desinfección, eso no debería ser un problema.

gmazzap
fuente
1
A +1 le gusta la simplicidad ;-) también parece que podríamos ajustar esto para otros caracteres de ruido también.
Birgire
1
@ Birgire no todos? ;)
gmazzap
@birgire sí, podríamos evitar que se elimine cualquier carácter sanitize_title, pero como es filtrable, no es posible escribir una solución siempre válida. Entonces fui específico.
gmazzap
1
Esta respuesta tiene, con mucho, la solución más limpia y explica claramente el problema que enfrentamos. Muchas gracias, ¡generosidad para ti!
dKen
7

es el comportamiento previsto para WP

Sí, como ya se explicó, WP_Query::get_posts()usa sanitize_title_for_query()( que usasanitize_title() ) para desinfectar el nombre de una publicación singular.

En resumen, después de pasar el nombre de la publicación sanitize_title_for_query(), my-permalink === my-permalink~~~ya que sanitize_title_for_query()elimina el final ~~~. Puede probar esto haciendo lo siguiente:

echo  sanitize_title_for_query( 'my-permalink~~~' )

¿Hay alguna manera de que pueda apagar esto para que las tildes no coincidan

Esto no es algo que pueda apagar. Hay un filtro en el sanitize_title()llamado sanitize_titleque se puede utilizar para alterar el comportamiento de sanitize_title(), pero eso es casi siempre no es una muy buena idea. La inyección de SQL es muy grave, por lo que dejar que algo se escape a través de las grietas debido al mal saneamiento puede tener una influencia realmente mala en la integridad de su sitio. El "exceso de saneamiento" a veces puede ser un dolor en el trasero.

No estoy seguro de lo que buscas, pero sospecho que tal vez quieras 404 publicaciones individuales con esta tilde final, en tus palabras, "apágalo". La única forma en que puedo pensar en esta etapa es detener la consulta principal cuando tenemos estas tildes finales. Para esto, podemos filtrar la posts_wherecláusula de la consulta principal.

EL FILTRO

Nota: Solo consideré publicaciones singulares normales, y no páginas frontales estáticas o archivos adjuntos, puede extender el filtro para incorporar esto

add_filter( 'posts_where', function ( $where, \WP_Query $q )
{
    // Only apply the filter on the main query
    if ( !$q->is_main_query() )
        return $where;

    // Only apply the filter on singular posts
    if ( !$q->is_singular() )
        return $where;

    // We are on a singular page, lets get the singular post name
    $name = sanitize_title_for_query( $q->query_vars['name'] );

    // Suppose $name is empty, like on ugly permalinks, lets bail and let WorPress handle it from here
    if ( !$name )
        return $where;

    // Get the single post URL
    $single_post_url = home_url( add_query_arg( [] ) );
    $parsed_url      = parse_url( $single_post_url );

    // Explode the url and return the page name from the path
    $exploded_pieces = explode( '/',  $parsed_url['path'] );
    $exploded_pieces = array_reverse( $exploded_pieces );

    // Loop through the pieces and return the part holding the pagename
    $raw_name = '';
    foreach ( $exploded_pieces as $piece ) {
        if ( false !== strpos( $piece, $name ) ) {
            $raw_name = $piece;

            break;
        }
    }

    // If $raw_name is empty, we have a serious stuff-up, lets bail and let WordPress handle this mess
    if ( !$raw_name )
        return $where;

    /**
     * All we need to do now is to match $name against $raw_name. If these two don't match,
     * we most probably have some extra crap in the post name/URL. We need to 404, even if the
     * the sanitized version of $raw_name would match $name. 
     */
    if ( $raw_name === $name )
        return $where;

    // $raw_name !== $name, lets halt the main query and 404
    $where .= " AND 0=1 ";

    // Remove the redirect_canonical action so we do not get redirected to the correct URL due to the 404
    remove_action( 'template_redirect', 'redirect_canonical' );

    return $where;
}, 10, 2 );

POCAS NOTAS

El filtro anterior devolverá una página 404 cuando tengamos una URL como https://mywordpresssite.com/my-permalink~~~~~~. Sin embargo, al eliminar remove_action( 'template_redirect', 'redirect_canonical' );del filtro, puede hacer que la consulta redirija automáticamente https://mywordpresssite.com/my-permalinky muestre la publicación única debido a redirect_canonical()que se engancha a template_redirectqué maneja la redirección de los 404 generados por WordPress

Pieter Goosen
fuente
7

Sí, parece extraño que debamos tener la misma coincidencia para:

example.tld/2016/03/29/test/

y por ejemplo

example.tld/2016/03/29/..!!$$~~test~~!!$$../

Por qué esto es posible, parece ser esta parte del WP_Query::get_posts()método:

if ( '' != $q['name'] ) {
    $q['name'] = sanitize_title_for_query( $q['name'] );

donde sanitize_title_for_query()se define como:

function sanitize_title_for_query( $title ) {
        return sanitize_title( $title, '', 'query' );
}

Debería ser posible hacer esto más estricto con el sanitize_titlefiltro, pero podría no ser una buena idea anular la salida predeterminada, basada en sanitize_title_with_dashes, que es responsable del saneamiento aquí. Debería considerar crear un ticket en lugar de cambiarlo, si ya no hay información actualizada sobre este comportamiento.

Actualizar

Me pregunto si podríamos limpiar el ruido de la corriente con la ruta sanitize_title_for_query()y redirigir a la URL limpia si es necesario.

Aquí hay una demostración con la que puede jugar en su sitio de prueba y ajustarla a sus necesidades:

/**
 * DEMO: Remove noise from url and redirect to the cleaned version if needed 
 */
add_action( 'init', function( )
{
    // Only for the front-end
    if( is_admin() )
        return;

    // Get current url
    $url = home_url( add_query_arg( [] ) );

    // Let's clean the current path with sanitize_title_for_query()
    $parse = parse_url( $url );
    $parts = explode( '/',  $parse['path'] );
    $parts = array_map( 'sanitize_title_for_query', $parts );   
    $path_clean = join( '/', $parts );
    $url_clean = home_url( $path_clean );
    if( ! empty( $parse['query'] ) )
        $url_clean .= '?' . $parse['query'];

    // Only redirect if the current url is noisy
    if( $url === $url_clean )
        return;
    wp_safe_redirect( esc_url_raw( $url_clean ) );
    exit;
} );

Incluso podría ser mejor usar sanitize_title_with_dashes()directamente para evitar los filtros y reemplazar:

$parts = array_map( 'sanitize_title_for_query', $parts );

con:

foreach( $parts as &$part )
{
    $part = sanitize_title_with_dashes( $part, '', 'query' );
}

ps: Creo que aprendí este truco, para obtener la ruta actual con un vacío add_query_arg( [] ), de @gmazzap ;-) Esto también se observa en el Codex. Gracias de nuevo a @gmazzap por el recordatorio de usar esc_url()cuando se muestra la salida de add_query_arg( [] )o esc_url_raw()cuando, por ejemplo, se redirige. Compruebe la referencia anterior del Codex para eso también.

Birgire
fuente
+1 Solo para aclarar, esos caracteres especiales se eliminan, por lo que, aunque la versión extraña de la URL es visible en la barra de ubicación, WordPress funciona con la URL real, por lo que la solicitud funciona en primer lugar. No veo ningún riesgo de seguridad mayor con ese comportamiento.
Nicolai
1
sí, creo que no deberíamos meternos con el filtro de saneamiento para cambiar esto @ialocin
birgire
1
Claro, a menos que haya una muy buena razón, es una molestia que no vale la pena. No quiere decir que lo más probable es que no sea bueno para la cordura de los desarrolladores, ni siquiera para entrar en el saneamiento técnico. Sin embargo, solo mis dos centavos.
Nicolai
1
@birgire cuando se usa de la misma manera, add_query_argdebe ser escapado con esc_urlo esc_url_rawpara evitar problemas de seguridad ...
gmazzap
ahh sí, gracias, si recuerdo correctamente, este fue un problema de seguridad descubierto en muchos complementos recientemente @gmazzap
birgire
3

Permítanme explicar el procesamiento de WordPress de una solicitud y un método para cambiar el comportamiento de WordPress para lograr sus objetivos en consecuencia.

Analizando la solicitud

Cuando WordPress recibe una solicitud, comienza un proceso de disección de la solicitud y la transforma en una página. El núcleo de este proceso comienza cuando WP::main()se llama al método de consulta principal de WordPress . Esta función analiza la consulta, como identificó correctamente, en parse_request()(in includes/class-wp.php). Allí, WordPress intenta hacer coincidir la URL con una de las reglas de reescritura . Cuando la URL coincide, crea una cadena de consulta de las partes de la URL y codifica estas partes (todo entre dos barras) usando urlencode(), para evitar que caracteres especiales como &estropeen la cadena de consulta. Estos caracteres codificados pueden haberle hecho pensar que el problema residía allí, pero en realidad se convirtieron en sus caracteres "reales" correspondientes al analizar la cadena de consulta.

Ejecutando la consulta asociada con la solicitud

Después de que WordPress haya analizado la URL, configura la clase de consulta principal WP_Query, que se realiza con el mismo main()método de la WPclase. La esencia de WP_Queryse puede encontrar en su get_posts()método donde todos los argumentos de consulta se analizan y desinfectan y se construye la consulta SQL real (y, finalmente, se ejecuta).

En este método, en la línea 2730, se ejecuta el siguiente código:

$q['name'] = sanitize_title_for_query( $q['name'] );

Esto desinfecta la publicación para obtenerla de la tabla de publicaciones. La salida de información de depuración dentro del bucle muestra que aquí es donde reside el problema: su nombre de publicación my-permalink~, se transforma en my-permalink, que luego se utiliza para recuperar la publicación de la base de datos.

La función de desinfección del título de la publicación

La función sanitize_title_for_queryllama sanitize_titlecon los parámetros adecuados, lo que procede a desinfectar el título. Ahora el núcleo de esta función es aplicar el sanitize_titlefiltro:

$title = apply_filters( 'sanitize_title', $title, $raw_title, $context );

Este filtro ha, en WordPress nativa, una única función que se le atribuye: sanitize_title_with_dashes. He escrito una extensa descripción de lo que hace esta función, que se puede encontrar aquí . En esta función, la línea que está causando el problema es

$title = preg_replace('/[^%a-z0-9 _-]/', '', $title);

Esta línea elimina todos los caracteres, excepto los caracteres alfanuméricos, espacios, guiones y guiones bajos.

Resolviendo tu problema

Entonces, básicamente hay una única forma de resolver su problema: eliminar la sanitize_title_with_dashesfunción del filtro y reemplazarla con su propia función. En realidad, esto no es tan difícil de hacer, pero :

  1. Cuando WordPress cambia el proceso interno de desinfección de títulos, esto tendrá efectos importantes en su sitio web.
  2. Es posible que otros complementos conectados a este filtro no manejen correctamente la nueva funcionalidad.
  3. Lo más importante : WordPress usa el resultado de la sanitize_titlefunción directamente en la consulta SQL por esta línea:

    $where .= " AND $wpdb->posts.post_name = '" . $q['name'] . "'";

    ¡Si alguna vez considera cambiar el filtro, asegúrese de escapar correctamente del título antes de que se use en la consulta!

Conclusión: resolver su problema no es necesario en lo que respecta a la seguridad, pero si desea hacerlo, reemplácelo sanitize_title_with_dashescon su propia funcionalidad y preste atención al escape de SQL.

NB todos los nombres de archivos y números de línea corresponden a archivos de WordPress 4.4.2.

Engelen
fuente
3

Algunas personas ya han explicado el problema, así que simplemente publicaré una solución alternativa. Debería ser bastante autoexplicativo.

add_action( 'template_redirect', function() {
    global $wp;

    if ( ! is_singular() || empty( $wp->query_vars['name'] ) )
        return;

    if ( $wp->query_vars['name'] != get_query_var( 'name' ) ) {
        die( wp_redirect( get_permalink(), 301 ) );
        // or 404, or 403, or whatever you want.
    }
});

Usted tendrá que hacer algo un poco diferente para este tipo de puestos jerárquicos embargo, ya que WP_Queryse ejecutará pagenamea través wp_basenamey luego desinfectar, así query_vars['pagename']y get_query_var('pagename')no coincidirán con los niños becuase este último no contiene la parte de los padres.

Ojalá me redirect_canonicalhiciera cargo de esta basura.

kovshenin
fuente
0

ESTE ES EL ARREGLO ... PARA EL ERROR DE WORDPRESS SOLO AGREGUE EL BLOQUE DE MODOS DE SEGURIDAD DE COMENZAR por encima del BLOQUE generado por Wordpress.

# BEGIN security mod
<IfModule mod_rewrite.c>
RewriteRule ^.*[~]+.*$ - [R=404]
</IfModule>
#END security mod

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /wordpress/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /wordpress/index.php [L]
</IfModule>

# END WordPress
Michael S. Howard
fuente
-3

Siempre puede intentar agregar agregando lo siguiente a su .htaccessarchivo:

RewriteEngine On
RewriteRule \.php~$  [forbidden,last]

La segunda línea de arriba debe ir justo debajo de la primera línea que se muestra. Debe evitar que se index.php~muestre en las URL.

Huginn
fuente
Esto no funciona para los enlaces permanentes sobre los que se trata la pregunta, ¿verdad?
Nicolai