Wordpress pasujące adresy URL ze znakami tylnymi


11

Otrzymałem raport o usterce (1), który wydaje się sugerować, że może istnieć problem bezpieczeństwa w sposobie, w jaki Wordpress obsługuje adresy URL z następującymi tyldami. Wygląda na to, że skaner uważa, że ​​witryna może wyświetlać niektóre katalogi i tym podobne.

Byłem zaskoczony, że moja witryna wciąż wyświetla treść pod tymi różnymi adresami URL, więc zrobiłem test, instalując całkowicie pustą instancję WP, przełączając się na bezpośrednie linki „Nazwa postu” i potwierdziłem, że tak, każdy adres URL z dodaną tyldą nadal jest interpretowany jako adres URL bez tyldy.

Rzeczywiście taki adres URL:

https://mywordpresssite.com/my-permalink

Jest również dostępny z następującymi adresami URL:

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

Rozejrzałem się trochę, żeby zobaczyć, gdzie WP analizuje permalinki, i wyśledziłem to class-wp.phpw parse_requestmetodzie, ale nie mogłem pójść dalej.

Moje pytanie brzmi, czy jest to zamierzone zachowanie dla WP, a jeśli tak, to czy jest jakiś sposób, aby to wyłączyć, aby tyldy nie były dopasowane? Dlaczego WP interpretuje adresy URL z tyldami jako URL bez nich?

(1) Tak, teraz wszyscy widzieliśmy kilka poważnych włamań i wycieków danych w Wielkiej Brytanii, to znowu ten czas, gdy „ochroniarze” wszyscy udają, że robią wszystko, przekazując nam deweloperom 200-stronicowe raporty ze skanowania pełen fałszywie pozytywnych i ogólnych problemów, o których nie wiedzą nic w oczekiwaniu, jeśli przeczytamy i zareagujemy na wspomniany raport, nic złego się nigdy nie wydarzy.

Odpowiedzi:


13

Przejdźmy prosto

Jeśli dobrze rozumiem OP, twój problem polega na tym, że adresy URL zawierające tyldę są w ogóle dopasowane.

Wszystkie pozostałe odpowiedzi koncentrują się na tym, że czyszczenie przed zapytaniem usuwa niektóre znaki przed wykonaniem zapytania, jednak należy być w stanie zapobiec sytuacji, w której reguła przepisywania nie będzie pasować w pewnych okolicznościach.

I jest to wykonalne, niezbyt łatwe, ale wykonalne.

Dlaczego to pasuje?

Powodem, dla którego dwa adresy URL lubią tę samą regułę przepisywania example.com/postnamei są do niej example.com/postname~zgodne, jest to, że reguła WP przepisywania postów używa znacznika przepisywania, %postname%który jest zastępowany wyrażeniem regularnym ([^/]+)podczas tworzenia reguł przepisywania.

Problem polega na tym, że wyrażenie regularne ([^/]+)pasuje również do nazwy postu, postname~a z powodu dezynfekcji nazwa zapytania zostanie postnamezakończona prawidłowym wynikiem.

Oznacza to, że jeśli uda nam się zmienić regex z ([^/]+)na ([^~/]+)tylda, nie będzie już pasować, więc aktywnie zapobiegamy dopasowaniu adresów URL zawierających tyldę w nazwie postu.

Ponieważ żadna reguła nie będzie pasować, adres URL skończy się na 404, co, jak sądzę, powinno być oczekiwanym zachowaniem.

Zapobiegaj dopasowywaniu

add_rewrite_tagjest funkcją, która pomimo nazwy może być używana do aktualizacji istniejącego znacznika przepisywania, takiego jak %postname%.

Jeśli więc użyjemy kodu:

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

osiągniemy nasz cel i nieexample.com/postname~ będziemy pasować do reguły .example.com/postname

Tak, tak, powyższe 3 wiersze to jedyny potrzebny kod .

Jednak zanim to zadziała, będziesz musiał opróżnić reguły przepisywania, odwiedzając stronę ustawień permalink na backend.

Pamiętaj, że wyrażenie regularne ([^~/]+)zapobiega umieszczaniu tyldy w dowolnym miejscu w nazwie postu, nie tylko jako znaku końcowego, ale ponieważ nazwy postów nie mogą tak naprawdę zawierać tyldy z powodu dezynfekcji, nie powinno to stanowić problemu.


1
+1 podobna do prostoty ;-) Wygląda również na to, że możemy to dostosować również do innych znaków szumu.
birgire

1
@ Birgire nie wszyscy? ;)
gmazzap

@ birgire tak, moglibyśmy zapobiegać rozebraniu każdej postaci sanitize_title, ale ponieważ jest filtrowalna, nie jest możliwe napisanie zawsze ważnego rozwiązania. Więc poszedłem konkretnie.
gmazzap

1
Ta odpowiedź ma zdecydowanie najczystsze rozwiązanie i jasno wyjaśnia problem, przed którym stoimy. Wielkie dzięki - nagroda dla Ciebie!
dKen

7

jest zamierzonym zachowaniem dla WP

