URL de correspondance Wordpress avec tildes de fin

11

On m'a remis un rapport de vulnérabilité (1) qui semble impliquer qu'il pourrait y avoir un problème de sécurité dans la façon dont Wordpress traite les URL avec les tildes suivants. Il semble que le scanner pense que le site Web est en train de servir certaines listes de répertoires et autres.

J'ai été surpris que mon site Web serve toujours du contenu sur ces différentes URL. J'ai donc fait un test en installant une instance de WP totalement vierge, en basculant sur des permaliens "Post name" et en confirmant que, oui, toutes les URL avec un tilde ajouté. est interprété 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 analysait les permaliens et je les ai suivis jusqu'à class-wp.php dans la méthode parse_request , mais je n'ai pas pu aller beaucoup plus loin que cela.

Ma question est de savoir si ce comportement est destiné à WP et, dans l'affirmative, est-il possible de désactiver ce comportement afin que les tildes ne correspondent pas? Pourquoi WP interpréterait-il les URL avec des tildes comme une URL sans eux?

(1) Oui, nous avons tous été témoins de quelques piratages majeurs et de fuites de données au Royaume-Uni. C'est à nouveau cette fois-ci que les "agents de la sécurité" prétendent tous faire leur part en nous remettant Les développeurs analysent des rapports de 200 pages remplis de faux-positifs et de problèmes génériques dont ils ne savent rien. Si nous lisons et agissons sur ce rapport, rien de grave ne se passera jamais.

    
posée dKen 15.11.2015 - 18:19

7 réponses

13

Allons-y simplement

Si je comprends bien l'OP, votre problème est que les URL contenant un tilde correspondent.

Toutes les autres réponses mettent l'accent sur le fait que la désinfection d'une requête supprime certains caractères avant d'effectuer la requête. Toutefois, vous devez pouvoir empêcher une règle de réécriture de ne correspondre à certaines circonstances.

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

Pourquoi correspond-il, en première place?

La raison pour laquelle deux URL telles que example.com/postname et 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% remplacé par la regex ([^/]+) lors de la création de règles de réécriture.

Le problème, c'est que l'expression rationnelle ([^/]+) correspond également au postname postname~ et, en raison de la désinfection, le nom demandé sera postname et aboutira dans un résultat valide.

Cela signifie que si nous sommes en mesure de changer l'expression rationnelle de ([^/]+) à ([^~/]+) tilde ne correspondra plus, nous évitons donc que les URL contenant tilde dans le nom de l'article ne correspondent.

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_tag est une fonction qui, malgré son nom, peut être utilisée pour mettre à jour une balise de réécriture existante telle que %postname% .

Donc, si nous utilisons le code:

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

nous atteindrons notre objectif et example.com/postname~ ne ne correspondra pas à la règle de 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 effacer les règles de réécriture en visitant la page des paramètres de permalien sur le backend.

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

    
réponse donnée gmazzap 30.03.2016 - 20:01
7
  

est le comportement prévu pour WP

Oui, comme indiqué précédemment, WP_Query::get_posts() utilise sanitize_title_for_query() ( qui utilise sanitize_title() ) pour assainir le nom d'un post singulier.

En bref, une fois que le nom du message est passé par sanitize_title_for_query() , my-permalink === my-permalink~~~ en tant que sanitize_title_for_query() supprime le dernier point ~~~ . Vous pouvez tester cela en procédant comme suit:

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

puis-je désactiver cette option pour que les tildes ne correspondent pas

Ce n'est pas quelque chose que vous pouvez désactiver. Il existe dans sanitize_title() un filtre appelé sanitize_title que 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 sérieuse, alors laisser quelque chose glisser entre les mailles craintes à cause d'un mauvais assainissement peut avoir une très mauvaise influence sur l'intégrité de votre site. Le "sur assainissement" peut parfois être pénible.

Je ne sais pas trop ce que vous cherchez, mais je suppose que vous voudrez peut-être créer 404 posts avec ce tilde final, comme vous le dites, "éteignez-le". La seule façon dont je puisse penser à ce stade est de mettre fin à la requête principale lorsque nous avons ces tildes de fin. Pour cela, nous pouvons filtrer la clause posts_where de la requête principale.

LE FILTRE

Remarque: je n'ai pris en compte que les publications au singulier normales, et non les pages de couverture statiques ni les pièces jointes. Vous pouvez étendre le filtre pour l'intégrer

.
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 renvoie une page 404 lorsque nous avons une URL telle que https://mywordpresssite.com/my-permalink~~~~~~ . Vous pouvez cependant, en supprimant remove_action( 'template_redirect', 'redirect_canonical' ); du filtre, rediriger automatiquement la requête vers https://mywordpresssite.com/my-permalink et afficher la publication unique en raison de redirect_canonical() qui est relié à template_redirect qui gère la redirection des 404 générés par WordPress

    
réponse donnée Pieter Goosen 29.03.2016 - 22:46
7

