URL yang cocok dengan wordpress dengan trailing tildes


11

Saya telah diberikan laporan kerentanan (1) yang tampaknya menyiratkan bahwa mungkin ada masalah keamanan dalam cara Wordpress menangani URL dengan tildes berikut. Tampaknya pemindai berpikir situs web tersebut mungkin menyajikan beberapa daftar direktori dan semacamnya.

Saya terkejut bahwa situs web saya masih menyajikan konten pada URL yang berbeda, jadi saya melakukan tes dengan menginstal contoh WP yang benar-benar kosong, beralih ke permalink "Nama pos", dan mengonfirmasi bahwa ya, URL apa pun dengan tilde tambahan masih akan ditafsirkan sebagai URL tanpa tilde.

Memang, url seperti ini:

https://mywordpresssite.com/my-permalink

Juga dapat diakses dengan URL berikut:

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

Saya mencari-cari sedikit di mana WP mem-parsing permalinks, dan saya melacaknya class-wp.phpdalam parse_requestmetode, tetapi tidak bisa lebih jauh dari itu.

Pertanyaan saya adalah apakah ini dimaksudkan untuk perilaku WP, dan jika demikian, apakah ada cara saya bisa mematikan ini sehingga tildes tidak cocok? Mengapa WP menafsirkan URL dengan tildes sebagai URL tanpa URL?

(1) Yap, sekarang kita semua telah melihat beberapa peretasan besar dan kebocoran data di Inggris, sudah saatnya lagi di mana "keamanan" semua orang berpura-pura melakukan bagian mereka dengan menyerahkan kepada kami laporan pemindaian 200 halaman pengembang penuh dengan kesalahan positif dan masalah umum yang tidak mereka ketahui tentang apa yang diharapkan jika kita membaca dan menindaklanjuti laporan tersebut, tidak ada hal buruk yang akan terjadi.

Jawaban:


13

Ayo sederhana

Jika saya memahami OP dengan baik, masalah Anda adalah bahwa url yang berisi tilde cocok sama sekali.

Semua jawaban lain fokus pada fakta bahwa sanitasi untuk kueri menghapus beberapa karakter sebelum melakukan kueri, namun seseorang harus mampu mencegah aturan penulisan ulang agar tidak cocok dalam beberapa keadaan.

Dan itu bisa dilakukan, tidak sangat mudah, tetapi bisa dilakukan.

Mengapa cocok, di tempat pertama?

Alasan mengapa dua url menyukai example.com/postnamedan example.com/postname~mencocokkan aturan penulisan ulang yang sama adalah karena aturan penulisan ulang WP untuk posting menggunakan tag penulisan ulang %postname%yang diganti oleh regex ([^/]+)ketika aturan penulisan ulang dibuat.

Masalahnya adalah bahwa regex ([^/]+)juga cocok dengan postname postname~dan, karena sanitasi, nama yang ditanyakan akan postnameberakhir pada hasil yang valid.

Ini berarti bahwa jika kami dapat mengubah regex dari ([^/]+)ke ([^~/]+)tilde tidak akan cocok lagi maka kami secara aktif mencegah url yang mengandung tilde dalam nama pos yang akan dicocokkan.

Karena tidak ada aturan yang cocok, url akan menjadi 404, yang seharusnya menjadi perilaku yang diharapkan, saya pikir.

Cegah pencocokan

add_rewrite_tagadalah fungsi yang, terlepas dari namanya, dapat digunakan untuk memperbarui tag penulisan ulang yang ada seperti %postname%.

Jadi, jika kita menggunakan kodenya:

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

kami akan mencapai target kami dan tidakexample.com/postname~ akan cocok dengan aturan untuk .example.com/postname

Jadi, ya, 3 baris di atas adalah satu-satunya kode yang Anda perlukan .

Namun, sebelum berfungsi, Anda harus menghapus aturan penulisan ulang dengan mengunjungi halaman pengaturan permalink di backend.

Perhatikan bahwa regex ([^~/]+)mencegah tilde berada di mana saja dalam nama pos, tidak hanya sebagai karakter tambahan, tetapi karena nama pos tidak dapat benar-benar mengandung tilde karena sanitasi, itu seharusnya tidak menjadi masalah.


1
+1 seperti kesederhanaan ;-) sepertinya kita juga bisa menyesuaikan ini untuk karakter noise lainnya.
birgire

1
@Birgire bukankah kita semua? ;)
gmazzap

@ Borgire ya, kami dapat mencegah karakter apa pun dilucuti sanitize_title, tetapi karena dapat difilter, tidak mungkin untuk menulis solusi yang selalu valid. Jadi saya pergi spesifik.
gmazzap

1
Jawaban ini sejauh ini merupakan solusi terbersih, dan dengan jelas menjelaskan masalah yang kita hadapi. Terima kasih banyak - hadiah untuk Anda!
dKen

7

dimaksudkan perilaku untuk WP

