Wordpress passende URLs mit nachfolgenden Tildes

11

Ich habe einen Schwachstellenbericht (1) erhalten, der darauf hindeutet, dass möglicherweise ein Sicherheitsproblem in der Art und Weise vorliegt, wie Wordpress URLs mit folgenden Tildes behandelt. Es scheint, dass der Scanner glaubt, dass die Website einige Verzeichnislisten und dergleichen bereitstellt.

Ich war überrascht, dass meine Website immer noch Inhalte auf diesen verschiedenen URLs bereitstellte. Daher habe ich einen Test durchgeführt, indem ich eine völlig leere WP-Instanz installiert, auf Permalinks "Postname" umgestellt und bestätigt habe, dass jede URL mit hinzugefügter Tilde weiterhin als interpretiert wird die URL ohne Tilde.

In der Tat eine URL wie diese:

https://mywordpresssite.com/my-permalink

Ist auch über folgende URLs zugänglich:

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

Ich stöberte ein wenig herum, um zu sehen, wo WP die Permalinks analysiert, und habe es class-wp.phpin der parse_requestMethode aufgespürt , konnte aber nicht viel weiter kommen.

Meine Frage ist, ob dies ein beabsichtigtes Verhalten für WP ist, und wenn ja, kann ich dies auf irgendeine Weise ausschalten, damit die Tildes nicht übereinstimmen? Warum sollte WP URLs mit Tildes als URL ohne diese interpretieren?

(1) Ja, jetzt haben wir alle ein paar große Hacks und Datenlecks in Großbritannien gesehen. Es ist wieder so weit, dass die "Sicherheitsleute" alle so tun, als würden sie ihren Beitrag leisten, indem sie uns Entwicklern 200-seitige Scan-Berichte geben Voller Fehlalarme und allgemeiner Probleme, von denen sie in der Erwartung nichts wissen, wenn wir diesen Bericht lesen und darauf reagieren, wird niemals etwas Schlimmes passieren.

dKen
quelle

Antworten:

13

Lass uns einfach gehen

Wenn ich OP gut verstehe, besteht Ihr Problem darin, dass URLs, die eine Tilde enthalten, überhaupt übereinstimmen.

Alle anderen Antworten konzentrieren sich auf die Tatsache, dass bei der Bereinigung von Abfragen einige Zeichen entfernt werden, bevor die Abfrage ausgeführt wird. Es sollte jedoch möglich sein, zu verhindern, dass eine Umschreiberegel unter bestimmten Umständen nicht übereinstimmt.

Und es ist machbar, nicht sehr einfach, aber machbar.

Warum passt es überhaupt?

Der Grund, warum zwei URLs dieselbe Umschreiberegel mögen example.com/postnameund mit dieser example.com/postname~übereinstimmen, liegt darin, dass die WP-Umschreiberegel für Posts das Umschreibetag verwendet %postname%, das beim Erstellen von ([^/]+)Umschreiberegeln durch den regulären Ausdruck ersetzt wird .

Das Problem ist, dass Regex ([^/]+)auch mit dem Postnamen postname~übereinstimmt und der abgefragte Name aufgrund der Bereinigung zu postnameeinem gültigen Ergebnis führt.

Wenn wir in der Lage sind, den regulären Ausdruck von ([^/]+)in ([^~/]+)Tilde zu ändern , stimmt dies nicht mehr überein. Daher verhindern wir aktiv, dass URLs, die Tilde im Beitragsnamen enthalten, übereinstimmen.

Da keine Regel übereinstimmt, wird die URL zu einem 404, was meiner Meinung nach das erwartete Verhalten sein sollte.

Matching verhindern

add_rewrite_tagist eine Funktion, die trotz ihres Namens verwendet werden kann, um ein vorhandenes Umschreibetag wie zu aktualisieren %postname%.

Also, wenn wir den Code verwenden:

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

wir werden unser Ziel erreichen und example.com/postname~wird nicht die Regel für übereinstimmen example.com/postname.

Ja, die drei Zeilen oben sind der einzige Code, den Sie benötigen .

