WordPress将URL与尾随代字号匹配

时间:2015-11-15 作者:dKen

我收到了一份漏洞报告(1),该报告似乎暗示Wordpress处理具有以下颚化符的URL的方式可能存在安全问题。扫描器似乎认为该网站可能提供了一些目录列表等等。

我很惊讶我的网站仍然在这些不同的URL上提供内容,所以我做了一个测试,安装了一个完全空白的WP实例,切换到“Post name”永久链接,并确认是的,任何添加了tilde的URL仍然会被解释为没有tilde的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页的扫描报告,其中充满了误报和他们不知道的一般问题。如果我们阅读并按照报告采取行动,就不会发生什么坏事

7 个回复
SO网友:gmazzap

简单地说,如果我很理解OP,那么您的问题是包含颚化符的URL根本不匹配。

所有其他答案都关注这样一个事实,即在执行查询之前,对查询进行清理会去掉一些字符,但是应该能够防止重写规则在某些情况下不匹配。

这是可行的,不是很容易,但可行。

首先,为什么它匹配

两个URL相似的原因example.com/postnameexample.com/postname~ 匹配相同的重写规则是因为帖子的WP重写规则使用重写标记%postname% 被regex替换([^/]+) 创建重写规则时。

问题是正则表达式([^/]+) 也与postname匹配postname~ 而且,由于清理,查询的名称将为postname 最终得到一个有效的结果。

这意味着如果我们能够将正则表达式从([^/]+)([^~/]+) tilde将不再匹配,因此我们积极防止在帖子名称中包含tilde的URL被匹配。

由于没有匹配的规则,url最终将成为404,我认为这应该是预期的行为。

阻止匹配

add_rewrite_tag 是一个函数,尽管名称不同,但它可以用来更新现有的重写标记,如%postname%.

因此,如果我们使用代码:

add_action(\'init\', function() {
  add_rewrite_tag( \'%postname%\', \'([^~/]+)\', \'name=\' );
});
我们将达到目标example.com/postname~not 匹配规则example.com/postname.

所以,是的,the 3 lines above is the only code you\'ll need.

然而,在它工作之前,您需要通过访问后端的永久链接设置页面刷新重写规则。

请注意,regex([^~/]+) 禁止在帖子名称中的任何位置使用颚化符,不仅是作为尾随字符,而且因为经过清理,帖子名称实际上不能包含颚化符,所以这应该不是问题。

SO网友:birgire

是的,似乎很奇怪,我们应该有相同的匹配:

example.tld/2016/03/29/test/
和例如。

example.tld/2016/03/29/..!!$$~~test~~!!$$../
为什么这是可能的,似乎是this partWP_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, 负责这里的卫生。如果当前没有关于此行为的once,您应该考虑创建一个票证,而不是更改它。

更新我想知道我们是否可以用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\' );
}
ps:我想我学会了这个技巧,用一个空的add_query_arg( [] ), 来自@gmazzap;-)这也是noted 在法典中。再次感谢@gmazzap提醒您使用esc_url() 显示的输出时add_query_arg( [] )esc_url_raw() 例如重定向时。也可以查看之前的法典参考。

SO网友:Pieter Goosen

是否为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 并显示由于redirect_canonical() 连接到template_redirect 它处理WordPress生成的404的重定向

SO网友:engelen

让我解释一下WordPress对请求的处理,以及相应地改变WordPress行为以实现目标的方法。

解析请求

当WordPress收到请求时,它会开始解析请求并将其转换为页面的过程。这个过程的核心开始于WordPress主查询方法WP::main() 被调用。此函数在parse_request() (英寸includes/class-wp.php). 在那里,WordPress尝试将URL与rewrite rules. 当URL匹配时,它会创建URL部分的查询字符串,并使用urlencode(), 防止特殊字符,如& 避免弄乱查询字符串。这些编码字符可能会让您认为问题仍然存在,但在解析查询字符串时,它们实际上会变成相应的“真实”字符。

WordPress解析URL后,运行与请求关联的查询,它将设置主查询类,WP_Query, 这是以相同的方式完成的main() 的方法WP 班的牛肉WP_Query 可在its中找到get_posts() 方法,在该方法中,所有查询参数都将被解析和清理,并构造(并最终运行)实际的SQL查询。

在此方法中,第2730行执行以下代码:

$q[\'name\'] = sanitize_title_for_query( $q[\'name\'] );
这将清理post,以便从posts表中获取它。在循环中输出调试信息表明这就是问题所在:您的帖子名,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. 我已经对这个函数的功能做了全面的概述,which can be found here. In this function, the line that\'s causing your problem is

$title = preg_replace(\'/[^%a-z0-9 _-]/\', \'\', $title);
此行将除去字母数字字符、空格、连字符和下划线以外的所有字符。

解决您的问题

因此,基本上只有一种方法可以解决您的问题:删除sanitize_title_with_dashes 函数,并用自己的函数替换它。这其实并不难做到,but:

当WordPress更改标题清理的内部过程时,这将对您的网站产生重大影响

  • Most importantly: WordPress使用sanitize_title 作用directly 在此行的SQL查询中:

    $where .= " AND $wpdb->posts.post_name = \'" . $q[\'name\'] . "\'";
    
    如果您考虑更改过滤器,请确保在查询中使用标题之前正确转义标题!

  • 结论:就安全性而言,没有必要解决您的问题,但如果您想这样做,请更换sanitize_title_with_dashes 使用您自己的功能,并注意SQL转义。

    注意:所有文件名和行号都与WordPress 4.4.2文件相对应

    SO网友:kovshenin

    一些人已经解释了这个问题,所以我将发布一个替代解决方案。应该是不言而喻的。

    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\') 不适用于子级,因为后者不包含父级零件。

    我希望redirect_canonical 只是处理好这些垃圾。

    SO网友:Michael S. Howard

    这是修复。。。对于WORDPRESS的BUG,只需在WORDPRESS生成的块上方添加BEGIN security mod块。

    # 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
    

    SO网友:Huginn

    您始终可以尝试将以下内容添加到.htaccess 文件:

    RewriteEngine On
    RewriteRule \\.php~$ – [forbidden,last]
    
    上面的第二行应位于所示第一行的正下方。它应该防止index.php~ 在URL中显示。

    相关推荐

    Let me choose permalinks

    我需要选择一个叫做“mysite”的永久链接。com/1418”,但wordpress不断在永久链接中添加“-2”。通常这意味着我已经有了一个名为“相同”的页面,它位于垃圾箱或其他地方。但这里的情况似乎并非如此。我尝试在设置中重置永久链接,这也没有帮助。我如何使用数字作为页面名称permalink,而不用wordpress在permalink中添加“-2”。