Ya, seperti yang sudah dijelaskan, WP_Query::get_posts()menggunakan sanitize_title_for_query()( yang menggunakansanitize_title() ) untuk membersihkan nama pos dari pos tunggal.

Singkatnya, setelah nama posting dilewati sanitize_title_for_query(), my-permalink === my-permalink~~~sebagai sanitize_title_for_query()menghilangkan trailing ~~~. Anda dapat menguji ini dengan melakukan hal berikut:

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

apakah ada cara saya bisa mematikan ini sehingga tildes tidak cocok

Ini bukan sesuatu yang bisa Anda matikan. Ada filter yang sanitize_title()disebut sanitize_titleyang dapat Anda gunakan untuk mengubah perilaku sanitize_title(), tapi itu hampir selalu bukan ide yang sangat bagus. Injeksi SQL sangat serius, sehingga membiarkan sesuatu lolos dari keretakan karena sanitasi yang buruk dapat memiliki pengaruh yang sangat buruk pada integritas situs Anda. "Kelebihan sanitasi" kadang-kadang bisa menjadi sakit di pantat.

Saya tidak yakin apa yang Anda cari, tetapi saya curiga Anda mungkin ingin 404 posting tunggal dengan tilde trailing ini, dengan kata-kata Anda, "matikan". Satu-satunya cara yang dapat saya pikirkan pada tahap ini adalah untuk menghentikan permintaan utama ketika kita memiliki tildes trailing ini. Untuk ini, kita bisa memfilter posts_whereklausa dari permintaan utama.

FILTER

Catatan: Saya hanya menganggap posting tunggal normal, dan bukan halaman depan statis atau lampiran, Anda dapat memperluas filter untuk memasukkan ini

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

BEBERAPA CATATAN

Filter di atas akan menghasilkan halaman 404 ketika kita memiliki URL seperti https://mywordpresssite.com/my-permalink~~~~~~. Namun Anda dapat, dengan menghapus remove_action( 'template_redirect', 'redirect_canonical' );dari filter, memiliki permintaan secara otomatis mengarahkan ulang https://mywordpresssite.com/my-permalinkdan menampilkan posting tunggal karena redirect_canonical()yang terhubung dengan template_redirectyang menangani pengalihan dari WordPress yang dihasilkan 404's


7

Ya, rasanya aneh bahwa kami harus memiliki pasangan yang sama untuk:

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

dan misalnya

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

Mengapa ini mungkin, tampaknya menjadi bagian dari WP_Query::get_posts()metode ini:

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

di mana sanitize_title_for_query()didefinisikan sebagai:

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

Seharusnya dimungkinkan untuk membuat ini lebih ketat dengan sanitize_titlefilter, tetapi mungkin bukan ide yang baik untuk mengganti output default, berdasarkan sanitize_title_with_dashes, yang bertanggung jawab atas sanitasi di sini. Anda harus mempertimbangkan membuat tiket alih-alih mengubahnya, jika tidak ada saat ini sudah tentang perilaku ini.

Memperbarui

Saya ingin tahu apakah kita dapat membersihkan suara dari jalur saat ini dengan sanitize_title_for_query()dan mengarahkan ulang ke url yang dibersihkan jika perlu?

Berikut ini adalah demo yang dapat Anda mainkan di situs pengujian dan sesuaikan dengan kebutuhan Anda:

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

Bahkan mungkin lebih baik menggunakan sanitize_title_with_dashes()langsung untuk menghindari filter dan mengganti:

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

dengan:

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

ps: Saya pikir saya telah mempelajari trik ini, untuk mendapatkan jalur saat ini dengan kosong add_query_arg( [] ), dari @gmazzap ;-) Ini juga dicatat dalam Codex. Sekali lagi terima kasih kepada @gmazzap untuk pengingat penggunaan esc_url()saat menampilkan output add_query_arg( [] )atau esc_url_raw()ketika misalnya mengarahkan ulang. Periksa referensi Codex sebelumnya untuk itu juga.


+1 Hanya untuk memperjelas, karakter khusus itu dihapus, jadi, meskipun versi aneh URL terlihat di bilah lokasi, WordPress tidak bekerja dengan URL yang sebenarnya, itulah sebabnya permintaan bekerja di tempat pertama. Saya tidak melihat risiko keamanan walikota dengan perilaku itu.
Nicolai

1
ya saya pikir kita tidak boleh main-main dengan filter sanitasi untuk mengubah @ialocin ini
birgire

1
Tentu, kecuali ada alasan yang sangat bagus, itu tidak merepotkan. Belum lagi, kemungkinan besar tidak baik untuk kewarasan pengembang - bahkan tidak masuk ke sanitasi teknis. Hanya dua sen saya.
Nicolai

1
@Birgire ketika digunakan seperti itu add_query_argperlu melarikan diri dengan esc_urlatau esc_url_rawuntuk mencegah masalah keamanan ...
gmazzap

ahh ya terima kasih, jika saya ingat dengan benar ini adalah masalah keamanan yang ditemukan di banyak plugin baru-baru ini @gmazzap
birgire

3

