问题描述
我已经提交了一个漏洞报告 (1),这似乎意味着 Wordpress 处理具有以下波浪符的 URL 的方式可能会出现安全问题。看起来扫描器认为网站可能正在为某些目录列表提供服务。
我很惊讶,我的网站仍然在这些不同的网址上提供内容,所以我做了一个测试,安装一个完全空白的 WP 实例,切换到”Post name” 永久链接,并确认,任何带有添加波浪号的 URL 仍然被解释为没有波浪号
的确,这样一个网址:
https://mywordpresssite.com/my-permalink
也可通过以下 URL 访问:
https://mywordpresssite.com/my-permalink~
https://mywordpresssite.com/my-permalink~/
https://mywordpresssite.com/my-permalink~~~~~~
我戳了一下,看到 WP 解析固定链接的位置,并且我将其按 parse_request
方法跟踪到 class-wp.php
,但不能比这更远。
我的问题是,如果这是针对 WP 的行为,如果是,有什么办法可以关闭它,所以波浪号不匹配?为什么 WP 将波纹符号的 URL 解释为没有它们的 URL?
(1) 是的,现在我们都看到了英国的几个主要的黑客和数据泄露,现在再次,”security” 的所有人都假装他们通过向我们开发人员提供 200 页扫描报告, false-positives 和一般性问题,如果我们阅读并对所述报告采取行动,他们就不会在期望中知道什么,没有什么不好的。
最佳解决方案
我们去简单吧
如果我理解 OP 好,你的问题是包含波浪号的网址完全匹配。
所有其他答案的重点在于,在执行查询之前,查询的清理消除了一些字符,但是在某些情况下,应该能够防止重写规则不匹配。
它是可行的,不是很容易,但可行。
为什么匹配,首先?
为什么像 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 行是你需要的唯一代码。
但是,在它工作之前,您需要刷新重写规则,方法是访问后端的固定链接设置页面。
请注意,正则表达式 ([^~/]+)
防止波浪号在邮件名称中的任何位置,不仅作为尾随字符,而是因为发布名称因为清理而不能实际包含波浪号,那应该不是问题。
次佳解决方案
是的,我们应该有一样的匹配:
example.tld/2016/03/29/test/
和例如
example.tld/2016/03/29/..!!$$~~test~~!!$$../
为什么这是可能的,似乎是 this part 的 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
,这是负责这里的卫生设计,可能不是一个好主意来覆盖默认输出。您应该考虑创建一张票,而不是改变它,如果没有一次关于这个行为的现在。
Update
我想知道我们是否可以使用 sanitize_title_for_query()
清除当前路径中的噪音,如有必要,重定向到清理的网址?
这是一个演示,您可以在测试网站上玩,并根据您的需要进行调整:
/**
* 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:我认为我学到了这个技巧,从 @gmazzap ;-) 得到一个空的 add_query_arg( [] )
的当前路径,这也是 Codex 中的 noted 。再次感谢 @gmazzap 在显示 add_query_arg( [] )
或 esc_url_raw()
的输出时使用 esc_url()
的提醒。重定向检查以前的法典参考文献。
第三种解决方案
is intended behaviour for 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~~~' )
is there any way I can switch this off so tildes are not matched
这不是你可以关闭的东西。 sanitize_title()
中有一个称为 sanitize_title
的过滤器,您可以使用它来更改 sanitize_title()
的行为,但这几乎总是不是一个好主意。 SQL 注入是非常严重的,因为由于卫生条件不好而导致的漏洞可能会对您网站的完整性造成很大的影响。 “Over sanitation” 有时可能是屁股的痛苦。
我不知道你是什么,但我怀疑你可能想要 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 );
几点注意
当我们有一个类似 https://mywordpresssite.com/my-permalink~~~~~~
的 URL 时,上述过滤器将返回一个 404 页面。然而,您可以通过从过滤器中删除 remove_action( 'template_redirect', 'redirect_canonical' );
,将查询自动重定向到 https://mywordpresssite.com/my-permalink
,并显示单个帖子,因为 redirect_canonical()
挂接到 template_redirect
,它处理 WordPress 生成的 404 的重定向
参考文献
注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。