URLs correspondentes do Wordpress com tils à direita


11

Recebi um relatório de vulnerabilidade (1) que parece sugerir que pode haver um problema de segurança na maneira como o Wordpress lida com URLs com os seguintes tildes. Parece que o scanner acha que o site pode estar exibindo algumas listagens de diretórios e tal.

Fiquei surpreso que meu site ainda estivesse exibindo conteúdo nessas URLs diferentes, então fiz um teste instalando uma instância WP totalmente em branco, mudei para permalinks "Nome da postagem" e confirmei que sim, qualquer URL com til adicionado ainda é interpretada como o URL sem o til.

De fato, um URL como este:

https://mywordpresssite.com/my-permalink

Também é acessível com os seguintes URLs:

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

Pesquisei um pouco para ver onde o WP analisa os permalinks e o localizei class-wp.phpno parse_requestmétodo, mas não pude ir muito além disso.

Minha pergunta é se esse é o comportamento pretendido para o WP e, em caso afirmativo, existe alguma maneira de desativar isso para que os tildes não correspondam? Por que o WP interpretaria os URLs com til como um URL sem eles?

(1) Sim, agora todos nós vimos alguns dos principais hacks e vazamentos de dados no Reino Unido, é o momento em que todos os "seguranças" fingem que estão fazendo a sua parte, entregando a nós desenvolvedores relatórios de varredura de 200 páginas cheio de falsos positivos e questões genéricas sobre os quais eles não sabem nada na expectativa, se lermos e agirmos sobre o referido relatório, nada de ruim acontecerá.

Respostas:


13

Vamos simples

Se eu entendo bem o OP, seu problema é que os URLs que contêm um til são compatíveis.

Todas as outras respostas se concentram no fato de que a higienização para consulta retira alguns caracteres antes de executar a consulta; no entanto, uma deve ser capaz de impedir que uma regra de reescrita não corresponda em algumas circunstâncias.

E é factível, não muito fácil, mas factível.

Por que combina, em primeiro lugar?

O motivo pelo qual dois URLs gostam example.com/postnamee example.com/postname~correspondem à mesma regra de reescrita é porque a regra de reescrita do WP para postagens usa a tag reescrita %postname%que é substituída pela regex ([^/]+)quando as regras de reescrita são criadas.

O problema é que o regex ([^/]+)também corresponde ao nome do post postname~e, por causa da higienização, o nome consultado postnameterminará em um resultado válido.

Isso significa que, se conseguirmos alterar o regex de ([^/]+)para ([^~/]+)til, não corresponderemos mais, portanto impediremos ativamente que URLs contendo til no nome da postagem sejam correspondidos.

Como nenhuma regra corresponderá, o URL acabará sendo um 404, que deve ser o comportamento esperado, eu acho.

Impedir correspondência

add_rewrite_tagé uma função que, apesar do nome, pode ser usada para atualizar uma tag de reescrita existente como %postname%.

Então, se usarmos o código:

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

vamos atingir o nosso alvo e example.com/postname~vai não coincidir com a regra para example.com/postname.

Então, sim, as 3 linhas acima são o único código necessário .

No entanto, antes que funcione, você precisará liberar as regras de reescrita visitando a página de configurações de link permanente no back-end.

Observe que o regex ([^~/]+)impede que um til esteja em qualquer lugar no nome da postagem, não apenas como caractere à direita, mas como os nomes das postagens não podem realmente conter o til por causa da higienização, isso não deve ser um problema.


1
+1 como a simplicidade ;-) também parece que poderíamos ajustar isso para outros caracteres de ruído.
birgire

1
@birgire, não todos? ;)
gmazzap

@ Birgire Sim, podemos impedir que qualquer caractere seja removido sanitize_title, mas, como é filtrável, não é possível escrever uma solução sempre válida. Então eu fui específico.
gmazzap

1
Essa resposta tem de longe a solução mais limpa e explica claramente o problema que estamos enfrentando. Muito obrigado - recompensa para você!
DKen

7

é o comportamento pretendido para o WP

Sim, como já explicado, WP_Query::get_posts()usa sanitize_title_for_query()( que usasanitize_title() ) para limpar o nome da postagem de uma postagem singular.

