Wordpress khớp URL với dấu ngã


11

Tôi đã được trao một báo cáo lỗ hổng (1) dường như ngụ ý rằng có thể có vấn đề bảo mật trong cách Wordpress xử lý URL với các dấu hiệu sau. Có vẻ như máy quét nghĩ rằng trang web có thể đang phục vụ một số danh sách thư mục và những thứ tương tự.

Tôi đã rất ngạc nhiên khi trang web của tôi vẫn đang phục vụ nội dung trên các URL khác nhau đó, vì vậy tôi đã kiểm tra bằng cách cài đặt một ví dụ WP hoàn toàn trống, chuyển sang permalinks "Tên bài đăng" và xác nhận rằng, mọi URL có thêm dấu ngã vẫn được hiểu là URL không có dấu ngã.

Thật vậy, một url như thế này:

https://mywordpresssite.com/my-permalink

Cũng có thể truy cập bằng các URL sau:

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

Tôi chọc một chút để xem WP phân tích các permalinks ở đâu và tôi đã theo dõi nó class-wp.phptrong parse_requestphương thức, nhưng không thể đi xa hơn thế.

Câu hỏi của tôi là nếu đây là hành vi dành cho WP, và nếu vậy, có cách nào tôi có thể tắt cái này để dấu ngã không khớp không? Tại sao WP lại diễn giải các URL có dấu là một URL mà không có chúng?

(1) Yep, bây giờ chúng tôi đã nhìn thấy tất cả một vài hacks lớn và rò rỉ dữ liệu ở Anh, đó là thời gian mà lại nơi "an ninh" kẻ tất cả giả vờ họ đang làm chút của họ bằng cách bàn giao chúng tôi phát triển 200 trang quét báo cáo đầy những vấn đề sai lầm và chung chung mà họ không biết bất cứ điều gì trong kỳ vọng nếu chúng ta đọc và hành động trên báo cáo nói trên, sẽ không có điều gì xấu xảy ra.

Câu trả lời:


13

Hãy đi đơn giản

Nếu tôi hiểu rõ về OP, vấn đề của bạn là các url chứa dấu ngã được khớp hoàn toàn.

Tất cả các câu trả lời khác tập trung vào thực tế là vệ sinh cho truy vấn loại bỏ một số ký tự trước khi thực hiện truy vấn, tuy nhiên người ta phải có khả năng ngăn quy tắc viết lại không khớp trong một số trường hợp.

Và nó là có thể làm được, không phải rất dễ dàng, nhưng có thể làm được.

Tại sao nó phù hợp, ở vị trí đầu tiên?

Lý do tại sao hai url thích example.com/postnameexample.com/postname~khớp với cùng một quy tắc viết lại là bởi vì quy tắc viết lại WP cho các bài đăng sử dụng thẻ viết lại %postname%được thay thế bằng biểu thức chính ([^/]+)quy khi quy tắc viết lại được tạo.

Vấn đề là regex ([^/]+)cũng phù hợp với tên bài đăng postname~và vì vệ sinh, tên được truy vấn sẽ postnamekết thúc trong một kết quả hợp lệ.

Điều đó có nghĩa là nếu chúng tôi có thể thay đổi regex từ ([^/]+)sang ([^~/]+)dấu ngã sẽ không khớp nữa nên chúng tôi chủ động ngăn các url chứa dấu ngã trong tên bài đăng được khớp.

Vì không có quy tắc nào phù hợp, nên cuối cùng url sẽ là một 404, đây sẽ là hành vi được mong đợi, tôi nghĩ vậy.

Ngăn kết hợp

add_rewrite_taglà một chức năng, mặc dù tên của nó, có thể được sử dụng để cập nhật một thẻ viết lại hiện có như thế nào %postname%.

Vì vậy, nếu chúng ta sử dụng mã:

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

chúng tôi sẽ đạt được mục tiêu của chúng tôi và example.com/postname~sẽ không phù hợp với quy tắc cho example.com/postname.

Vì vậy, vâng, 3 dòng trên là mã duy nhất bạn cần .