Bevor es jedoch funktioniert, müssen Sie die Umschreiberegeln löschen, indem Sie die Seite mit den Permalink-Einstellungen im Backend aufrufen.

Beachten Sie, dass Regex ([^~/]+)verhindert , dass sich eine Tilde an einer beliebigen Stelle im Post-Namen befindet, nicht nur als nachfolgendes Zeichen. Da Post-Namen aufgrund der Bereinigung keine Tilde enthalten können, sollte dies kein Problem sein.

gmazzap
quelle
1
+1 wie die Einfachheit ;-) es sieht auch so aus, als könnten wir dies auch für andere Rauschzeichen anpassen.
Birgire
1
@birgire nicht wir alle? ;)
gmazzap
@birgire Ja, wir könnten verhindern, dass Zeichen entfernt werden sanitize_title, aber da es filterbar ist, ist es nicht möglich, eine immer gültige Lösung zu schreiben. Also ging ich spezifisch.
gmazzap
1
Diese Antwort hat bei weitem die sauberste Lösung und erklärt klar das Problem, mit dem wir konfrontiert sind. Vielen Dank - Kopfgeld an Sie!
dKen
7

ist beabsichtigtes Verhalten für WP

Ja, wie bereits erläutert, WP_Query::get_posts()wird sanitize_title_for_query()( der verwendetsanitize_title() ) verwendet, um den Postnamen eines einzelnen Posts zu bereinigen.

Kurz nach dem Post Namen durchlaufen sanitize_title_for_query(), my-permalink === my-permalink~~~so sanitize_title_for_query()entfernt das Hinter ~~~. Sie können dies folgendermaßen testen:

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

Gibt es eine Möglichkeit, dies auszuschalten, damit die Tildes nicht übereinstimmen?

Dies können Sie nicht ausschalten. Es gibt einen Filter sanitize_title()namens, mit sanitize_titledem Sie das Verhalten von ändern können sanitize_title(), aber das ist fast immer keine sehr gute Idee. Die SQL-Injection ist sehr schwerwiegend. Wenn Sie also aufgrund schlechter Hygiene etwas durch die Risse rutschen lassen, kann dies einen sehr schlechten Einfluss auf die Integrität Ihrer Site haben. "Überhygiene" kann manchmal ein Schmerz im Hintern sein.

Ich bin mir nicht sicher, wonach Sie suchen, aber ich vermute, dass Sie vielleicht 404 einzelne Posts mit dieser nachlaufenden Tilde in Ihren Worten "ausschalten" möchten. Die einzige Möglichkeit, die ich mir derzeit vorstellen kann, besteht darin, die Hauptabfrage anzuhalten, wenn wir diese nachlaufenden Tildes haben. Dazu können wir die posts_whereKlausel der Hauptabfrage filtern .

DER FILTER

Hinweis: Ich habe nur normale einzelne Beiträge berücksichtigt und keine statischen Titelseiten oder Anhänge. Sie können den Filter erweitern, um dies zu berücksichtigen

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

WENIGE ANMERKUNGEN

Der obige Filter gibt eine 404-Seite zurück, wenn wir eine URL wie haben https://mywordpresssite.com/my-permalink~~~~~~. Sie können jedoch durch Entfernen remove_action( 'template_redirect', 'redirect_canonical' );aus dem Filter die Abfrage automatisch umleiten https://mywordpresssite.com/my-permalinkund den einzelnen Beitrag anzeigen lassen, aufgrund redirect_canonical()dessen template_redirectdie Umleitung der von WordPress generierten 404-Dateien verknüpft ist

Pieter Goosen
quelle
7

Ja, es scheint seltsam, dass wir das gleiche Spiel haben sollten für:

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

und zB

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

Warum dies möglich ist, scheint dieser Teil der WP_Query::get_posts()Methode zu sein:

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

wo sanitize_title_for_query()ist definiert als:

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

