【问题标题】:Randomized $_SESSION token is changed before validation when PHP file is specified in built-in server在内置服务器中指定 PHP 文件时,在验证之前更改随机 $_SESSION 令牌
【发布时间】:2025-12-05 05:30:02
【问题描述】:

我正在生成一个唯一令牌并将其保存在每个请求的会话变量中(以典型的 CSRF 保护方式)。令牌在使用 POSTED 令牌值检查验证后刷新。

这是我的代码(index.php):

<?php
    
session_start();

if (!empty($_POST['token'])) {
    var_dump($_POST['token'], $_SESSION['token']);
    exit;
}

$_SESSION['token'] = rand();

echo '<form action="index.php" method="post"><input name="token" value="' . $_SESSION['token'] . '"></form>';

当我使用php -S localhost:8888 运行脚本时,它运行良好。但是当我指定像php -S localhost:8888 index.php 这样的index.php 文件时,$_SESSION['token'] 会发生变化。 ($_POST['token']$_SESSION['token'] 不匹配)。

php -S localhost:8888

before after

php -S localhost:8888 index.php

before after

我也尝试过使用路由文件。它也不起作用。 php -S localhost:8888 server.php

<?php
// server.php

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = urldecode($uri);

if ($uri !== '/' and file_exists($uri))
{
    return false;
}

require_once 'index.php';

控制台输出:

php -S localhost:8888