Tak, jak już wyjaśniono, WP_Query::get_posts()używa sanitize_title_for_query()( który używasanitize_title() ) do dezynfekcji nazwy pojedynczego postu.

Krótko mówiąc, po przejściu nazwy wpisu sanitize_title_for_query(), my-permalink === my-permalink~~~ponieważ sanitize_title_for_query()usuwa końcowe ~~~. Możesz to przetestować, wykonując następujące czynności:

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

czy jest jakiś sposób, żeby to wyłączyć, aby tyldy nie były dopasowane

Nie możesz tego wyłączyć. Jest to filtr sanitize_title()o nazwie sanitize_title, które można wykorzystać do zmiany zachowania sanitize_title(), ale nie jest to prawie zawsze bardzo dobry pomysł. Wstrzyknięcie SQL jest bardzo poważne, więc przepuszczenie czegoś przez pęknięcia z powodu złych warunków sanitarnych może mieć naprawdę zły wpływ na integralność witryny. „Nadmierne warunki sanitarne” mogą czasami być uciążliwe.

Nie jestem pewien, o co ci chodzi, ale podejrzewam, że możesz chcieć 404 pojedynczych postów z tylną tyldą, twoimi słowami „wyłącz to”. Jedynym sposobem, jaki mogę wymyślić na tym etapie, jest zatrzymanie głównego zapytania, gdy mamy te tyldy tyldy. W tym celu możemy filtrować posts_whereklauzulę głównego zapytania.

FILTR

Uwaga: rozważałem tylko zwykłe pojedyncze posty, a nie statyczne pierwsze strony lub załączniki, możesz rozszerzyć filtr, aby to uwzględnić

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

KILKA UWAG

Powyższy filtr zwróci stronę 404, gdy będziemy mieć adres URL podobny do tego https://mywordpresssite.com/my-permalink~~~~~~. Możesz jednak, usuwając remove_action( 'template_redirect', 'redirect_canonical' );z filtra, automatycznie przekierować zapytanie https://mywordpresssite.com/my-permalinki wyświetlić pojedynczy post, dzięki redirect_canonical()któremu zaczepi się, do template_redirectktórego uchwytów przekierowanie wygenerowanych 404 WordPressów


7

Tak, wydaje się dziwne, że powinniśmy mieć takie samo dopasowanie do:

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

i np

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

Dlaczego jest to możliwe, wydaje się, że ta część z WP_Query::get_posts()metodą:

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

gdzie sanitize_title_for_query()jest zdefiniowany jako:

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

Powinno być możliwe zaostrzenie tego sanitize_titlefiltru, ale może nie być dobrym pomysłem zastąpienie domyślnego wyjścia, w oparciu o sanitize_title_with_dashes, które jest odpowiedzialne za warunki sanitarne tutaj. Powinieneś rozważyć utworzenie biletu zamiast jego zmiany, jeśli nie ma już aktualnych informacji na temat tego zachowania.

Aktualizacja

Zastanawiam się, czy moglibyśmy usunąć szum z prądu ścieżki sanitize_title_for_query()i w razie potrzeby przekierować do oczyszczonego adresu URL?

Oto demo, z którym możesz zagrać na swojej stronie testowej i dostosować się do swoich potrzeb:

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

Może być nawet lepiej użyć sanitize_title_with_dashes()bezpośrednio, aby uniknąć filtrów i wymienić:

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

z:

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

ps: Myślę, że nauczyłem się tej sztuczki, aby uzyskać bieżącą ścieżkę z pustym add_query_arg( [] ), od @gmazzap ;-) Jest to również odnotowane w Kodeksie. Jeszcze raz dziękuję @gmazzap za przypomnienie o używaniu esc_url()podczas wyświetlania wyniku add_query_arg( [] )lub esc_url_raw()np. Przekierowania go. Sprawdź również poprzednie odniesienie do Kodeksu.


+1 Aby wyjaśnić, te znaki specjalne są usuwane, więc chociaż dziwna wersja adresu URL jest widoczna na pasku lokalizacji, WordPress działa z rzeczywistym adresem URL, dlatego żądanie działa w pierwszej kolejności. Przy takim zachowaniu nie widzę żadnych zagrożeń bezpieczeństwa burmistrza.
Nicolai

1
tak, myślę, że nie powinniśmy zadzierać z filtrem sanitarnym, aby zmienić ten @ialocin
birgire

1
Oczywiście, o ile nie ma bardzo dobrego powodu, jest to kłopot, nie warto. Nie mówiąc, najprawdopodobniej nie jest to dobre dla zdrowia psychicznego deweloperów - nawet nie dostanie się do technicznych warunków sanitarnych. Tylko moje dwa centy.
Nicolai

1
@ birgire, gdy jest używany w ten sposób, add_query_argmusi być zabezpieczony przed ucieczką esc_urllub w esc_url_rawcelu uniknięcia problemów bezpieczeństwa ...
gmazzap

Ach tak, dziękuję, jeśli dobrze pamiętam, to był problem bezpieczeństwa wykryty w wielu wtyczkach ostatnio @gmazzap
birgire