Es sollte möglich sein, dies mit dem sanitize_titleFilter strenger zu gestalten , aber es ist möglicherweise keine gute Idee, die Standardausgabe zu überschreiben, basierend auf der sanitize_title_with_dashes, die hier für die Hygiene verantwortlich ist. Sie sollten in Betracht ziehen, ein Ticket zu erstellen, anstatt es zu ändern, wenn dieses Verhalten noch nicht einmal aktuell ist.

Aktualisieren

Ich frage mich, ob wir das Rauschen aus dem aktuellen Pfad sanitize_title_for_query()entfernen und bei Bedarf zur bereinigten URL umleiten könnten .

Hier ist eine Demo, mit der Sie auf Ihrer Testseite spielen und sich an Ihre Bedürfnisse anpassen können:

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

Es könnte sogar besser sein, sanitize_title_with_dashes()direkt zu verwenden, um die Filter zu vermeiden und zu ersetzen:

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

mit:

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

ps: Ich glaube, ich habe diesen Trick gelernt, um den aktuellen Pfad mit einem leeren add_query_arg( [] )von @gmazzap zu erhalten ;-) Dies ist auch im Codex vermerkt . Nochmals vielen Dank an @gmazzap für die Erinnerung an die Verwendung esc_url()beim Anzeigen der Ausgabe add_query_arg( [] )oder esc_url_raw()beim z. B. Umleiten. Überprüfen Sie auch die vorherige Codex-Referenz.

Birgire
quelle
+1 Zur Verdeutlichung werden diese Sonderzeichen entfernt. Obwohl die seltsame Version der URL in der Adressleiste angezeigt wird, funktioniert WordPress mit der tatsächlichen URL, weshalb die Anforderung an erster Stelle funktioniert. Ich sehe bei diesem Verhalten keine Sicherheitsrisiken für Bürgermeister.
Nicolai
1
Ja, ich denke, wir sollten uns nicht mit dem Hygienefilter
anlegen
1
Sicher, es sei denn, es gibt einen sehr guten Grund, es ist ein Aufwand, der sich nicht lohnt. Um nicht zu sagen, es ist höchstwahrscheinlich nicht gut für die geistige Gesundheit der Entwickler - nicht einmal für die technische Sanitärversorgung. Nur meine zwei Cent.
Nicolai
1
@birgire, wenn wie verwendet, add_query_argmüssen mit esc_urloder esc_url_rawum Sicherheitsprobleme zu entkommen ...
gmazzap
ahh ja danke, wenn ich mich richtig erinnere, war dies ein Sicherheitsproblem, das in vielen Plugins kürzlich entdeckt wurde @gmazzap
birgire
3

Lassen Sie mich die Verarbeitung einer Anfrage durch WordPress und eine Methode zum Ändern des Verhaltens von WordPress erläutern, um Ihre Ziele entsprechend zu erreichen.

Analysieren der Anforderung

Wenn WordPress eine Anfrage erhält, beginnt es damit, die Anfrage zu zerlegen und in eine Seite umzuwandeln. Der Kern dieses Prozesses beginnt mit dem WP::main()Aufruf der WordPress-Hauptabfragemethode . Diese Funktion analysiert die Abfrage, wie Sie korrekt identifiziert haben, in parse_request()(in includes/class-wp.php). Dort versucht WordPress, die URL mit einer der Umschreiberegeln abzugleichen . Wenn die URL übereinstimmt, wird eine Abfragezeichenfolge der URL-Teile erstellt und diese Teile (alles zwischen zwei Schrägstrichen) mit codiert urlencode(), um zu verhindern, dass Sonderzeichen &die Abfragezeichenfolge durcheinander bringen. Diese codierten Zeichen haben Sie möglicherweise zu der Annahme veranlasst, dass das Problem dort liegt, aber sie werden beim Parsen der Abfragezeichenfolge tatsächlich in ihre entsprechenden "echten" Zeichen umgewandelt.

Ausführen der der Anforderung zugeordneten Abfrage

Nachdem WordPress die URL analysiert hat, richtet es die Hauptabfrageklasse ein WP_Query, die auf dieselbe main()Weise wie die WPKlasse ausgeführt wird. Das Rindfleisch von WP_Querykann in seiner get_posts()Methode gefunden werden, in der alle Abfrageargumente analysiert und bereinigt werden und die eigentliche SQL-Abfrage erstellt (und schließlich ausgeführt) wird.

