끝에 물결표가있는 Wordpress 일치하는 URL


11

Wordpress가 다음 물결표로 URL을 처리하는 방식에 보안 문제가있을 수 있음을 암시하는 취약성 보고서 (1)를 받았습니다. 스캐너는 웹 사이트가 일부 디렉토리 목록 등을 제공하고 있다고 생각합니다.

내 웹 사이트가 여전히 다른 URL에서 콘텐츠를 제공하고 있다는 사실에 놀랐습니다. 완전히 빈 WP 인스턴스를 설치하여 테스트를 수행하고 "게시 이름"퍼머 링크로 전환 한 다음 물결표가 추가 된 URL은 여전히 ​​다음과 같이 해석됩니다. 물결표가없는 URL

실제로 다음과 같은 URL :

https://mywordpresssite.com/my-permalink

다음 URL을 통해 액세스 할 수도 있습니다.

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

나는 WP는 영구 링크를 구문 분석 곳을보고 조금 주위를 찌르고, 나는 그것을 아래로 추적 class-wp.php에 parse_request방법,하지만보다 훨씬 더 가져올 수 없습니다.

내 질문은 이것이 WP의 의도 된 동작인지, 그렇다면 물결표가 일치하지 않도록 끌 수있는 방법이 있습니까? 왜 WP는 물결표가있는 URL을없는 URL로 해석합니까?

(1) 그렇습니다. 이제 우리는 영국에서 몇 가지 주요 핵과 데이터 유출을 보았습니다. "보안"사람들이 개발자에게 200 페이지 스캔 보고서를 건네면서 비트를하고있는 척하는 시대가되었습니다. 이 보고서를 읽고 조치를 취하면 기대에 대해 전혀 알지 못하는 잘못된 긍정적 인 문제와 일반적인 문제로 가득 찬 나쁜 일은 일어나지 않을 것입니다.

답변:


13

간단하게 갑시다

OP를 잘 이해하면 물결표가 포함 된 URL이 전혀 일치하지 않습니다.

다른 모든 답변은 쿼리를 삭제하기 전에 쿼리를 수행하기 전에 일부 문자를 제거한다는 사실에 중점을 두지 만 일부 상황에서는 다시 쓰기 규칙이 일치하지 않도록 할 수 있어야합니다.

그리고 그것은 쉬운 일이 아니지만 가능한 일입니다.

처음에 왜 일치합니까?

두 개의 URL 이 동일한 다시 쓰기 규칙을 좋아 example.com/postname하고 example.com/postname~일치시키는 이유는 게시물에 대한 WP 다시 쓰기 규칙이 다시 쓰기 규칙이 작성 될 때 %postname%정규식으로 대체되는 다시 쓰기 태그 를 사용하기 때문 ([^/]+)입니다.

문제는 정규 표현식 ([^/]+)이 포스트 postname~이름과 도 일치하고 위생 때문에 쿼리 된 이름 postname이 유효한 결과 로 끝나는 것입니다.

즉, 정규식 ([^/]+)을 ([^~/]+)틸드 로 변경할 수 있으면 더 이상 일치하지 않으므로 게시물 이름에 틸드가 포함 된 URL이 일치하지 않도록 적극적으로 방지합니다.

규칙이 일치하지 않기 때문에 URL은 404가 될 것으로 예상되는 동작입니다.

일치 방지

add_rewrite_tag이름에도 불구하고와 같은 기존 다시 쓰기 태그를 업데이트하는 데 사용할 수있는 함수입니다 %postname%.

따라서 코드를 사용하면

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

우리는 우리의 목표에 도달하고 example.com/postname~할 수 없습니다 에 대한 규칙과 일치 example.com/postname.

예, 위의 3 줄만 필요한 코드 입니다.

그러나 작동하기 전에 백엔드의 퍼머 링크 설정 페이지를 방문하여 다시 쓰기 규칙을 플러시해야합니다.

정규 표현식 ([^~/]+)은 물결 문자가 아닌 포스트 이름의 임의의 위치에 물결표가 생기는 것을 방지합니다. 그러나 포스트 이름에는 위생으로 인해 물결표를 실제로 포함 할 수 없으므로 문제가되지 않습니다.


1
단순성과 +1 ;-) 다른 노이즈 문자에 대해서도 이것을 조정할 수있는 것처럼 보입니다.
— birgire

1
@birgire 우리 모두 아닌가요? ;)
— gmazzap

@birgire 그렇습니다. 모든 문자를 제거 할 수 sanitize_title는 있지만 필터링 할 수 있기 때문에 항상 유효한 솔루션을 작성할 수는 없습니다. 그래서 나는 구체적으로 갔다.
— gmazzap

1
이 답변에는 지금까지 가장 깨끗한 해결책이 있으며 우리가 직면 한 문제를 명확하게 설명합니다. 많은 감사합니다-현상금!
— dKen

7

WP의 의도 된 동작

