在我们 上一篇文章 中,我们介绍了跨站脚本 (XSS) 以及 WordPress 提供的防止 XSS 攻击的功能。今天,我们将探讨前端开发人员面临的另一个安全问题:跨站请求伪造 (CSRF)。
不要以为这些安全问题不重要,最近在 WP SEO 插件中发现了一个重大漏洞,该插件已安装在 1,000,000 多个 WordPress 网站上,并且允许黑客使用 CSRF 操纵 WordPress 数据库。(该插件已迅速修复,但您可以看出这类问题有多可怕。)
Nonce和跨站请求伪造
简单来说,CSRF 是指坏人试图欺骗用户(通常是具有 WordPress 仪表板访问权限的用户)执行他们本意不希望执行的操作。一个简单的例子可以帮助说明。
假设您正在构建一个 WordPress 插件,允许登录的用户提交他们的图片。在网站的前端,您的插件可能会生成如下表单
<?php if ( is_user_logged_in() ) : ?>
<form action="/submit-picture/" method="get">
<input type="text" name="picture_url">
<input type="submit">
</form>
<?php endif; ?>
在后端,您处理图片表单提交
if ( is_user_logged_in() && isset( $_REQUEST['picture_url'] ) ) {
// Save the picture here
}
现在,假设一个黑客想要提交一张伪造的图片。黑客没有用户名或密码——所以他们什么也做不了,对吧?
不完全是。为了劫持您的其中一个用户的帐户以提交伪造的图片,黑客可能会尝试让登录的用户点击如下所示的链接
http://your-site.com/submit-picture/?picture_url=fake-picture.jpg
如果登录的用户点击该链接,是什么阻止了图片被提交?您猜对了:**什么也没有**。黑客获胜。
这是因为我们没有做任何事情来验证用户提交图片的**意图**。“F”在 CSRF 中代表伪造:黑客代表用户伪造(伪造)了一个请求,有点像您在小学时伪造妈妈的签名在病假条上一样。
如何防止CSRF
我们可以通过使用 WordPress 中内置的一些便捷功能来阻止 CSRF 攻击。为了防止请求被成功“伪造”,WordPress 使用 Nonce(一次性数字)来验证请求是否确实由当前用户发出。
基本流程如下
- 生成一个Nonce。
- 该Nonce与表单一起提交。
- 在后端,检查Nonce的有效性。如果有效,则继续操作。如果无效,则所有操作都将停止——请求可能被伪造了!
让我们添加一个Nonce
在前端,假设我们想为表单提交添加一个Nonce。我们将使用 wp_nonce_field 便利函数来实现
<form action="/submit-picture/" method="get">
<input type="text" name="picture_url">
<input type="submit">
<?php wp_nonce_field( 'submit_picture' ); ?>
</form>
很简单,对吧?现在,当我们在后端处理此表单时,我们只需要使用一些 WordPress 内置函数来验证Nonce是否有效
// By default, we can find the nonce in the "_wpnonce" request parameter.
$nonce = $_REQUEST['_wpnonce'];
if ( ! wp_verify_nonce( $nonce, 'submit_picture' ) ) {
exit; // Get out of here, the nonce is rotten!
}
// Now we can process the submission
if ( is_user_logged_in() && isset( $_REQUEST['picture_url'] ) ) {
// Save the picture here
}
由于黑客不知道Nonce的值,因此他无法创建会导致危害的伪造链接。任何恶意尝试都将在wp_verify_nonce
检查时被阻止。我们阻止了黑客,对吧?!在许多情况下,是的。我们让黑客伪造请求变得更加困难。
Nonce是特定于用户的
但是,如果您的网站的一个恶意登录用户想要为另一个用户提交伪造的图片呢?恶意用户能否只是查看网站的 HTML 源代码,找到 Nonce 字段,并将其添加到他们的“恶意”URL 中,如下所示?
http://your-site.com/submit-picture/?picture_url=fake-picture.jpg&_wpnonce=NONCEVALUE
幸运的是,它不会起作用。WordPress Nonce是 特定于用户会话的,因此黑客无法用自己的Nonce替换另一个用户的Nonce。
感谢评论中 Mark Allen 指出特定于会话的Nonce。
尝试这些Nonce函数
WordPress 充满了便捷函数,使生成 Nonce(和防止 CSRF)变得更容易。以下是一些您可能会在不同情况下发现有用的函数
函数:wp_verify_nonce
功能:验证Nonce值是否由 WordPress 正确生成。
无论您如何生成 Nonce,都应在后端使用wp_verify_nonce
进行检查
就像上面的示例一样,使用wp_verify_nonce
验证用户的意图
$submitted_value = $_REQUEST['_wpnonce'];
if ( wp_verify_nonce( $submitted_value, 'your_action_name' ) ) {
// nonce was valid...
}
函数:wp_nonce_field
功能:打印包含 Nonce 值的隐藏<input>
标签。
用于构建需要防止 CSRF 的<form>
(几乎每个<form>
都需要)。
就像上面的示例一样,wp_nonce_field
是一个便捷函数,使我们构建 HTML 表单变得更容易
<form>
<!-- Other form fields go here -->
<?php wp_nonce_field( 'your_action_name' ); ?>
</form>
函数:wp_nonce_url
功能:返回附加了 Nonce 值的 URL。
用于构建需要将 Nonce 字段作为查询字符串参数附加的 URL。在需要 CSRF 验证的 GET 请求中非常有用。
这是一个wp_nonce_url
的示例,其中我们需要验证用户是否确实希望点击链接
<?php
$action_url = wp_nonce_url( '/change-color/?color=blue', 'change_color' );
// $action_url is now "/change-color/?color=blue&_wpnonce=GENERATED_VALUE"
?>
<a href="<?php echo esc_url( $action_url ); ?>">Change to blue</a>
函数:wp_create_nonce
功能:生成 Nonce 值。
用于需要不适合上述便捷函数的 Nonce 值的情况。
以下是如何使用wp_create_nonce
生成以 JSON 格式返回的 Nonce 值(在 AJAX 请求中返回时很有用)
<?php
$nonce_value = wp_create_nonce( 'your_action_name' );
return json_encode( array( 'nonce' => $nonce_value ) );
?>
函数:check_ajax_referer
功能:使用wp_verify_nonce
检查提交的 Nonce,如果验证失败,则停止执行。
check_ajax_referer
是另一个便捷函数,主要用于 AJAX 请求。您可以使用它来跳过自己执行wp_verify_nonce
检查
<?php
// Forged requests won't get past this line:
check_ajax_referer( 'your_action_name' );
// Do your action here
?>
所有内容都使用Nonce!
CSRF 漏洞的危害程度从无害(例如投票提交)到极其危险(例如修改密码或权限)不等。
无论严重程度如何,**只要用户在 WordPress 中执行操作**,都应该使用 nonce 来防止 CSRF 攻击。因为……为什么不呢?你肯定不希望黑客伪造你网站上的请求,而 WordPress 提供了阻止他们的工具。
使用 nonce,阻止伪造,挫败黑客!
我有一个问题 - 假设你想生成一个 nonce 作为唯一的哈希值。如何在后端验证随机生成的哈希值,以及最初如何生成它?这似乎是一种更安全的方法来保护表单提交。
这本质上就是 WordPress 使用
wp_create_nonce
等函数所做的事情 - 生成一个哈希值并在后端进行检查。你指的是 WP 如何做这件事的具体细节吗?这是关于 nonce 是什么以及它们在 WordPress 中的作用的最清晰的文章之一。我经常认为前端开发人员认为所有安全问题都是后端开发人员的责任。这完全是错误的。
我有一个问题是,多年来,Google CAPTCHA 在阻止垃圾邮件方面越来越没有效果。同样地,你认为 WP 中的 nonce 是否也存在随着时间的推移而风险增加的情况?如果是这样,你认为我们如何才能从被动反应转向主动预防这些安全风险?
仅供参考,我理解垃圾邮件与黑客不同。
WP nonce 并非完美无缺,但我认为它们没有 CAPTCHA 那样存在同样的问题。随着图像识别脚本在识别图像中的数字方面变得越来越好,CAPTCHA 的效果会降低。如果 nonce 正确使用,则无法以相同的方式“破解”它们。
我希望 WordPress 有自动 nonce 生成功能。例如,在 Ruby on Rails 中,你永远不必编写 nonce 生成或检查代码 - 这一切都是自动完成的。这将是防止 CSRF 攻击的一大进步。
在英国,“nonce”这个词的意思是“虐待儿童者”……因此,这个概念的国际化可能需要重新考虑!
感谢这篇文章,它很好地解释了如何防止
CSRF
攻击。但是,我很震惊文章中没有提到使用
POST
请求而不是GET
请求。使用POST
表单可以使CSRF
攻击更加困难,因为你无法简单地通过更改 URL 中的参数来伪造请求(使用POST
,URL 中没有参数)。对我来说,使用
POST
请求是基础,应该是第一步。也应该使用CSRF
令牌,但那只是第二步。此外,你最好依赖变量
$_POST
或$_GET
来获取你想要的确切值,因为$_REQUEST
是依赖于配置的,其内容可能会被 cookie 值更改(参见 https://php.ac.cn/manual/en/reserved.variables.request.php),因此我不建议在这种情况下使用它。在一般情况下,表单应使用
POST
而不是GET
(至少),当– 数据库内容被修改(这里就是这种情况)
– 用户的会话状态发生变化(例如连接和断开连接页面)
干杯!
Romain
POST 请求确实会让它变得更难(你不能只是诱骗某人点击一个链接),但并没有难很多。如果黑客可以让某人访问他们的网站,他们可以通过 JavaScript 发出 POST 请求。
我使用 GET 来使示例更简单,但你说得对 - POST 应该用于大多数表单提交。
我从未考虑过 CSRF。我不使用 WP,但我可以看到如何在所有用户操作上实现唯一的标识符。必须承认,安全是我最薄弱的一环(Chris,你应该对此进行一个投票),但显然有一些简单的措施我们都应该实施 - 就像我们(尝试)为性能提升保留一个简短的清单一样。
这个用户特定的 nonce 只是将用户的 ID 附加到 nonce 上吗?如果是这样,攻击者能否简单地将 nonce 的最后一部分更改为相应的用户 ID?
好问题!用户 ID 用于 *生成* nonce,因此该值对于该用户来说是唯一的。用户 ID 在 nonce 值中不可见,因此黑客无法简单地附加 ID。
你实际上不需要创建用户特定的 nonce。WordPress 会为你完成这件事。WordPress nonce 实际上是特定于用户会话的,因此即使以相同用户身份在其他地方登录也无法使用它。
请参阅 Codex https://codex.wordpress.org/WordPress_Nonces#Creating_a_nonce。
你说得对!文章已更新以修正我的错误。感谢你抽出时间更正我!
还可以使用
check_admin_referer()
,就像使用check_ajax_referer()
一样。为了提高安全性,你可以使用
current_user_can()
和相应的权限来检查权限。