php -S localhost:8878 
[Mon Mar 29 11:49:49 2021] PHP 8.0.3 Development Server (http://localhost:8878) started 
[Mon Mar 29 11:49:52 2021] [::1]:47410 Accepted 
[Mon Mar 29 11:49:52 2021] [::1]:47412 Accepted 
[Mon Mar 29 11:49:52 2021] [::1]:47410 [200]: GET / 
[Mon Mar 29 11:49:52 2021] [::1]:47410 Closing 
[Mon Mar 29 11:49:53 2021] [::1]:47412 [404]: GET /favicon.ico - No such file or directory 
[Mon Mar 29 11:49:53 2021] [::1]:47412 Closing

php -S localhost:8888 server.php

php -S localhost:8858 server.php
[Mon Mar 29 11:48:51 2021] PHP 8.0.3 Development Server (http://localhost:8858) started 
[Mon Mar 29 11:48:53 2021] [::1]:33156 Accepted 
[Mon Mar 29 11:48:53 2021] [::1]:33158 Accepted 
[Mon Mar 29 11:48:53 2021] [::1]:33156 Closing 
[Mon Mar 29 11:48:54 2021] [::1]:33158 Closing

测试使用:

PHP 7.3.27-1~deb10u1 (cli) (built: Feb 13 2021 16:31:40) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.27, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.27-1~deb10u1, Copyright (c) 1999-2018, by Zend Technologies

PHP 8.0.3 (cli) (built: Mar  5 2021 08:38:30) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.3, Copyright (c), by Zend Technologies

这是 PHP 内置服务器的错误吗?

【问题讨论】:

  • 在这种情况下为什么要将文件传递给服务器?
  • @Steven 为什么不呢?它不应该有所作为,不是吗?传递index.php 有什么缺点吗?
  • @brombeer 是的,传递带有-S 的文件意味着该文件充当路由器文件,预计返回内容或false
  • @Steven 但是那个脚本正在返回内容吗?
  • @brombeer 是的,确实如此,从技术上讲,我确实希望这段代码能够工作。但事实并非如此。因此,理解为什么 OP 以它们的方式做事(这会改变 PHP 的行为)似乎是一个明智的起点......作为参考,我的第二个问题是“表单是如何提交的?”

标签: php session token csrf php-builtin-server


【解决方案1】:

路由文件

这是因为您在命令中将选项设置为php: you set the routing file to be index.php`...

php -S localhost:8888 index.php
                      ^^^^^^^^^

...这意味着每个请求都将首先通过该文件,然后再决定要做什么。例如,假设我们的index.php 文件包含以下内容:

if (rand(1,100) % 2) {
    echo "Not a multiple of 2\n";
    return true;
} else {
    echo "Multiple of 2\n";
    return false;
}

注意:return true; 在技术上是不需要的,但为了清楚起见,我已将其包括在内

这里发生的是发出请求并运行路由文件。如果路由文件返回false(即是2 的倍数),则请求将通过请求的文件。如果代码返回true,则执行停止并且请求的文件不会被访问。

在您的情况下,请求的文件是index.php(同一个文件),因此,使用上面的代码,您将始终得到如下输出:

Not a multiple of 2

// OR

Multiple of 2
Multiple of 2

// OR 

Multiple of 2
Not a multiple of 2

使用您的代码/设置分解 http 请求

所以,看看你的实际代码...

session_start();

if (!empty($_POST['token'])) {
    var_dump($_POST['token'], $_SESSION['token']);
    exit;
}

$_SESSION['token'] = rand();

echo '<form ...>...</form';

这里发生了什么?

  1. 您请求index.php*
  2. 服务器访问路由文件index.php来决定做什么
  3. 路由文件输出的数据有效表示true
  4. 返回路由文件中的数据(表单)并设置 SESSION 变量
    • 文件请求被忽略
  5. 您提交表单
  6. 请求通过路由文件来决定做什么(不是属性action**的位置)
  7. 路由文件检查POST&gt;tokenSESSION&gt;token
  8. 它们应该匹配,因此路由文件有效地输出指示true***的数据
  9. 数据返回
    • 文件请求(来自action 属性)被忽略

* 你尝试访问的文件是 0 区别;用infkdsngfdslghfdslgnfdg.php 试试,它仍然会以同样的方式工作。您请求的文件永远不会被访问,它似乎只是因为请求的文件和路由的文件是相同的!

** 如上,你可以将action属性设置为几乎任何东西,试试fdsfnldgksdf.php

***令牌是否匹配路由文件仍然输出等于true的数据

根据@brombeer 的测试,这确实按预期工作,那么为什么它不适合你呢?

问题

如果您检查服务器正在运行的命令提示符/终端,您会得到正在发生的事情的流(例如,当请求被接受时等)。您会注意到,如果您在发出请求时看到它,您会得到如下结果:

[DATE] [::1]:XXX01 Accepted
[DATE] [::1]:XXX02 Accepted
[DATE] [::1]:XXX01 Closing
[DATE] [::1]:XXX02 Closing

另一方面,@brombeer 会得到如下结果:

[DATE] [::1]:XXX01 Accepted
[DATE] [::1]:XXX01 Closing

这就是问题所在。您正在向@brombeer 的一个请求发出两个请求,并且两个请求都通过路由文件。

第一个请求是您所期望的,并且您会得到预期的输出。但是,在您收到该输出后,第二个请求将运行(遵循与上述完全相同的流程 - 请记住,请求什么 file 并不重要,脚本将输出相同的内容(!) -并有效地将$_SESSION["token"] 更改为新的随机数。

如果你改变这可能会更容易看到......

$_SESSION["token"] = rand();

...到...

$_SESSION["token"]++;

第二个请求是什么?

这与 PHP 无关;这完全与您的浏览器有关。浏览器对请求的文件以外的各种事物发出请求。例如:

  • JavaScript 文件
  • CSS 文件
  • 页面上使用的图片

当然,在这个例子中你没有使用任何一个。但是,浏览器会查找一些资源,不管你是否告诉它们:通常基于上下文。

在这种情况下,您的浏览器足够智能,可以知道您正在尝试访问网站(可能是因为端口号,也可能是因为请求方法或 URI)。

因此,它会尝试找到一些希望在网站上找到的其他文件,特别是:favicon.ico(您应该能够在 Network 下的 Bowser 开发工具中看到此请求)。

正如已经解释的那样,因为您已经使用路由文件设置了服务器,所以请求与index.phpfnjksgjfndsglkjnsf.php 请求经历相同的过程。事实上,实际的图标文件甚至从未被查找过。

您可以通过将此代码添加到文件顶部来进一步证明这一点...

if (!strpos($_SERVER["REQUEST_URI"], ".php")) {
    return false;
}

如果请求的文件不是php 文件,这将停止在您的路由文件中执行。此外,因为我们返回false,服务器将查找图标文件。返回true 也可以,但不会查找图标文件。

您也可以尝试将端口更改为:8030 之类的东西,我希望代码会按您的预期工作(因为浏览器不会请求网站图标)。

解决方案

回到我对这个问题的第一条评论......

在这种情况下,为什么要将文件传递给服务器?

我仍然不确定您为什么这样做:我认为这是因为您不了解路由文件的作用?或者你不明白你正在创建一个路由文件?

希望我们已经在这里解决了这个问题?

无论哪种方式,我都相当肯定设置 routing file 不是您想要的,而且出于您的目的,它似乎也不是您需要的。

所以不要在命令末尾添加index.php


其他工作示例

将您的index.php 替换为以下代码:

session_start();

echo "<pre>";

if (!empty($_POST['token'])) {
    var_dump($_POST['token'], $_SESSION['token']);
    exit;
}

$_SESSION['token']++;

echo '<form action="indasdasfdex.php" method="post"><input name="token" value="' . $_SESSION['token'] . '"><input type="submit" value="submit"></form>';

var_dump($_POST['token'] ?? null, $_SESSION['token'] ?? null);

使用index.php 作为路由器文件运行您的服务器(根据您的原始问题):

php -S localhost:8888 index.php

// Because you require `index.php` in your `server.php`
// this will work the same if you use `server.php` instead

会发生这种情况

请求:index.php

Router file runs `$_SESSION["token"] == 1`
Router file returns data to browser with a form: `"token" == 1`
Request terminated
Browser shows returned data
    Form: `token.value == 1`
    `var_dump` output
        `$_POST["token"] == null`
        `$_SESSION["token"] == 1`

请求:favicon.ico

Router file runs `$_SESSION["token"] == 2`
Router file returns data to browser with a form `"token" == 2`
Request terminated
Browser doesn't show returned data (but you can see it in dev tools)
    _Not shown because an image isn't returned!_
    _Can be seen in dev tools_
    Form: `token.value == 2`
    `var_dump` output
        `$_POST["token"] == null`
        `$_SESSION["token"] == 2`

请求:提交表单到index.php

Router file runs
    Dumps `$_SERVER["token"]` and `$_POST["token"]` (2,1)
Router file `exit`
Request terminated
Browser shows returned data
    `var_dump` output
        `$_POST["token"] == 1`
        `$_SESSION["token"] == 2`

请求:favicon.ico

Router file runs `$_SESSION["token"] == 3`
Router file returns data to browser with a form `"token" == 3`
Request terminated
Browser doesn't show returned data (but you can see it in dev tools)
    _Not shown because an image isn't returned!_
    _Can be seen in dev tools_
    Form: `token.value == 3`
    `var_dump` output
        `$_POST["token"] == null`
        `$_SESSION["token"] == 3`**

【讨论】:

  • 感谢您的回答。我确实知道路由器文件是什么,我也尝试过。 php -S localhost:8888 router.php这里是server.php的内容&lt;?php $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $uri = urldecode($uri); if ($uri !== '/' and file_exists($uri)) { return false; } require_once 'index.php';
  • 我已编辑问题以包含 server.php
  • 我在使用路由器文件时得到这个输出。 php -S localhost:8858 server.php [Mon Mar 29 11:48:51 2021] PHP 8.0.3 Development Server (http://localhost:8858) started [Mon Mar 29 11:48:53 2021] [::1]:33156 Accepted [Mon Mar 29 11:48:53 2021] [::1]:33158 Accepted [Mon Mar 29 11:48:53 2021] [::1]:33156 Closing [Mon Mar 29 11:48:54 2021] [::1]:33158 Closing
  • 不使用路由器文件。 php -S localhost:8878 [Mon Mar 29 11:49:49 2021] PHP 8.0.3 Development Server (http://localhost:8878) started [Mon Mar 29 11:49:52 2021] [::1]:47410 Accepted [Mon Mar 29 11:49:52 2021] [::1]:47412 Accepted [Mon Mar 29 11:49:52 2021] [::1]:47410 [200]: GET / [Mon Mar 29 11:49:52 2021] [::1]:47410 Closing [Mon Mar 29 11:49:53 2021] [::1]:47412 [404]: GET /favicon.ico - No such file or directory [Mon Mar 29 11:49:53 2021] [::1]:47412 Closing
  • @adhm 也许我没有说清楚。您的命令行php -S localhost:8888 index.php 使用index.php 作为路由器文件。正如我的回答中所述,这是您的问题。这有意义吗?
最近更新 更多