예, 이미 설명 WP_Query::get_posts()용도 sanitize_title_for_query()( 사용이sanitize_title() ) 단수 게시물의 게시 이름을 살균 할 수 있습니다.

즉, 게시물 이름이을 (를 sanitize_title_for_query()) 통과 한 후 후행 my-permalink === my-permalink~~~을 sanitize_title_for_query()제거합니다 ~~~. 다음을 수행하여이를 테스트 할 수 있습니다.

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

물결표가 일치하지 않도록 끌 수있는 방법이 있습니까?

이것은 당신이 끌 수있는 것이 아닙니다. 거기에 필터이다 sanitize_title()라는 sanitize_title당신의 행동을 변경하는 데 사용할 수는 sanitize_title()있지만, 거의 항상 아주 좋은 생각이 아니다. SQL 주입은 매우 심각하므로 잘못된 위생으로 인해 균열로 인해 무언가가 미끄러 져 들어가면 사이트의 무결성에 실제로 나쁜 영향을 줄 수 있습니다. "위생 이상"은 때때로 엉덩이에 통증이 될 수 있습니다.

나는 당신이 무엇을하고 있는지 잘 모르겠지만, 당신은 아마도 당신이 말로 "전환"으로 말미 암음이있는 404 개의 싱글 포스트를 원한다고 생각합니다. 이 단계에서 내가 생각할 수있는 유일한 방법은 이러한 물결표가있을 때 주 쿼리를 중단하는 것입니다. 이를 위해 posts_where주 쿼리 의 절을 필터링 할 수 있습니다.

필터

참고 : 정적 단일 페이지 또는 첨부 파일이 아닌 일반 단일 게시물 만 고려했습니다.이를 통합하도록 필터를 확장 할 수 있습니다

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

몇 가지 참고 사항

위의 필터는와 같은 URL이있을 때 404 페이지를 반환합니다 https://mywordpresssite.com/my-permalink~~~~~~. 그러나 remove_action( 'template_redirect', 'redirect_canonical' );필터에서 제거 하면 쿼리가 자동으로 리디렉션되어 https://mywordpresssite.com/my-permalink단일 게시물로 표시되어 WordPress의 리디렉션을 처리하는 404로 redirect_canonical()연결됩니다.template_redirect


7

그렇습니다.

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

그리고 예

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

이것이 가능한 이유가 될 것 같습니다 이 부분 의 WP_Query::get_posts()방법 :

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

여기서 sanitize_title_for_query()다음과 같이 정의됩니다.

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

sanitize_title필터 를 사용하여이를 더 엄격하게 만들 수 는 있지만 sanitize_title_with_dashes여기에서 위생을 담당 하는 기본 출력을 무시하는 것은 좋지 않을 수 있습니다. 이 동작에 대한 최신 정보가 없으면 티켓을 변경하는 대신 티켓을 만드는 것이 좋습니다.

최신 정보

현재 경로에서 노이즈를 sanitize_title_for_query()정리하고 필요한 경우 정리 된 URL로 리디렉션 할 수 있는지 궁금 합니다.

테스트 사이트에서 플레이하고 필요에 따라 조정할 수있는 데모는 다음과 같습니다.

