带有结尾波浪号的Wordpress匹配URL


11

我已经收到了一个漏洞报告(1),这似乎暗示着Wordpress处理跟随波浪号的URL的方式可能存在安全问题。扫描程序似乎认为该网站可能正在提供某些目录列表等。

令我感到惊讶的是,我的网站仍在这些不同的URL上提供内容,因此我通过安装一个完全空白的WP实例进行了测试,切换为“帖子名称”永久链接,并确认是的,所有带有波浪号的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的URL?

(1)是的,现在我们都已经在英国看到了几次重大的黑客攻击和数据泄露事件,这一次是“安全”人员再次假装自己通过向开发人员提供200页扫描报告来做自己的工作如果我们阅读并采取上述行动,他们充满了假阳性和一般性问题,他们对他们一无所知。

Answers:


13

让我们简单一点

如果我对OP很了解,那么您的问题是包含波浪号的URL完全匹配。

所有其他答案都集中在以下事实上:对查询的清理会在执行查询之前去除一些字符,但是在某些情况下,它应该能够防止重写规则不匹配。

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

为什么它匹配第一?

之类的两个url喜欢example.com/postnameexample.com/postname~匹配相同的重写规则的原因是,帖子的WP重写规则使用了创建重写规则时被%postname%regex替换的重写标记([^/]+)

问题在于正则表达式([^/]+)也与邮编匹配,postname~并且由于进行了清理,所查询的名字将以postname有效结果结尾。

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

由于没有规则匹配,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~~~as 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 );

注意事项

当我们有类似的网址时,上述过滤器将返回404页面https://mywordpresssite.com/my-permalink~~~~~~。但是,您可以通过remove_action( 'template_redirect', 'redirect_canonical' );从过滤器中删除查询,使查询自动重定向到https://mywordpresssite.com/my-permalink并显示单个帖子,因为redirect_canonical()该帖子已被钩到template_redirect处理WordPress生成的404的重定向


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

ps:我想我学到了这个技巧,可以add_query_arg( [] )从@gmazzap 获得空的当前路径;-) 法典中也提到了这一点。再次感谢@gmazzap esc_url()在显示add_query_arg( [] )esc_url_raw()重定向输出时提醒使用。还要检查以前的食典参考。


+1只是为了澄清,这些特殊字符会被删除,因此,尽管在位置栏中可以看到URL的奇怪版本,但WordPress确实可以使用实际URL,这就是请求首先起作用的原因。我没有看到这种行为有任何市长的安全隐患。
尼古拉

1
是的,我认为我们不应该使用卫生过滤器来更改此@ialocin
birgire

1
当然,除非有很好的理由,否则这是不值得的麻烦。更不用说,这很可能不利于开发人员的理智-甚至不涉及技术卫生。不过只有我的两分钱。
Nicolai

1
@birgire像这样使用时,add_query_arg必须避免esc_urlesc_url_raw防止安全问题...
gmazzap

嗯,是的,谢谢,如果我没
记错的

3

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

解析请求

当WordPress收到请求时,它将开始解剖请求并将其转换为页面的过程。当WP::main()调用WordPress主要查询方法时,此过程的核心开始。如您所正确识别的,此函数将解析查询parse_request()(在中includes/class-wp.php)。在那里,WordPress尝试将URL与重写规则之一进行匹配。匹配URL时,它将创建URL部分的查询字符串,并使用编码这些部分(两个斜杠之间的所有urlencode()字符),以防止特殊字符(例如&,弄乱查询字符串)。这些编码的字符可能使您认为问题出在这里,但是在解析查询字符串时,它们实际上变成了它们对应的“真实”字符。

运行与请求关联的查询

WordPress解析URL后,它将设置主查询类,该类以与该类WP_Query相同的main()方法完成WPWP_Query可以在其get_posts()方法中找到的强项,在该方法中,所有查询参数都经过解析和清理,然后构造(并最终运行)实际的SQL查询。

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

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

这可以清理帖子,以便从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。我已经对该功能的作用进行了广泛的概述,可以在此处找到在此功能中,导致您出现问题的行是

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

该行将除去字母数字字符,空格,连字符和下划线以外的所有字符。

解决你的问题

因此,基本上有一种方法可以解决您的问题:sanitize_title_with_dashes从过滤器中删除该函数,然后将其替换为您自己的函数。这实际上并不难,但是

  1. 当WordPress更改标题的内部清理流程时,这将对您的网站产生重大影响。
  2. 其他插入此过滤器的插件可能无法正确处理新功能。
  3. 最重要的是:WordPress 通过此行直接在SQL查询中使用sanitize_title函数的结果:

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

    如果您曾经考虑过更改过滤器,请确保在查询中使用标题之前正确地将其转义了!

结论:就安全性而言,解决您的问题不是必需的,但是如果您要这样做,请sanitize_title_with_dashes用您自己的功能替换,并注意SQL转义。

注意,所有文件名和行号均与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')它将不会与孩子匹配,因为后者将不包含父部分。

我希望redirect_canonical只是照顾这个废话。


0

这就是解决方法...对于WordPress,它的错误仅需在Wordpress生成的块上方添加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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.