URL de correspondance Wordpress avec des tildes de fin


11

J'ai reçu un rapport de vulnérabilité (1) qui semble impliquer qu'il peut y avoir un problème de sécurité dans la façon dont Wordpress gère les URL avec les tildes suivants. Il semble que le scanner pense que le site Web propose des listes de répertoires, etc.

J'ai été surpris que mon site Web diffuse toujours du contenu sur ces différentes URL, j'ai donc fait un test en installant une instance WP totalement vide, je suis passé aux permaliens "Post name" et j'ai confirmé que oui, toute URL avec un tilde ajouté est toujours interprétée comme l'URL sans le tilde.

En effet, une URL comme celle-ci:

https://mywordpresssite.com/my-permalink

Est également accessible avec les URL suivantes:

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

J'ai fouillé un peu pour voir où WP analyse les permaliens, et je l'ai retrouvé class-wp.phpdans la parse_requestméthode, mais je n'ai pas pu aller beaucoup plus loin.

Ma question est de savoir si c'est un comportement prévu pour WP, et si oui, y a-t-il un moyen de désactiver cela pour que les tildes ne correspondent pas? Pourquoi WP interpréterait-il les URL avec des tildes comme une URL sans eux?

(1) Oui, maintenant nous avons tous vu quelques hacks et fuites de données majeurs au Royaume-Uni, c'est à ce moment-là que les gars de la "sécurité" prétendent tous faire leur part en nous remettant aux développeurs des rapports d'analyse de 200 pages. plein de faux positifs et de problèmes génériques dont ils ne savent rien dans l'attente si nous lisons et agissons sur ledit rapport, rien de mauvais ne se produira jamais.

Réponses:


13

Allons simple

Si je comprends bien OP, votre problème est que les URL contenant un tilde sont identiques.

Toutes les autres réponses se concentrent sur le fait que la désinfection de la requête supprime certains caractères avant d'effectuer la requête, mais il faut pouvoir empêcher une règle de réécriture de ne pas correspondre dans certaines circonstances.

Et c'est faisable, pas très facile, mais faisable.

Pourquoi cela correspond, en premier lieu?

La raison pour laquelle deux URL aiment example.com/postnameet example.com/postname~correspondent à la même règle de réécriture est que la règle de réécriture WP pour les publications utilise la balise de réécriture %postname%qui est remplacée par l'expression régulière ([^/]+)lorsque les règles de réécriture sont créées.

Le problème est que l'expression ([^/]+)régulière correspond également au nom de poste postname~et, en raison de la désinfection, le nom demandé se postnameterminera par un résultat valide.

Cela signifie que si nous sommes en mesure de modifier l'expression régulière de ([^/]+)la ([^~/]+)tilde ne correspondra pas à plus si nous empêchons activement urls contenant tilde au nom de poste à correspondre.

Puisqu'aucune règle ne correspondra, l'URL finira par être un 404, ce qui devrait être le comportement attendu, je pense.

Empêcher la correspondance

add_rewrite_tagest une fonction qui, malgré son nom, peut être utilisée pour mettre à jour une balise de réécriture existante comme %postname%.

Donc, si nous utilisons le code:

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

nous atteindrons notre objectif et example.com/postname~nous ne correspondre à la règle example.com/postname.

Donc, oui, les 3 lignes ci-dessus sont le seul code dont vous aurez besoin .

Cependant, avant que cela fonctionne, vous devrez vider les règles de réécriture en visitant la page des paramètres de permalien sur le backend.

Notez que l'expression régulière ([^~/]+)empêche un tilde d'être n'importe où dans le nom du message, non seulement en tant que caractère de fin, mais comme les noms de message ne peuvent pas réellement contenir de tilde à cause de la désinfection, cela ne devrait pas poser de problème.


1
+1 comme la simplicité ;-) il semble également que nous pourrions également ajuster cela pour d'autres caractères de bruit.
birgire

1
@birgire, n'est-ce pas nous tous? ;)
gmazzap

@birgire oui, nous pourrions empêcher tout caractère supprimé sanitize_title, mais comme il est filtrable, il n'est pas possible d'écrire une solution toujours valide. Je suis donc devenu spécifique.
gmazzap

1
Cette réponse a de loin la solution la plus propre et explique clairement le problème auquel nous sommes confrontés. Merci beaucoup - générosité pour vous!
dKen

7

est le comportement prévu pour WP

Oui, comme déjà expliqué, WP_Query::get_posts()utilise sanitize_title_for_query()( qui utilisesanitize_title() ) pour assainir le nom de poste d'un poste singulier.