Em resumo, depois que o nome da postagem passou sanitize_title_for_query(), my-permalink === my-permalink~~~como sanitize_title_for_query()remove o final ~~~. Você pode testar isso fazendo o seguinte:

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

Existe alguma maneira de desligar isso para que os tildes não correspondam

Isso não é algo que você pode desligar. Existe um filtro sanitize_title()chamado no sanitize_titlequal você pode usar para alterar o comportamento de sanitize_title(), mas isso nem sempre é uma boa ideia. A injeção de SQL é muito séria, portanto, deixar escapar alguma coisa devido a problemas de saneamento pode ter uma influência muito ruim na integridade do seu site. Às vezes, "excesso de saneamento" pode ser uma dor no traseiro.

Não tenho certeza do que você está procurando, mas suspeito que talvez você queira 404 postagens únicas com esses til à direita, em suas palavras, "desligue-o". A única maneira de pensar nesse estágio é interromper a consulta principal quando tivermos esses tildes à direita. Para isso, podemos filtrar a posts_wherecláusula da consulta principal.

O FILTRO

Nota: eu considerei apenas postagens singulares normais, e não capas estáticas ou anexos, você pode estender o filtro para incorporar isso

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

Algumas notas

O filtro acima retornará uma página 404 quando tivermos um URL como https://mywordpresssite.com/my-permalink~~~~~~. No entanto, ao remover remove_action( 'template_redirect', 'redirect_canonical' );do filtro, a consulta é redirecionada automaticamente para https://mywordpresssite.com/my-permalinke exibe a única postagem, devido à redirect_canonical()qual está conectada à template_redirectqual lida com o redirecionamento de 404 gerados pelo WordPress.


7

Sim, parece estranho que tenhamos a mesma correspondência para:

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

e por exemplo

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

Por que isso é possível, parece ser essa parte do WP_Query::get_posts()método:

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

onde sanitize_title_for_query()é definido como:

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

Deve ser possível tornar isso mais rigoroso com o sanitize_titlefiltro, mas talvez não seja uma boa idéia substituir a saída padrão, com base em sanitize_title_with_dashes, que é responsável pelo saneamento aqui. Você deve criar um ticket em vez de alterá-lo, se já não houver uma corrente sobre esse comportamento.

Atualizar

Gostaria de saber se poderíamos limpar o ruído da corrente do caminho com sanitize_title_for_query()e redirecionar para o URL limpo, se necessário?

Aqui está uma demonstração com a qual você pode jogar no site de teste e ajustar às suas necessidades:

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

Pode até ser melhor usar sanitize_title_with_dashes()diretamente para evitar os filtros e substituir:

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

com:

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

ps: Acho que aprendi esse truque, para obter o caminho atual com um vazio add_query_arg( [] ), em @gmazzap ;-) Isso também é observado no Codex. Agradecemos novamente a @gmazzap pelo lembrete de uso esc_url()ao exibir a saída add_query_arg( [] )ou esc_url_raw()quando, por exemplo, redirecioná-la. Verifique a referência anterior do Codex para isso também.


+1 Apenas para esclarecer, esses caracteres especiais são removidos. Portanto, embora a versão estranha do URL seja visível na barra de localização, o WordPress funciona com o URL real, e é por isso que a solicitação funciona em primeiro lugar. Não estou vendo nenhum risco de segurança com esse comportamento.
Nicolai

1
sim, eu acho que não deve mexer com o filtro de saneamento para mudar esta @ialocin
birgire

1
Claro, a menos que haja uma razão muito boa, é um aborrecimento que não vale a pena. Para não dizer, provavelmente não é bom para a sanidade dos desenvolvedores - nem mesmo para o saneamento técnico. Apenas meus dois centavos embora.
Nicolai

1
@birgire quando usado como assim add_query_argnecessidade de ser precedidos por esc_urlou esc_url_rawpara evitar problemas de segurança ...
gmazzap

ahh sim graças, se bem me lembro este era um problema de segurança descoberto em muitos plugins recentemente @gmazzap
birgire

3

Deixe-me explicar o processamento de uma solicitação pelo WordPress e um método para alterar o comportamento do WordPress para atingir seus objetivos de acordo.