/**
 * 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()필터를 피하고 교체하기 위해 직접 사용하는 것이 더 좋습니다 .

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

와:

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

추신 : 나는 add_query_arg( [] )@gmazzap에서 empty를 가진 현재 경로를 얻기 위해이 트릭을 배웠다고 생각합니다 .-) 이것은 Codex 에도 언급 되어 있습니다. esc_url()출력을 표시 add_query_arg( [] )하거나 esc_url_raw()예를 들어 리디렉션 할 때 사용하도록 상기시켜주는 @gmazzap에게 다시 감사드립니다 . 이전 코덱스 참조도 확인하십시오.


+1 명확히하기 위해 특수 문자가 제거되므로 위치 표시 줄에 이상한 버전의 URL이 표시되지만 WordPress는 실제 URL과 함께 작동하므로 요청이 처음부터 작동합니다. 그 행동으로 시장 보안 위험이 보이지 않습니다.
— Nicolai

1
예, 우리는이 @ialocin을 변경하기 위해 위생 필터를 망쳐서는 안된다고 생각합니다
— birgire

1
물론, 정당한 이유가 없다면, 그만한 가치가없는 번거 로움입니다. 말할 것도없이, 그것은 기술 위생에 들어 가지 않는 개발자 정신에 좋지 않습니다. 그래도 내 두 센트.
— Nicolai

1
@birgire를 사용 하면 보안 문제를 add_query_arg피 esc_url하거나 esc_url_raw보안 문제를 방지 해야 합니다 ...
— gmazzap

아 네 고마워요, 올바르게 기억한다면 이것은 최근에 많은 플러그인에서 발견 된 보안 문제였습니다. @gmazzap
— birgire

3

WordPress의 요청 처리 방법과 목표를 달성하기 위해 WordPress의 동작을 변경하는 방법을 설명하겠습니다.

요청 파싱

WordPress는 요청을 받으면 요청을 해부하고 페이지로 변환하는 프로세스를 시작합니다. 이 프로세스의 핵심은 WordPress 기본 쿼리 메서드 WP::main()가 호출 될 때 시작됩니다 . 이 함수는 parse_request()(in includes/class-wp.php) 에서 올바르게 식별 한대로 쿼리를 구문 분석합니다 . 거기에서 WordPress는 URL을 다시 쓰기 규칙 중 하나와 일치시킵니다 . URL이 일치하면 URL 부분의 쿼리 문자열을 만들고을 사용하여 이러한 부분 (두 슬래시 사이의 모든 부분)을 인코딩 하여 쿼리 문자열을 엉망 urlencode()으로 만드는 것과 같은 특수 문자를 방지 &합니다. 이러한 인코딩 된 문자로 인해 문제가 있다고 생각할 수도 있지만 쿼리 문자열을 구문 분석 할 때 실제로 해당 "실제"문자로 바뀝니다.

요청과 관련된 쿼리 실행

WordPress가 URL을 구문 분석 한 후 클래스 WP_Query의 동일한 main()메소드 에서 수행되는 기본 쿼리 클래스를 설정합니다 WP. 이 방법은 모든 쿼리 인수가 구문 분석되고 삭제되고 실제 SQL 쿼리가 구성되는 (그리고 결국 실행되는) 방법에서 WP_Query찾을 수 있습니다 get_posts().

이 방법에서는 2730 행에서 다음 코드가 실행됩니다.

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

게시물 테이블에서 가져 오기 위해 게시물을 삭제합니다. 루프 내에서 디버그 정보를 출력하면 문제가있는 위치 인 게시물 이름 my-permalink~이로 변환 된 my-permalink다음 데이터베이스에서 게시물을 가져 오는 데 사용됩니다.

게시물 제목 살균 기능

함수 sanitize_title_for_query호출 sanitize_title표제 살균 진행 적절한 파라미터로. 이제이 함수의 핵심은 sanitize_title필터를 적용하는 것입니다 .

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

이 필터는 기본 WordPress에 단일 기능이 첨부되어 있습니다 sanitize_title_with_dashes. 이 기능의 기능에 대한 광범위한 개요를 작성했으며 여기에서 확인할 수 있습니다 . 이 기능에서 문제를 일으키는 선은

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

이 줄은 영숫자 문자, 공백, 하이픈 및 밑줄을 제외한 모든 문자를 제거합니다.

문제 해결

따라서 기본적으로 문제를 해결하는 단일 방법이 sanitize_title_with_dashes있습니다. 필터 에서 기능을 제거하고 자신의 기능으로 교체하십시오. 이것은 실제로 그렇게 어려운 것은 아니지만 :

  1. 워드 프레스가 타이틀을 살균하는 내부 프로세스를 변경하면 웹 사이트에 큰 영향을 미칩니다.
  2. 이 필터에 연결된 다른 플러그인은 새 기능을 올바르게 처리하지 못할 수 있습니다.
  3. 가장 중요한 사항 : WordPress는 다음 줄에 의해 SQL 쿼리에서 직접sanitize_title 함수 결과를 사용합니다 .

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

    필터 변경을 고려할 때는 쿼리에서 제목을 사용하기 전에 제목을 올바르게 이스케이프해야합니다!

결론 : 보안에 관한 한 문제를 해결하는 것은 필요하지 않지만, 원하는 경우 sanitize_title_with_dashes자체 기능으로 바꾸고 SQL 이스케이프에주의를 기울이십시오.

NB의 모든 파일 이름과 줄 번호는 WordPress 4.4.2 파일과 일치합니다.


3

일부 사람들은 이미 문제를 설명 했으므로 대안 솔루션을 게시 할 것입니다. 꽤 설명이 필요합니다.

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

당신이 있기 때문에, 비록 계층 포스트 유형 뭔가 조금의 다른 작업을 수행해야합니다 WP_Query실행 pagename을 통해 wp_basename다음과 살균 때문에, query_vars['pagename']그리고 get_query_var('pagename')후자는 부모의 일부가 포함되지 않습니다 becuase 아이들을 위해 일치하지 않습니다.

redirect_canonical이 쓰레기를 처리 했으면 좋겠다 .


0

이것은 픽스입니다 ... WORDPRESS의 버그에 대해서만 Wordpress Generated BLOCK 위에 BEGIN 보안 모드 블록을 추가하십시오.

# 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

항상 .htaccess파일에 다음을 추가해보십시오 .

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

위의 두 번째 줄은 표시된 첫 번째 줄 바로 아래에 있어야합니다. index.php~URL에 표시 되지 않아야 합니다.


이것은 질문에 관한 예쁜 퍼머 링크에는 효과가 없습니다.
— Nicolai
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.