Wordpress eşleşen URL'leri takip eden tildlerle


11

Wordpress'in aşağıdaki tildlerle URL'leri işleme biçiminde bir güvenlik sorunu olabileceğini ima eden bir güvenlik açığı raporu (1) verildi. Tarayıcı, web sitesinin bazı dizin listelerini ve benzerlerini sunduğunu düşünüyor gibi görünüyor.

Web sitemin hala bu farklı URL'lerde içerik sunduğuna şaşırdım, bu yüzden tamamen boş bir WP örneği yükleyerek bir test yaptım, "Posta adı" kalıcı bağlantılarına geçtim ve evet, eklenen tilde'ye sahip herhangi bir URL'nin hala yorumlandığını doğruladım yaklaşık işareti olmayan URL.

Gerçekten, böyle bir url:

https://mywordpresssite.com/my-permalink

Aşağıdaki URL'lerle de erişilebilir:

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

WP'nin kalıcı bağlantıları nerede ayrıştırdığını görmek için biraz etrafta dolaştım class-wp.phpve parse_requestyöntemde izledim , ancak bundan daha fazlasını alamadım.

Benim sorum bu WP için amaçlanan davranış olup olmadığını ve eğer öyleyse, tildes eşleşmez böylece bunu kapatabilirsiniz herhangi bir yolu var mı? WP, tilde içeren URL'leri neden onlar olmadan bir URL olarak yorumlasın ki?

(1) Evet, şimdi Birleşik Krallık'ta birkaç büyük hack ve veri sızıntısı gördük, o zaman yine "güvenlik" adamlarının bize geliştiricilere 200 sayfalık tarama raporları göndererek üzerlerine düşeni yaptıklarını iddia ettikleri zaman yanlış pozitif ve jenerik meselelerle dolu, eğer bu raporu okuyup harekete geçersek, hiçbir şey bilmiyorlardı, kötü bir şey olmayacak.

Yanıtlar:


13

Hadi basit olalım

OP'yi iyi anlarsam, sorununuz tilde içeren URL'lerin hiç eşleşmemesidir.

Diğer tüm yanıtlar, sorgu için dezenfekte etmenin, sorguyu gerçekleştirmeden önce bazı karakterleri çıkarmasına odaklanır; ancak, bir yeniden yazma kuralının bazı durumlarda eşleşmemesini engelleyebilmelidir.

Ve yapılabilir, çok kolay değil, ama yapılabilir.

İlk etapta neden uyuşuyor?

İki url'nin aynı yeniden yazma kuralını beğenmesinin example.com/postnameve example.com/postname~eşleştirmesinin nedeni, yazılar için WP yeniden yazma kuralının, yeniden yazma kuralları oluşturulduğunda %postname%normal ifade ile değiştirilen yeniden yazma etiketini kullanmasıdır ([^/]+).

Sorun, normal ifadenin posta ([^/]+)adıyla da eşleşmesidir postname~ve sanitasyon nedeniyle sorgulanan ad postnamegeçerli bir sonuçla sonuçlanacaktır.

Biz gelen regex değiştirmek mümkün olup olmadığını anlamına gelir ([^/]+)için ([^~/]+)aktif sonrası adına tilde içeren URL'ler önlemek böylece tilde artık Eşleşmeyecek eşleştirilecek.

Hiçbir kural eşleşmeyeceğinden, url 404 olacak ve bu da beklenen davranış olmalı diye düşünüyorum.

Eşleşmeyi önle

add_rewrite_tag, adına rağmen mevcut bir yeniden yazma etiketini güncellemek için kullanılabilen bir işlevdir %postname%.

Yani, eğer kodu kullanırsak:

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

hedefimize ulaşacağız ve example.com/postname~olacak değil kuralını maç example.com/postname.

Yani, evet, yukarıdaki 3 satır ihtiyacınız olan tek kod .

Ancak, çalışmadan önce arka uçtaki kalıcı bağlantı ayarları sayfasını ziyaret ederek yeniden yazma kurallarını temizlemeniz gerekir.

Normal ifade ([^~/]+), bir tilde öğesinin yalnızca izleyen karakter olarak değil, ad adında herhangi bir yerde olmasını önlediğini, ancak posta adlarının sterilizasyon nedeniyle gerçekte tilde içeremediğinden, bu bir sorun olmamalıdır.


1
+ 1 sadelik gibi ;-) Ayrıca bunu diğer gürültü karakterleri için de ayarlayabiliriz gibi görünüyor.
16'da birgire

1
@birgire hepimiz değil mi? ;)
gmazzap

@birgire evet, sıyrılan herhangi bir karakteri önleyebiliriz sanitize_title, ancak filtrelenebilir olduğu için her zaman geçerli bir çözüm yazmak mümkün değildir. Bu yüzden spesifik olarak gittim.
gmazzap

1
Bu cevap açık ara en temiz çözüme sahiptir ve karşılaştığımız sorunu açık bir şekilde açıklamaktadır. Çok teşekkürler - sana lütuf!
dKen

7

WP için amaçlanan davranış