Tuy nhiên, trước khi nó hoạt động, bạn sẽ cần xóa các quy tắc viết lại bằng cách truy cập trang cài đặt permalink trên phụ trợ.

Lưu ý rằng regex ([^~/]+)ngăn không cho dấu ngã ở bất kỳ vị trí nào trong tên bài đăng, không chỉ là ký tự dấu, mà vì tên bài đăng thực sự không thể chứa dấu ngã vì vệ sinh, nên đó không phải là vấn đề.


1
+1 giống như sự đơn giản ;-) có vẻ như chúng ta cũng có thể điều chỉnh điều này cho các ký tự nhiễu khác.
bạch dương

1
@birgire không phải tất cả chúng ta sao? ;)
gmazzap

@birgire có, chúng tôi có thể ngăn chặn bất kỳ ký tự nào bị loại bỏ sanitize_title, nhưng vì nó có thể lọc được, nên không thể viết một giải pháp luôn hợp lệ. Vì vậy, tôi đã đi cụ thể.
gmazzap

1
Câu trả lời này cho đến nay là giải pháp sạch nhất và giải thích rõ ràng vấn đề chúng ta đang đối mặt. Cảm ơn rất nhiều - tiền thưởng cho bạn!
dKen

7

là hành vi dự định cho WP

Có, như đã giải thích, WP_Query::get_posts()sử dụng sanitize_title_for_query()( sử dụngsanitize_title() ) để vệ sinh tên bài đăng của một bài đăng số ít.

Nói tóm lại, sau khi tên bài đăng đi qua sanitize_title_for_query(), my-permalink === my-permalink~~~như sanitize_title_for_query()xóa dấu vết ~~~. Bạn có thể kiểm tra điều này bằng cách làm như sau:

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

Có cách nào để tôi có thể tắt cái này để các dấu ngã không khớp

Đây không phải là thứ bạn có thể tắt. Có một bộ lọc trong sanitize_title()gọi sanitize_titlemà bạn có thể sử dụng để thay đổi hành vi của sanitize_title(), nhưng đó là hầu như luôn luôn không phải là một ý tưởng rất tốt. SQL tiêm là rất nghiêm trọng, do đó, để cho một cái gì đó trượt qua các vết nứt do vệ sinh kém có thể có ảnh hưởng thực sự xấu đến tính toàn vẹn của trang web của bạn. "Vệ sinh quá mức" đôi khi có thể là một cơn đau ở mông.

Tôi không chắc chắn bạn sẽ làm gì sau đó, nhưng tôi nghi ngờ rằng bạn có thể muốn 404 bài đăng đơn lẻ với những dấu vết này, theo cách nói của bạn, "tắt nó đi". Cách duy nhất tôi có thể nghĩ đến trong giai đoạn này là tạm dừng truy vấn chính khi chúng ta có các dấu ngã này. Đối với điều này, chúng ta có thể lọc posts_wheremệnh đề của truy vấn chính.

BỘ LỌC

Lưu ý: Tôi chỉ xem xét các bài đăng đơn lẻ bình thường và không phải các trang trước hoặc tệp đính kèm tĩnh, bạn có thể mở rộng bộ lọc để kết hợp điều này

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

THÔNG BÁO

Bộ lọc ở trên sẽ trả về một trang 404 khi chúng tôi có URL như thế https://mywordpresssite.com/my-permalink~~~~~~. Tuy nhiên, bạn có thể xóa bằng remove_action( 'template_redirect', 'redirect_canonical' );bộ lọc, để truy vấn tự động chuyển hướng đến https://mywordpresssite.com/my-permalinkvà hiển thị bài đăng duy nhất do redirect_canonical()được nối với template_redirectxử lý chuyển hướng của WordPress được tạo 404


7

Vâng, có vẻ lạ khi chúng ta nên có cùng một trận đấu cho:

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

và vd

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

Tại sao điều này là có thể, dường như là một phần của WP_Query::get_posts()phương pháp:

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

nơi sanitize_title_for_query()được định nghĩa là:

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