Oui, il semble étrange que nous ayons le même résultat 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 méthode WP_Query::get_posts() :

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 filtre sanitize_title , 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 une seule fois ce problème en cours.

Mettre à jour

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

Voici une démonstration avec laquelle vous pourrez jouer sur votre site de test et vous 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 avoir appris cette astuce, pour obtenir le chemin actuel avec un add_query_arg( [] ) vide, à partir de @gmazzap ;-) C'est aussi noté dans le Codex. Merci encore à @gmazzap pour le rappel d’utiliser esc_url() lors de l’affichage du résultat de add_query_arg( [] ) ou esc_url_raw() lorsque, par exemple. le rediriger. Vérifiez également la référence précédente du Codex.

    
réponse donnée birgire 29.03.2016 - 15:28
3

Laissez-moi vous expliquer le traitement d’une requête par WordPress, ainsi qu’une méthode pour changer le comportement de WordPress afin d’atteindre vos objectifs en conséquence.

Analyse de la demande

Lorsque WordPress reçoit une demande, il lance un processus de dissection de la demande et de le transformer en une 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 correctement identifiée dans parse_request() (in includes/class-wp.php ). WordPress essaie de faire correspondre l'URL à l'une des règles de réécriture . Lorsque l'URL est appariée, il crée une chaîne de requête des parties de l'URL et les code (le séparant de deux barres obliques) à l'aide de urlencode() , pour éviter que des caractères spéciaux tels que & ne perturbent la chaîne de requête. Ces caractères codés vous ont peut-être amené à penser que le problème y résidait, mais ils ont été transformés en "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

Une fois que l'URL a été analysé par WordPress, il définit la classe de requête principale, WP_Query , ce qui est effectué dans la même méthode main() de la classe WP . Le boeuf de WP_Query peut être trouvé dans sa méthode get_posts() où tous les arguments de requête sont analysés et analysés et la requête SQL réelle est construite (et éventuellement exécutée).

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

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

Ceci désinfecte le post pour le récupérer depuis la table posts. 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 extraire la publication de la base de données.

Fonction de désinfection du titre du message

La fonction sanitize_title_for_query appelle sanitize_title avec les paramètres appropriés, ce qui permet d'assainir le titre. Le noyau de cette fonction applique maintenant le filtre sanitize_title :

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

Ce filtre est, dans WordPress natif, associé à une seule fonction: sanitize_title_with_dashes . J'ai décrit de manière exhaustive le fonctionnement de cette fonction, . . 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 sauf les caractères alphanumériques, les espaces, les tirets et les traits de soulignement.

Résoudre votre problème

Donc, il existe fondamentalement un seul moyen de résoudre votre problème: supprimer la fonction sanitize_title_with_dashes du filtre et la remplacer par votre propre fonction. Ce n’est 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. Il est possible que d'autres plugins connectés à ce filtre ne gèrent pas correctement la nouvelle fonctionnalité.
  3. Plus important encore : WordPress utilise le résultat de la fonction sanitize_title directement dans la requête SQL par cette ligne:

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

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

Conclusion: la résolution de votre problème n’est pas nécessaire du point de vue de la sécurité, mais si vous voulez le faire, remplacez le sanitize_title_with_dashes par votre propre fonctionnalité et prêtez attention à l’échappement SQL.

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

    
réponse donnée engelen 29.03.2016 - 15:38
3

Certaines personnes ont déjà expliqué le problème, je vais donc poster 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.
    }
});

Cependant, comme WP_Query exécutera pagename via wp_basename , il faudra faire quelque chose de différent pour les types de publication hiérarchiques, puis le désinfecter. Ainsi, query_vars['pagename'] et get_query_var('pagename') ne correspondront pas aux enfants. car ce dernier ne contiendra pas la partie parent.

Je souhaite que redirect_canonical prenne soin de cette merde.

    
réponse donnée kovshenin 02.04.2016 - 14:06
0

C’EST LA CORRECTION ... POUR LE BUG DE WORDPRESS, AJOUTEZ JUSTE LE BLOC DE MOD DE SECURATION DEBUT 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
    
réponse donnée Michael S. Howard 08.03.2018 - 13:52
-3

Vous pouvez toujours essayer d'ajouter les éléments suivants à votre fichier .htaccess :

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

La deuxième ligne ci-dessus devrait être placée sous la première ligne. Cela devrait empêcher index.php~ de s'afficher dans les URL.

    
réponse donnée Cutter 29.03.2016 - 15:39

Lire d'autres questions sur les étiquettes