Evet, daha önce açıklandığı gibi, tekil bir gönderinin posta adını sterilize etmek için WP_Query::get_posts()kullanır sanitize_title_for_query()( kullanırsanitize_title() ).

Kısacası, sonrası adı sonra geçirilir sanitize_title_for_query(), my-permalink === my-permalink~~~olarak sanitize_title_for_query()uzaklaşmaların sondaki ~~~. Bunu aşağıdakileri yaparak test edebilirsiniz:

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

bunu kapatmanın herhangi bir yolu var mı, böylece tildler eşleşmiyor

Bu kapatabileceğiniz bir şey değil. Orada bir filtredir sanitize_title()denilen sanitize_titleEğer davranışını değiştirmek için kullanabileceğiniz sanitize_title(), ama neredeyse her zaman çok iyi bir fikir değildir. SQL enjeksiyonu çok ciddi, bu nedenle kötü sanitasyon nedeniyle bir şeyin çatlaklardan kaymasına izin vermek sitenizin bütünlüğü üzerinde gerçekten kötü bir etkiye sahip olabilir. "Aşırı sanitasyon" bazen popoda bir ağrı olabilir.

Neyin peşinde olduğunuzdan emin değilim, ama bu son işaretiyle 404 tekil gönderi yapmak istediğinizden şüpheleniyorum, sözleriyle "kapat". Bu aşamada düşünebildiğim tek yol, bu takip eden tildlerimiz olduğunda ana sorguyu durdurmaktır. Bunun için posts_whereana sorgunun maddesini filtreleyebiliriz .

FİLTRE

Not: Statik ön sayfaları veya ekleri değil, yalnızca normal tekil gönderileri düşündüm, bunu dahil etmek için filtreyi genişletebilirsiniz

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

BİRKAÇ NOTLAR

Yukarıdaki filtre, benzer bir URL'miz olduğunda 404 sayfası döndürür https://mywordpresssite.com/my-permalink~~~~~~. Bununla birlikte, kaldırarak olabilir remove_action( 'template_redirect', 'redirect_canonical' );filtresinden, otomatik bir sorgu yönlendirmek https://mywordpresssite.com/my-permalinkve nedeniyle tek yayını göstermek redirect_canonical()için çengel olan template_redirectWordPress'in kolları yönlendirme 404 's oluşturulan hangi


7

Evet, aynı eşleşmeye sahip olmamız garip görünüyor:

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

ve örneğin

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

Bunun neden mümkün olduğu, yöntemin bu parçası gibi görünüyor WP_Query::get_posts():

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

burada sanitize_title_for_query()şu şekilde tanımlanır:

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

Bunu sanitize_titlefiltreyle daha sıkı yapmak mümkün olmalıdır , ancak sanitize_title_with_dashesburadaki sanitasyondan sorumlu olan varsayılan çıkışı geçersiz kılmak iyi bir fikir olmayabilir . Bu davranış hakkında zaten bir kez mevcut değilse, değiştirmek yerine bir bilet oluşturmayı düşünmelisiniz.

Güncelleme

Acaba yolu ile akım mevcut gürültü temizlemek sanitize_title_for_query()ve gerekirse temizlenmiş url yönlendirmek olabilir?

Test sitenizde oynayabileceğiniz ve ihtiyaçlarınıza göre ayarlayabileceğiniz bir demo:

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

sanitize_title_with_dashes()Filtrelerden kaçınmak ve değiştirmek için doğrudan kullanmak daha iyi olabilir :

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

ile:

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

ps: Sanırım şu yolu boş bir ile almak için add_query_arg( [] ), @gmazzap ;-) bu hileyi öğrendim Bu da Codex not edilir. Çıkışını esc_url()görüntülerken add_query_arg( [] )veya esc_url_raw()örneğin yeniden yönlendirirken kullanmanın hatırlatılması için @gmazzap'a tekrar teşekkürler . Bunun için önceki Kodeks referansını da kontrol edin.


+1 Sadece açıklığa kavuşturmak için, bu özel karakterler kaldırılır, bu nedenle URL'nin garip sürümü konum çubuğunda görünür olsa da, WordPress gerçek URL ile çalışır, bu nedenle istek ilk etapta çalışır. Bu davranışta belediye başkanı güvenlik riski görmüyorum.
Nicolai

1
evet Sanırım bu @ialocin değiştirmek için sanitasyon filtresi ile karışıklık olmamalıdır
birgire

1
Tabii, çok iyi bir sebep olmadığı sürece buna değmeyen bir güçlük. Söylemek değil, büyük olasılıkla geliştiriciler akıl sağlığı için iyi değil - hatta teknik sağlık hizmetlerine girmemek bile. Sadece benim iki sent.
Nicolai

1
@birgire gibi kullanıldığında güvenlik sorunları önlemek veya önlemek add_query_argiçin kaçmak gerekir ...esc_urlesc_url_raw
gmazzap

ahh evet teşekkürler, doğru hatırlıyorsam bu son zamanlarda birçok eklentide keşfedilen bir güvenlik sorunuydu @gmazzap
birgire

3

WordPress'in 'bir isteğin işlenmesi ve WordPress'in davranışını değiştirme yöntemini açıklayayım.