Có thể làm cho sanitize_titlebộ lọc này chặt chẽ hơn với bộ lọc, nhưng có lẽ không nên ghi đè đầu ra mặc định, dựa trên sanitize_title_with_dashes, chịu trách nhiệm vệ sinh ở đây. Bạn nên xem xét việc tạo một vé thay vì thay đổi nó, nếu không có hiện tại một lần về hành vi này.

Cập nhật

Tôi tự hỏi nếu chúng ta có thể dọn sạch tiếng ồn từ đường dẫn hiện tại sanitize_title_for_query()và chuyển hướng đến url được làm sạch nếu cần thiết?

Đây là bản demo mà bạn có thể chơi trên trang web thử nghiệm của mình và điều chỉnh theo nhu cầu của bạn:

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

Thậm chí có thể tốt hơn khi sử dụng sanitize_title_with_dashes()trực tiếp để tránh các bộ lọc và thay thế:

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

với:

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

ps: Tôi nghĩ rằng tôi đã học được mẹo này, để có được đường dẫn hiện tại với một khoảng trống add_query_arg( [] ), từ @gmazzap ;-) Điều này cũng được ghi chú trong Codex. Một lần nữa xin cảm ơn @gmazzap về lời nhắc sử dụng esc_url()khi hiển thị đầu ra add_query_arg( [] )hoặc esc_url_raw()khi vd chuyển hướng nó. Kiểm tra tham chiếu Codex trước đó cho quá.


+1 Chỉ cần làm rõ, những ký tự đặc biệt đó sẽ bị xóa, vì vậy, mặc dù phiên bản lạ của URL hiển thị trên thanh vị trí, WordPress không hoạt động với URL thực tế, đó là lý do tại sao yêu cầu hoạt động ở vị trí đầu tiên. Tôi không thấy bất kỳ rủi ro bảo mật nào của thị trưởng với hành vi đó.
Nicolai

1
vâng, tôi nghĩ rằng chúng ta không nên lộn xộn với bộ lọc vệ sinh để thay đổi @ialocin
birgire

1
Chắc chắn, trừ khi có một lý do rất tốt, đó là một rắc rối không đáng có. Không phải nói, rất có thể là không tốt cho sự tỉnh táo của các nhà phát triển - thậm chí không đi vào vệ sinh kỹ thuật. Chỉ cần tôi hai xu mặc dù.
Nicolai

1
@birgire khi được sử dụng như vậy add_query_argcần phải được thoát với esc_urlhoặc esc_url_rawđể ngăn chặn các vấn đề bảo mật ...
gmazzap

vâng, cảm ơn, nếu tôi nhớ chính xác thì đây là một vấn đề bảo mật được phát hiện trong nhiều plugin gần đây @gmazzap
birgire

3

Hãy để tôi giải thích việc xử lý yêu cầu của WordPress và phương pháp thay đổi hành vi của WordPress để hoàn thành mục tiêu của bạn.

Phân tích yêu cầu

Khi WordPress nhận được yêu cầu, nó bắt đầu một quá trình mổ xẻ yêu cầu và chuyển nó thành một trang. Cốt lõi của quá trình này bắt đầu khi phương thức truy vấn chính của WordPress WP::main()được gọi. Hàm này phân tích cú pháp truy vấn, như bạn đã xác định chính xác, trong parse_request()(in includes/class-wp.php). Ở đó, WordPress cố gắng khớp URL với một trong các quy tắc viết lại . Khi URL được khớp, nó sẽ tạo ra một chuỗi truy vấn của các phần URL và mã hóa các phần này (mọi thứ giữa hai dấu gạch chéo) bằng cách sử dụng urlencode(), để ngăn các ký tự đặc biệt như làm &rối chuỗi truy vấn. Các ký tự được mã hóa này có thể khiến bạn nghĩ rằng vấn đề nằm ở đó, nhưng chúng thực sự biến thành các ký tự "thực" tương ứng của chúng khi phân tích chuỗi truy vấn.

Chạy truy vấn liên quan đến yêu cầu

