【问题标题】:How can I pass a token generated on one PHP form to a form that processes it?如何将在一个 PHP 表单上生成的令牌传递给处理它的表单?
【发布时间】:2014-09-23 04:03:50
【问题描述】:

我正在尝试通过在每个表单上生成一个唯一令牌来保护我的表单免受 CSRF 攻击。我的表单上有一个隐藏字段,它会生成一个独特的令牌,如下所示:

public static function generateToken() {
        return $_SESSION['token'] = md5(uniqid(mt_rand(), TRUE));
}

我知道 md5 已贬值,不应该使用,因为它不像以下那样随机:

base64_encode(openssl_random_psuedo_bytes(32));

但我目前没有安装 ssl,我会处理这个问题。但我的主要问题是,在生成此令牌后,它会存储到用户当前所在的会话中,如下所示:

$_SESSION['token'] = $_POST['token'];

它获取包含生成令牌的字段的值。现在我已将此令牌存储到会话中,我需要弄清楚将其发送到处理文件。我在这个表单上有很多字段,所以在一个 if() 语句中检查每一件事看起来有点奇怪。我想我想说的是,如何在处理数据的表单上检查此令牌,如果令牌与表单上生成的令牌不同,我可以拒绝该数据吗?直接用die()杀掉处理脚本?

我也想知道我是否需要这样做 - 我已经阅读了 CSRF,但我想有人能做的最糟糕的事情就是更改一些正在发送以进行处理的信息,这就是为什么我要想要传递令牌来检查和拒绝数据就是这种情况。

<form name="form1" action="processing.php" method="POST" enctype="multipart/form-data">

这就是我所说的其他文件。 PHP 不是全部在一个地方,我通过 $_POST 方法获取处理脚本上的所有内容。

【问题讨论】:

  • 我们需要生成一个令牌将其存储在会话中,并且还需要在表单中有一个具有相同值的字段,例如通过回显它。当表单被提交回来时,我们需要检查/比较我们在会话中保存的值,以验证我们呈现的表单仅被提交,而不是来自不同站点的一些随机表单被提交到我们的站点。

标签: php forms csrf


【解决方案1】:
  1. 在服务器端(sql 或其他......)生成并保存令牌
  2. 使用令牌创建 cookie(客户端)
  3. 提交表单时,从 cookie 中检索令牌
  4. 如果 cookie 和保存的 token 一样,没关系

生成令牌:

$formName = "signin"; // name of the form 
$ip       = get_ip(); // create a function to get the ip of the user
$salt     = "8077_(-(àyhvboyr(à"; // uniq secret salt
$token    = md5(sha1($salt.$ip).sha1($salt.$formName));
...

if(empty($_COOKIE['token'])){ // if(empty($_SESSION['token'])){ 

    setcookie("token",$token, time()+3600*1); // use with cookie // time()+3600*1 = now + 1 hour
    $_SESSION['token'] = $token; // OR use with session

}else{

    // when the form is submit regenerate the token and compare with the request token
    if($_COOKIE['token'] == $token){ // OR if($_SESSION['token'] == $token){
       // request from server - ok
       // ...
    }else{
        // bad request
        setcookie("token",0); // destruct cookie
        $_SESSION['token'] = "";  // OR destruct session
        // redirect at the first form
    }
}


<form>
    // if you want use the token directly in your form, add this :
    <input type="hidden" name="token" value="<?php echo $token ?>" />
</form>

【讨论】:

  • 我认为我们不应该使用 cookie 来验证令牌。它应该是一个表单域。
  • 我仍然不确定如何比较表单本身字段中生成的令牌并将其传递给处理脚本,在该脚本中将比较字段令牌和服务器上的令牌。如果我们将令牌传递给服务器端,它不会还是我们最初生成的那个吗?它如何/为什么会改变?
  • 令牌是 uniq by user (ip..) 和 uniq by form (例如,你也可以添加时间限制).. 所以,你确定用户没有改变并且来自您的网站。
  • 我们永远不应该信任cookies,因为即使是那些可以由浏览器设置并发送的cookies。很有可能它们(表单字段和 cookie)都由第三方设置
  • 谢谢@Pirs!这真的帮助了我:)
【解决方案2】:
$formName = "signin"; // name of the form
$ip       = get_ip(); // create a function to get the ip of the user
$salt     = "8077_(-(àyhvboyr(à"; // uniq secret salt
$token    = md5(sha1($salt.$ip).sha1($salt.$formName));

$_SESSION["token"]=$token;
...
<form action="submit.php" >
// if you want use the token directly in your form, add this :
<input type="hidden" name="token" value="<?php echo $token ?>" />
</form>

在 submit.php 中我们需要写类似

if($_SESSION['token'] == $_POST['token']){ 
   // request from server
}else{
   // external request
}

【讨论】:

  • @Pirs 抱歉,我只是想说有变化。我们需要使用会话而不是烹饪
  • 为什么if($_POST['token'] == $token 被注释掉了?此外,如果它是来自服务器的请求 - 这意味着可以安全地继续处理,因为外部请求将成为终止脚本的理由?这对我有很大帮助 Sunand,谢谢!
  • @LoudGraveyard 做了更改,请检查
  • 是的,我确实重新检查了。谢谢你,我很感激你的澄清。我将选择@Pirs,因为他编写了大部分原始代码。不过,我非常感谢你们俩的帮助!
  • @LoudGraveyard 我不是为了得分,我当然支持你的决定。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-10
  • 1970-01-01
  • 2011-01-03
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 2018-11-11
相关资源
最近更新 更多