En bref, après le passage du nom de poste sanitize_title_for_query(), my-permalink === my-permalink~~~comme sanitize_title_for_query()supprime la fin ~~~. Vous pouvez tester cela en procédant comme suit:

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

est-il possible de désactiver cette fonction pour que les tildes ne correspondent pas

Ce n'est pas quelque chose que vous pouvez désactiver. Il existe un filtre sanitize_title()appelé sanitize_titleque vous pouvez utiliser pour modifier le comportement de sanitize_title(), mais ce n'est presque toujours pas une très bonne idée. L'injection SQL est très grave, donc laisser quelque chose glisser entre les mailles du filet en raison d'un mauvais assainissement peut avoir une très mauvaise influence sur l'intégrité de votre site. "Un assainissement excessif" peut parfois être une douleur dans le cul.

Je ne suis pas sûr de ce que vous recherchez, mais je pense que vous voudrez peut-être 404 messages uniques avec ces tilde de fin, selon vos mots, "éteignez-le". La seule façon à laquelle je peux penser à ce stade est d'arrêter la requête principale lorsque nous avons ces tildes de fin. Pour cela, nous pouvons filtrer la posts_whereclause de la requête principale.

LE FILTRE

Remarque: Je n'ai considéré que les messages singuliers normaux, et non les premières pages ou les pièces jointes statiques, vous pouvez étendre le filtre pour l'incorporer

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 );

QUELQUES NOTES

Le filtre ci-dessus renverra une page 404 lorsque nous aurons une URL comme https://mywordpresssite.com/my-permalink~~~~~~. Vous pouvez cependant, en supprimant remove_action( 'template_redirect', 'redirect_canonical' );du filtre, faire rediriger automatiquement la requête vers https://mywordpresssite.com/my-permalinket afficher la publication unique en raison de redirect_canonical()laquelle est accrochée à template_redirectqui gère la redirection des 404 générés par WordPress


7

Oui, il semble étrange que nous ayons le même match pour:

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

et par exemple

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

Pourquoi cela est possible, semble être cette partie de la WP_Query::get_posts()méthode:

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

sanitize_title_for_query()est défini comme:

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

Il devrait être possible de rendre cela plus strict avec le sanitize_titlefiltre, mais ce ne serait peut-être pas une bonne idée de remplacer la sortie par défaut, basée sur sanitize_title_with_dashes, qui est responsable de l'assainissement ici. Vous devriez envisager de créer un ticket au lieu de le modifier, s'il n'y a pas de courant déjà sur ce comportement.

Mise à jour

Je me demande si nous pourrions nettoyer le bruit du courant avec le chemin sanitize_title_for_query()et rediriger vers l'URL nettoyée si nécessaire?

Voici une démo avec laquelle vous pouvez jouer sur votre site de test et l'adapter à vos besoins:

