如何使用 PHP 正确添加跨站请求伪造 (CSRF) 令牌
我正在尝试为我网站上的表单添加一些安全性。其中一个表单使用 AJAX,另一个表单是简单的“联系我们”表单。我正在尝试添加 CSRF 令牌。我遇到的问题是令牌有时只显示在 HTML“值”中。其余时间,该值为空。这是我在 AJAX 表单上使用的代码:
PHP :
if (!isset($_SESSION)) {
session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token'])) {
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
HTML :
<input type="hidden" name="token" value="<?php echo $token; ?>" />
有什么建议吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
对于安全代码,请不要通过以下方式生成令牌:
$token = md5(uniqid(rand(), TRUE));
rand()
是可预测的uniqid()
仅添加最多 29 位熵md5()
不会添加熵,它只是确定性地混合它尝试一下:
生成 CSRF 令牌
PHP 7
旁注:我雇主的开源项目之一items 是一项将
random_bytes()
和random_int()
反向移植到 PHP 5 项目中的计划。它已获得 MIT 许可,并可在 Github 和 Composer 上以 paragonie/random_compat 的形式获取。PHP 5.3+(或使用 ext-mcrypt)
验证 CSRF 令牌
不要只使用
==
甚至===
,使用hash_equals()
(仅限 PHP 5.6+,但可用于带有 hash-compat 库)。进一步使用每个表单令牌
您可以使用
hash_hmac( )
。 HMAC 是一种特殊的带密钥的哈希函数,即使使用较弱的哈希函数(例如 MD5),也可以安全使用。不过,我建议改用 SHA-2 系列哈希函数。首先,生成第二个令牌用作 HMAC 密钥,然后使用如下逻辑来呈现它:
然后在验证令牌时使用全等操作:
在不知道
的情况下,为一种形式生成的令牌不能在另一个上下文中重用$_SESSION['second_token']
。 请务必使用单独的令牌作为 HMAC 密钥,而不是刚刚放在页面上的令牌。奖励:混合方法 + Twig 集成
任何使用 Twig 模板引擎 可以通过将此过滤器添加到其 Twig 环境中,从简化的双重策略中受益:
通过此 Twig 功能,您可以像这样使用通用令牌:
或者锁定的变体:
Twig 只关心模板渲染;您仍然必须正确验证令牌。在我看来,Twig 策略提供了更大的灵活性和简单性,同时保持了最大安全性的可能性。
一次性 CSRF 令牌
如果您有安全要求,即允许每个 CSRF 令牌只能使用一次,则最简单的策略是在每次成功验证后重新生成它。然而,这样做会使之前的每个令牌失效,这与同时浏览多个选项卡的人不能很好地混合。
Paragon Initiative Enterprises 为这些极端情况维护了一个Anti-CSRF 库。它专门与一次性执行表单令牌一起使用。当会话数据中存储了足够的令牌(默认配置:65535)时,它将首先循环出最旧的未兑换令牌。
For security code, please don't generate your tokens this way:
$token = md5(uniqid(rand(), TRUE));
rand()
is predictableuniqid()
only adds up to 29 bits of entropymd5()
doesn't add entropy, it just mixes it deterministicallyTry this out:
Generating a CSRF Token
PHP 7
Sidenote: One of my employer's open source projects is an initiative to backport
random_bytes()
andrandom_int()
into PHP 5 projects. It's MIT licensed and available on Github and Composer as paragonie/random_compat.PHP 5.3+ (or with ext-mcrypt)
Verifying the CSRF Token
Don't just use
==
or even===
, usehash_equals()
(PHP 5.6+ only, but available to earlier versions with the hash-compat library).Going Further with Per-Form Tokens
You can further restrict tokens to only be available for a particular form by using
hash_hmac()
. HMAC is a particular keyed hash function that is safe to use, even with weaker hash functions (e.g. MD5). However, I recommend using the SHA-2 family of hash functions instead.First, generate a second token for use as an HMAC key, then use logic like this to render it:
And then using a congruent operation when verifying the token:
The tokens generated for one form cannot be reused in another context without knowing
$_SESSION['second_token']
. It is important that you use a separate token as an HMAC key than the one you just drop on the page.Bonus: Hybrid Approach + Twig Integration
Anyone who uses the Twig templating engine can benefit from a simplified dual strategy by adding this filter to their Twig environment:
With this Twig function, you can use both the general purpose tokens like so:
Or the locked down variant:
Twig is only concerned with template rendering; you still must validate the tokens properly. In my opinion, the Twig strategy offers greater flexibility and simplicity, while maintaining the possibility for maximum security.
Single-Use CSRF Tokens
If you have a security requirement that each CSRF token is allowed to be usable exactly once, the simplest strategy regenerate it after each successful validation. However, doing so will invalidate every previous token which doesn't mix well with people who browse multiple tabs at once.
Paragon Initiative Enterprises maintains an Anti-CSRF library for these corner cases. It works with one-use per-form tokens, exclusively. When enough tokens are stored in the session data (default configuration: 65535), it will cycle out the oldest unredeemed tokens first.
看起来你的 if 需要一个 else 。
Looks like you need an else with your if.
当变量
$token
位于会话中时,不会从会话中检索该变量The variable
$token
is not being retrieved from the session when it's in there