끝에 물결표가있는 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.phpparse_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_argesc_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.