Sau khi WordPress phân tích cú pháp URL, nó sẽ thiết lập lớp truy vấn chính WP_Query, được thực hiện theo cùng một main()phương thức của WPlớp. Thịt bò của WP_Querycó thể được tìm thấy trong get_posts()phương thức của nó trong đó tất cả các đối số truy vấn được phân tích cú pháp và khử trùng và truy vấn SQL thực tế được xây dựng (và cuối cùng, chạy).

Trong phương thức này, trên dòng 2730, đoạn mã sau được thực thi:

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

Điều này vệ sinh bài đăng để lấy nó từ bảng bài viết. Xuất thông tin gỡ lỗi bên trong vòng lặp cho thấy đây là nơi giải quyết vấn đề: tên bài đăng của bạn my-permalink~, được chuyển thành my-permalink, sau đó được sử dụng để lấy bài đăng từ cơ sở dữ liệu.

Chức năng vệ sinh tiêu đề bài

Hàm sanitize_title_for_querygọi sanitize_titlevới các tham số thích hợp, tiến hành vệ sinh tiêu đề. Bây giờ cốt lõi của chức năng này là áp dụng sanitize_titlebộ lọc:

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

Bộ lọc này, trong WordPress nguyên gốc, có một chức năng duy nhất được đính kèm với nó : sanitize_title_with_dashes. Tôi đã viết một cái nhìn bao quát về chức năng này, có thể tìm thấy ở đây . Trong chức năng này, dòng gây ra vấn đề của bạn là

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

Dòng này loại bỏ tất cả các ký tự ngoại trừ các ký tự chữ và số, dấu cách, dấu gạch nối và dấu gạch dưới.

Giải quyết vấn đề của bạn

Vì vậy, về cơ bản có một cách duy nhất để giải quyết vấn đề của bạn: xóa sanitize_title_with_dasheschức năng khỏi bộ lọc và thay thế nó bằng chức năng của riêng bạn. Điều này thực sự không khó thực hiện, nhưng :

  1. Khi WordPress thay đổi quy trình vệ sinh tiêu đề nội bộ, điều này sẽ có tác dụng lớn trên trang web của bạn.
  2. Các plugin khác móc vào bộ lọc này có thể không xử lý chính xác chức năng mới.
  3. Quan trọng nhất : WordPress sử dụng kết quả của sanitize_titlehàm trực tiếp trong truy vấn SQL theo dòng này:

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

    Nếu bạn bao giờ xem xét việc thay đổi bộ lọc, hãy chắc chắn rằng bạn thoát đúng tiêu đề trước khi sử dụng nó trong truy vấn!

Kết luận: giải quyết vấn đề của bạn là không cần thiết khi có liên quan đến bảo mật, nhưng bạn nên làm điều đó, thay thế sanitize_title_with_dashesbằng chức năng của riêng bạn và chú ý đến việc thoát SQL.

NB tất cả tên tệp và số dòng tương ứng với các tệp WordPress 4.4.2.


3

Một số người đã giải thích vấn đề này, vì vậy tôi sẽ chỉ đăng một giải pháp thay thế. Nên khá tự giải thích.

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

Bạn sẽ phải làm một cái gì đó hơi khác một chút cho các loại bài đăng phân cấp, vì WP_Querysẽ chạy pagenamequa wp_basenamevà sau đó vệ sinh nó, vì vậy query_vars['pagename']get_query_var('pagename')sẽ không phù hợp với trẻ em vì sau này sẽ không chứa phần cha mẹ.

Tôi redirect_canonicalchỉ muốn chăm sóc tào lao này.


0

ĐÂY LÀ SỰ CỐ ...

# 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

Bạn luôn có thể thử thêm các mục sau vào .htaccesstệp của mình :

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

Dòng thứ hai ở trên phải đi bên dưới dòng đầu tiên được hiển thị. Nó sẽ ngăn không index.php~hiển thị trong URL.


Điều này không hoạt động cho các permalinks đẹp, câu hỏi là về, phải không?
Nicolai
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.