Bei diesem Verfahren wird in Zeile 2730 der folgende Code ausgeführt:

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

Dadurch wird der Beitrag bereinigt, um ihn aus der Beitragstabelle abzurufen. Die Ausgabe von Debug-Informationen innerhalb der Schleife zeigt, dass hier das Problem liegt: Ihr Beitragsname my-permalink~wird in transformiert my-permalink, der dann zum Abrufen des Beitrags aus der Datenbank verwendet wird.

Die Funktion zur Bereinigung nach dem Titel

Die Funktion sanitize_title_for_queryruft sanitize_titlemit den richtigen Parametern auf, wodurch der Titel bereinigt wird. Der Kern dieser Funktion ist nun das Anwenden des sanitize_titleFilters:

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

Diesem Filter ist in nativem WordPress eine einzige Funktion zugeordnet : sanitize_title_with_dashes. Ich habe einen umfassenden Überblick über die Funktionsweise dieser Funktion geschrieben, den Sie hier finden . In dieser Funktion ist die Zeile, die Ihr Problem verursacht,

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

In dieser Zeile werden alle Zeichen außer alphanumerischen Zeichen, Leerzeichen, Bindestrichen und Unterstrichen entfernt.

Lösen Sie Ihr Problem

Grundsätzlich gibt es einen einzigen Weg, um Ihr Problem zu lösen: Entfernen Sie die sanitize_title_with_dashesFunktion aus dem Filter und ersetzen Sie sie durch Ihre eigene Funktion. Das ist eigentlich gar nicht so schwer, aber :

  1. Wenn WordPress den internen Prozess der Bereinigung von Titeln ändert, hat dies erhebliche Auswirkungen auf Ihre Website.
  2. Andere Plugins, die in diesen Filter eingebunden sind, verarbeiten die neue Funktionalität möglicherweise nicht richtig.
  3. Am wichtigsten : WordPress verwendet das Ergebnis der sanitize_titleFunktion direkt in der SQL-Abfrage in dieser Zeile:

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

    Sollten Sie jemals in Betracht ziehen, den Filter zu ändern, stellen Sie sicher, dass Sie den Titel ordnungsgemäß maskieren, bevor er in der Abfrage verwendet wird!

Fazit: Die Lösung Ihres Problems ist aus Sicherheitsgründen nicht erforderlich. Sollten Sie dies jedoch tun möchten, ersetzen Sie die sanitize_title_with_dashesdurch Ihre eigene Funktionalität und achten Sie auf die SQL-Flucht.

Hinweis: Alle Dateinamen und Zeilennummern entsprechen den WordPress 4.4.2-Dateien.

engelen
quelle
3

Einige Leute haben das Problem bereits erklärt, daher werde ich nur eine alternative Lösung veröffentlichen. Sollte ziemlich selbsterklärend sein.

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

Sie müssen jedoch etwas anderes für hierarchische Beitragstypen tun, da WP_Querydiese pagenamedurchlaufen wp_basenameund dann bereinigt werden, also query_vars['pagename']undget_query_var('pagename') werden für Kinder nicht passen becuase letztere nicht das übergeordnete Teil enthalten.

Ich wünschte, ich hätte mich redirect_canonicalnur um diesen Mist gekümmert.

Kovshenin
quelle
0

Dies ist das Update ... für den Fehler von WORDPRESS fügen Sie einfach den BEGIN-Sicherheitsmod-Block über dem von Wordpress generierten BLOCK hinzu.

# 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
quelle
-3

Sie können jederzeit versuchen, Folgendes zu Ihrer .htaccessDatei hinzuzufügen :

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

Die zweite Zeile oben sollte direkt unter der ersten angezeigten Zeile stehen. Es sollte verhindert werden index.php~, dass in URLs angezeigt wird.

Huginn
quelle
Das funktioniert nicht für die hübschen Permalinks, um die es in der Frage geht, oder?
Nicolai