Izinkan saya menjelaskan pemrosesan permintaan oleh WordPress, dan metode untuk mengubah perilaku WordPress untuk mencapai tujuan Anda.

Parsing permintaan

Ketika WordPress menerima permintaan, itu memulai proses membedah permintaan dan mengubahnya menjadi sebuah halaman. Inti dari proses ini dimulai ketika metode kueri utama WordPress WP::main()dipanggil. Fungsi ini mem-parsing kueri, seperti yang Anda identifikasi dengan benar, di parse_request()(di includes/class-wp.php). Di sana, WordPress mencoba untuk mencocokkan URL dengan salah satu aturan penulisan ulang . Ketika URL dicocokkan, itu membuat string kueri bagian-bagian URL dan mengkodekan bagian-bagian ini (segala sesuatu di antara dua garis miring) menggunakan urlencode(), untuk mencegah karakter khusus seperti &dari mengacaukan string kueri. Karakter yang disandikan ini mungkin menyebabkan Anda berpikir bahwa masalahnya ada di sana, tetapi mereka benar-benar berubah menjadi karakter "asli" yang sesuai saat menguraikan string kueri.

Menjalankan kueri yang terkait dengan permintaan

Setelah WordPress mem-parsing URL, ia mengatur kelas kueri utama WP_Query,, yang dilakukan dengan main()metode WPkelas yang sama. Daging sapi WP_Querydapat ditemukan dalam get_posts()metodenya di mana semua argumen kueri diuraikan dan disanitasi dan kueri SQL aktual dibuat (dan, akhirnya, jalankan).

Dalam metode ini, pada baris 2730, kode berikut dijalankan:

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

Ini membersihkan posting untuk mengambilnya dari tabel posting. Outputing info debug di dalam loop menunjukkan bahwa ini adalah tempat masalahnya: nama posting Anda my-permalink~,, ditransformasikan my-permalink, yang kemudian digunakan untuk mengambil posting dari database.

Fungsi sanitasi judul posting

Fungsi sanitize_title_for_querypanggilan sanitize_titledengan parameter yang tepat, yang mulai membersihkan judul. Sekarang inti dari fungsi ini menerapkan sanitize_titlefilter:

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

Filter ini telah, di WordPress asli, fungsi tunggal yang melekat padanya: sanitize_title_with_dashes. Saya telah menulis tinjauan luas tentang apa fungsi ini, yang dapat ditemukan di sini . Dalam fungsi ini, garis yang menyebabkan masalah Anda adalah

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

Baris ini menghapus semua karakter kecuali karakter alfanumerik, spasi, tanda hubung, dan garis bawah.

Memecahkan masalah Anda

Jadi, pada dasarnya ada satu cara untuk menyelesaikan masalah Anda: menghapus sanitize_title_with_dashesfungsi dari filter dan menggantinya dengan fungsi Anda sendiri. Ini sebenarnya tidak terlalu sulit untuk dilakukan, tetapi :

  1. Ketika WordPress mengubah proses internal judul sanitasi, ini akan memiliki efek besar pada situs web Anda.
  2. Plugin lain yang menghubungkan ke filter ini mungkin tidak menangani fungsi baru dengan benar.
  3. Paling penting : WordPress menggunakan hasil sanitize_titlefungsi secara langsung dalam query SQL dengan baris ini:

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

    Jika Anda pernah mempertimbangkan untuk mengubah filter, pastikan Anda keluar dari judul dengan benar sebelum digunakan dalam kueri!

Kesimpulan: menyelesaikan masalah Anda tidak perlu sejauh menyangkut keamanan, tetapi jika Anda ingin melakukannya, ganti sanitize_title_with_dashesdengan fungsi Anda sendiri dan perhatikan untuk melarikan diri SQL.

NB semua nama file dan nomor baris sesuai dengan file WordPress 4.4.2.


3

Beberapa orang sudah menjelaskan masalahnya, jadi saya hanya akan memposting solusi alternatif. Seharusnya cukup jelas.

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

Anda akan harus melakukan sesuatu yang berbeda sedikit untuk jenis posting hirarkis meskipun, karena WP_Queryakan berjalan pagenamemelalui wp_basenamedan kemudian membersihkan, jadi query_vars['pagename']dan get_query_var('pagename')tidak akan cocok untuk anak-anak karena butuh yang terakhir tidak akan berisi bagian induk.

Saya berharap redirect_canonicalhanya mengurus omong kosong ini.


0

INI ADALAH PERBAIKAN ... UNTUK BUG WORDPRESS HANYA MENAMBAH blok mod keamanan BEGIN di atas BLOCK yang dihasilkan 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

Anda selalu dapat mencoba menambahkan hal berikut ke .htaccessfile Anda :

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

Baris kedua di atas seharusnya berada tepat di bawah baris pertama yang ditunjukkan. Seharusnya tidak index.php~ditampilkan di URL.


Ini tidak berfungsi untuk permalink cantik yang menjadi pertanyaan, bukan?
Nicolai
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.