/**
 * 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;
} );

Il pourrait même être préférable d'utiliser sanitize_title_with_dashes()directement pour éviter les filtres et remplacer:

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

avec:

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

ps: Je pense que j'ai appris cette astuce, pour obtenir le chemin actuel avec un vide add_query_arg( [] ), de @gmazzap ;-) Ceci est également noté dans le Codex. Merci encore à @gmazzap pour le rappel de l'utilisation esc_url()lors de l'affichage de la sortie add_query_arg( [] )ou esc_url_raw()lors de la redirection par exemple. Vérifiez également la référence précédente du Codex.


+1 Juste pour clarifier, ces caractères spéciaux sont supprimés, donc, bien que la version étrange de l'URL soit visible dans la barre d'emplacement, WordPress fonctionne avec l'URL réelle, c'est pourquoi la demande fonctionne en premier lieu. Je ne vois aucun risque de sécurité du maire avec ce comportement.
Nicolai

1
oui je pense que nous ne devrions pas jouer avec le filtre d'assainissement pour changer ce @ialocin
birgire

1
Bien sûr, à moins qu'il n'y ait une très bonne raison, c'est un tracas qui n'en vaut pas la peine. Pour ne pas dire, ce n'est probablement pas bon pour la santé mentale des développeurs - pas même pour entrer dans l'assainissement technique. Seulement mes deux cents cependant.
Nicolai

1
@birgire lorsqu'il est utilisé comme tel add_query_argdoit donc être échappé avec esc_urlou esc_url_rawpour éviter des problèmes de sécurité ...
gmazzap

ahh oui merci, si je me souviens bien, c'était un problème de sécurité découvert récemment dans de nombreux plugins @gmazzap
birgire

3

Permettez-moi d'expliquer le traitement d'une demande par WordPress et une méthode pour changer le comportement de WordPress pour atteindre vos objectifs en conséquence.

Analyse de la demande

Lorsque WordPress reçoit une demande, il démarre un processus de dissection de la demande et de la transformer en page. Le cœur de ce processus commence lorsque la méthode de requête principale WordPress WP::main()est appelée. Cette fonction analyse la requête, comme vous l'avez correctement identifié, dans parse_request()(in includes/class-wp.php). Là, WordPress essaie de faire correspondre l'URL avec l'une des règles de réécriture . Lorsque l'URL correspond, il crée une chaîne de requête des parties d'URL et code ces parties (tout entre deux barres obliques) à l'aide de urlencode(), pour empêcher les caractères spéciaux tels que &de gâcher la chaîne de requête. Ces caractères codés peuvent vous avoir fait penser que le problème résidait là, mais ils sont en fait transformés en leurs "vrais" caractères correspondants lors de l'analyse de la chaîne de requête.

Exécution de la requête associée à la demande

Après que WordPress a analysé l'URL, il configure la classe de requête principale WP_Query, ce qui se fait selon la même main()méthode que la WPclasse. Le boeuf de WP_Querypeut être trouvé dans sa get_posts()méthode où tous les arguments de requête sont analysés et nettoyés et la requête SQL réelle est construite (et, éventuellement, exécutée).

Dans cette méthode, sur la ligne 2730, le code suivant est exécuté:

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

Cela désinfecte le message pour le récupérer dans la table des messages. La sortie des informations de débogage à l'intérieur de la boucle montre que c'est là que réside le problème: votre nom de publication,, my-permalink~est transformé en my-permalink, qui est ensuite utilisé pour récupérer la publication dans la base de données.

La fonction de nettoyage du titre du message

La fonction sanitize_title_for_queryappelle sanitize_titleavec les paramètres appropriés, ce qui procède à la désinfection du titre. Maintenant, le cœur de cette fonction applique le sanitize_titlefiltre:

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

Ce filtre a, dans WordPress natif, une seule fonction attachée à elle: sanitize_title_with_dashes. J'ai écrit un aperçu complet de ce que fait cette fonction, qui peut être trouvé ici . Dans cette fonction, la ligne à l'origine de votre problème est

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

Cette ligne supprime tous les caractères à l'exception des caractères alphanumériques, des espaces, des tirets et des traits de soulignement.

Résoudre votre problème

Il existe donc essentiellement une seule façon de résoudre votre problème: supprimer la sanitize_title_with_dashesfonction du filtre et la remplacer par votre propre fonction. Ce n'est en fait pas si difficile à faire, mais :

  1. Lorsque WordPress modifie le processus interne de désinfection des titres, cela aura des effets majeurs sur votre site Web.
  2. D'autres plugins connectés à ce filtre peuvent ne pas gérer correctement la nouvelle fonctionnalité.
  3. Plus important encore : WordPress utilise le résultat de la sanitize_titlefonction directement dans la requête SQL par cette ligne:

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

    Si jamais vous envisagez de changer le filtre, assurez-vous d'avoir correctement échappé le titre avant qu'il ne soit utilisé dans la requête!

Conclusion: la résolution de votre problème n'est pas nécessaire en ce qui concerne la sécurité, mais si vous voulez le faire, remplacez le sanitize_title_with_dashespar votre propre fonctionnalité et faites attention à l'échappement SQL.

NB tous les noms de fichiers et numéros de ligne correspondent aux fichiers WordPress 4.4.2.


3

Certaines personnes ont déjà expliqué le problème, je vais donc simplement publier une solution alternative. Devrait être assez explicite.

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.
    }
});

Vous aurez à faire quelque chose d' un autre bit pour les types de postes hiérarchiques bien, puisque WP_Queryse déroulera à pagenametravers wp_basename, puis désinfectez, donc query_vars['pagename']et get_query_var('pagename')ne correspondra pas à des enfants becuase ce dernier ne contiendra pas la partie parent.

Je souhaite redirect_canonicaljuste pris soin de cette merde.


0

C'EST LE CORRECTIF ... POUR LE BUG DE WORDPRESS AJOUTEZ JUSTEMENT le bloc de mod de sécurité BEGIN au-dessus du BLOC généré par 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

-3

Vous pouvez toujours essayer d'ajouter en ajoutant les éléments suivants à votre .htaccessfichier:

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

La deuxième ligne ci-dessus doit passer juste sous la première ligne indiquée. Cela devrait empêcher l' index.php~affichage dans les URL.


Cela ne fonctionne pas pour les jolis permaliens sur lesquels porte la question, non?
Nicolai
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.