3

Pozwól, że wyjaśnię przetwarzanie żądania przez WordPress oraz metodę zmiany zachowania WordPress, aby odpowiednio zrealizować twoje cele.

Przetwarzanie żądania

Kiedy WordPress otrzymuje żądanie, rozpoczyna proces jego analizy i przekształcenia w stronę. Rdzeń tego procesu rozpoczyna się, gdy WP::main()wywoływana jest główna metoda zapytania WordPress . Ta funkcja analizuje zapytanie, jak poprawnie zidentyfikowano, w parse_request()(in includes/class-wp.php). Tam WordPress próbuje dopasować adres URL do jednej z reguł przepisywania . Gdy adres URL jest dopasowany, tworzy ciąg zapytania z części URL i koduje te części (wszystko między dwoma ukośnikami) za pomocą urlencode(), aby zapobiec wprowadzaniu przez znaki specjalne, takie jak &bałagan zapytania. Te zakodowane znaki mogły sprawić, że pomyślałeś, że problem tam występował, ale w rzeczywistości zostały one przekształcone w odpowiadające im „prawdziwe” znaki podczas analizowania ciągu zapytania.

Uruchomienie zapytania związanego z żądaniem

Po przeanalizowaniu adresu URL WordPress ustawia główną klasę zapytania WP_Query, co odbywa się w tej samej main()metodzie WPklasy. Wołowinę WP_Querymożna znaleźć w jej get_posts()metodzie, w której wszystkie argumenty zapytania są analizowane i odkażane, a rzeczywiste zapytanie SQL jest konstruowane (i ostatecznie uruchamiane).

W metodzie tej w wierszu 2730 wykonywany jest następujący kod:

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

To dezynfekuje post za pobranie go z tabeli postów. Wyprowadzanie informacji o debugowaniu w pętli pokazuje, że na tym polega problem: nazwa posta my-permalink~zostaje przekształcona na my-permalink, która jest następnie używana do pobrania postu z bazy danych.

Funkcja odkażania tytułu postu

Funkcja sanitize_title_for_querywywołuje sanitize_titleodpowiednie parametry, które następnie odkażają tytuł. Teraz rdzeniem tej funkcji jest zastosowanie sanitize_titlefiltra:

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

Filtr ten ma w natywnej WordPress, jedna funkcja z nim związane: sanitize_title_with_dashes. Napisałem obszerny przegląd działania tej funkcji, który można znaleźć tutaj . W tej funkcji linia powodująca problem to

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

Ta linia usuwa wszystkie znaki oprócz znaków alfanumerycznych, spacji, łączników i znaków podkreślenia.

Rozwiązywanie problemu

Jest więc zasadniczo jeden sposób rozwiązania problemu: usunięcie sanitize_title_with_dashesfunkcji z filtra i zastąpienie jej własną funkcją. Nie jest to wcale takie trudne, ale :

  1. Gdy WordPress zmieni wewnętrzny proces odkażania tytułów, będzie to miało duży wpływ na twoją stronę.
  2. Inne wtyczki podpinające się do tego filtra mogą niepoprawnie obsługiwać nową funkcjonalność.
  3. Co najważniejsze : WordPress używa wyniku sanitize_titlefunkcji bezpośrednio w zapytaniu SQL w tym wierszu:

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

    Jeśli kiedykolwiek zastanowisz się nad zmianą filtru, upewnij się, że właściwie unikniesz tytułu przed użyciem go w zapytaniu!

Wniosek: rozwiązanie problemu nie jest konieczne ze względów bezpieczeństwa, ale jeśli chcesz to zrobić, zamień na sanitize_title_with_dasheswłasną funkcjonalność i zwróć uwagę na ucieczkę SQL.

Uwaga: wszystkie nazwy plików i numery linii odpowiadają plikom WordPress 4.4.2.


3

Niektórzy ludzie już wyjaśnili problem, więc opublikuję alternatywne rozwiązanie. Powinny być dość oczywiste.

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

Trzeba będzie zrobić coś innego dla hierarchicznych typów pocztowych chociaż, ponieważ WP_Querybędzie prowadzony pagenameprzez wp_basenamea następnie zdezynfekować go, tak query_vars['pagename']i get_query_var('pagename')nie będzie pasował do dzieci ponieważ posiadał ten nie będzie zawierał część nadrzędną.

Chciałbym redirect_canonicaltylko zająć się tym gównem.


0

TO JEST NAPRAWA ... DLA BŁĘDU WORDPRESS PO PROSTU DODAJ blok modów bezpieczeństwa BEGIN powyżej BLOKU generowanego przez 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

Zawsze możesz spróbować dodać następujące elementy do swojego .htaccesspliku:

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

Druga linia powyżej powinna przejść bezpośrednio pod pierwszą pokazaną linią. Powinno to uniemożliwiać index.php~wyświetlanie w adresach URL.


To nie działa na ładne permalinki, o które chodzi w pytaniu, prawda?
Nicolai
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.