İsteği ayrıştırma

WordPress bir istek aldığında, isteği kesip bir sayfaya dönüştürme işlemini başlatır. Bu işlemin çekirdeği, WordPress ana sorgu yöntemi WP::main()çağrıldığında başlar. Bu işlev, doğru olarak tanımladığınız şekilde sorguyu parse_request()(in includes/class-wp.php) içinde ayrıştırır . Burada, WordPress URL'yi yeniden yazma kurallarından biriyle eşleştirmeye çalışır . URL eşleştiğinde, URL parçalarının bir sorgu dizesini oluşturur ve sorgu dizesini bozmak urlencode()gibi özel karakterlerin kullanılmasını önlemek için bu parçaları (iki eğik çizgi arasındaki her şey) kullanarak kodlar &. Bu kodlanmış karakterler, sorunun orada yaşadığını düşünmenize neden olmuş olabilir, ancak sorgu dizesini ayrıştırırken aslında karşılık gelen "gerçek" karakterlere dönüştürülürler.

İstekle ilişkili sorguyu çalıştırma

WordPress URL'yi ayrıştırdıktan sonra, sınıfın WP_Queryaynı main()yönteminde yapılan ana sorgu sınıfını ayarlar WP. Sığır eti, tüm sorgu bağımsız değişkenlerinin ayrıştırıldığı ve sterilize edildiği ve gerçek SQL sorgusunun oluşturulduğu (ve sonunda çalıştırıldığı) yönteminde WP_Querybulunabilir get_posts().

Bu yöntemde, 2730 satırında aşağıdaki kod yürütülür:

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

Bu, yayını mesajlar tablosundan getirmek için dezenfekte eder. Döngü içinde hata ayıklama bilgisinin çıktısı, sorunun burada yaşandığını gösterir: posta adınız, my-permalink~dönüştürülür ve my-permalinkdaha sonra posta veritabanından alınmak için kullanılır.

Post başlık sanitasyon fonksiyonu

İşlev , başlığı sterilize etmeye devam eden uygun parametrelerle sanitize_title_for_queryçağırır sanitize_title. Şimdi bu işlevin özü sanitize_titlefiltreyi uyguluyor :

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

Bu filtre, doğal WordPress bir fonksiyon olarak, kendisine bağlı olan: sanitize_title_with_dashes. Burada bulunan bu işlevin ne yaptığına dair kapsamlı bir genel bakış yazdım . Bu işlevde, soruna neden olan çizgi

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

Bu satır alfasayısal karakterler, boşluklar, kısa çizgiler ve alt çizgiler hariç tüm karakterleri ayırır.

Sorununuzu çözme

Bu nedenle, temel olarak sorununuzu çözmenin tek bir yolu vardır: sanitize_title_with_dashesişlevi filtreden kaldırmak ve yerine kendi işleviniz koymak . Bunu yapmak o kadar da zor değil, ama :

  1. WordPress, başlıkların sterilize edilmesi sürecini değiştirdiğinde, bunun web siteniz üzerinde büyük etkileri olacaktır.
  2. Bu filtreye bağlanan diğer eklentiler yeni işlevselliği doğru şekilde işlemeyebilir.
  3. En önemlisi : WordPress, sanitize_titleişlevin sonucunu doğrudan bu satırdaki SQL sorgusunda kullanır :

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

    Filtreyi değiştirmeyi düşünürseniz, sorguda kullanılmadan önce başlıktan uygun şekilde kaçtığınızdan emin olun!

Sonuç: güvenlik söz konusu olduğunda sorununuzu çözmek gerekli değildir, ancak bunu yapmak istiyorsanız, sanitize_title_with_dasheskendi işlevinizle değiştirin ve SQL çıkışına dikkat edin.

Not: Tüm dosya adları ve satır numaraları WordPress 4.4.2 dosyalarına karşılık gelir.


3

Bazı insanlar sorunu zaten açıkladı, bu yüzden sadece alternatif bir çözüm göndereceğim. Kendini açıklayıcı olmalı.

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

O zamandan beri, gerçi hiyerarşik sonrası türleri için biraz farklı bir şey yapmak zorunda kalacaktır WP_Queryçalışacaktır pagenamearacılığıyla wp_basenamesonra ve onu sterilize yüzden, query_vars['pagename']ve get_query_var('pagename')ikincisi ana kısmını içermez çünkü çocuklar için uymaz.

Keşke redirect_canonicalbu saçmalıklarla ilgilenmiş olsaydım .


0

BU DÜZELTME ... WORDPRESS'İN HATASI İÇİN SADECE BEGIN güvenlik mod bloğunu Wordpress Üretilen BLOK'un üstüne EKLEYİN.

# 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

Her zaman .htaccessdosyanıza aşağıdakileri eklemeyi deneyebilirsiniz :

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

Yukarıdaki ikinci satır, gösterilen ilk satırın hemen altına gitmelidir. index.php~URL'lerde gösterilmesini engellemelidir .


Bu, sorunun söz konusu olduğu kalıcı perdeler için geçerli değil, değil mi?
Nicolai
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.