Analisando a solicitação

Quando o WordPress recebe uma solicitação, ele inicia um processo de dissecação da solicitação e transformá-lo em uma página. O núcleo desse processo começa quando o método de consulta principal do WordPress WP::main()é chamado. Esta função analisa a consulta, como você identificou corretamente, em parse_request()(in includes/class-wp.php). Lá, o WordPress tenta combinar a URL com uma das regras de reescrita . Quando o URL é correspondido, ele cria uma cadeia de consulta das partes da URL e codifica essas partes (tudo entre duas barras) usando urlencode(), para impedir que caracteres especiais, como por exemplo, &atrapalhem a cadeia de consulta. Esses caracteres codificados podem ter levado você a pensar que o problema residia lá, mas na verdade eles são transformados nos caracteres "reais" correspondentes ao analisar a sequência de caracteres da consulta.

Executando a consulta associada à solicitação

Depois que o WordPress analisa a URL, ele configura a classe de consulta principal WP_Query, que é feita no mesmo main()método da WPclasse. O beef of WP_Querypode ser encontrado em seu get_posts()método em que todos os argumentos da consulta são analisados ​​e higienizados e a consulta SQL real é construída (e, eventualmente, executada).

Nesse método, na linha 2730, o seguinte código é executado:

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

Isso limpa a postagem para buscá-la na tabela de postagens. A saída de informações de depuração dentro do loop mostra que é aqui que reside o problema: seu nome da postagem,, my-permalink~é transformado em my-permalink, que é usado para buscar a postagem do banco de dados.

A função de higienização do título da postagem

A função sanitize_title_for_querychama sanitize_titlecom os parâmetros adequados, que procede à limpeza do título. Agora, o núcleo desta função está aplicando o sanitize_titlefiltro:

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

Este filtro tem, no WordPress nativa, uma única função ligado a ele: sanitize_title_with_dashes. Eu escrevi uma extensa visão geral do que essa função faz, que pode ser encontrada aqui . Nesta função, a linha que está causando seu problema é

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

Essa linha retira todos os caracteres, exceto caracteres alfanuméricos, espaços, hífens e sublinhados.

Resolvendo seu problema

Portanto, existe basicamente uma maneira única de resolver seu problema: remover a sanitize_title_with_dashesfunção do filtro e substituí-la por sua própria função. Na verdade, isso não é tão difícil de fazer, mas :

  1. Quando o WordPress altera o processo interno de higienização de títulos, isso terá grandes efeitos no seu site.
  2. Outros plugins conectados a esse filtro podem não lidar corretamente com a nova funcionalidade.
  3. Mais importante : o WordPress usa o resultado da sanitize_titlefunção diretamente na consulta SQL por esta linha:

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

    Se você já pensou em mudar o filtro, não se esqueça de escapar corretamente do título antes que ele seja usado na consulta!

Conclusão: a solução do seu problema não é necessária no que diz respeito à segurança, mas, se você desejar, substitua-a sanitize_title_with_dashespor sua própria funcionalidade e preste atenção ao escape do SQL.

NB: todos os nomes de arquivos e números de linha correspondem aos arquivos do WordPress 4.4.2.


3

Algumas pessoas já explicaram o problema, então vou postar uma solução alternativa. Deve ser bastante auto-explicativo.

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

Você vai ter que fazer algo um pouco diferente para tipos hierárquicos pós porém, desde que WP_Queryserá executado pagenameatravés de wp_basenamee, em seguida, higienizar-lo, por isso query_vars['pagename']e get_query_var('pagename')não irá corresponder para crianças becuase este último não conterá a peça pai.

Gostaria redirect_canonicalapenas de cuidar dessa porcaria.


0

ESTE É O CORRETO ... PARA O BUG DO WORDPRESS APENAS ADICIONE O bloco mod de segurança INICIAR acima do BLOCO Gerado pelo 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

Você sempre pode tentar adicionar o seguinte ao seu .htaccessarquivo:

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

A segunda linha acima deve ir logo abaixo da primeira linha mostrada. Deve impedir a index.php~exibição em URLs.


Isso não funciona para os permalinks bonitos de que trata a questão, certo?
Nicolai
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.