如何在 PHP 中检查不完整的 POST 请求


How to check for incomplete POST request in PHP

当连接速度较慢的远程 Web 客户端无法发送包含multipart/form-data内容的完整 POST 请求时,我遇到了问题,但 PHP 仍然使用部分接收的数据来填充数组$_POST。因此$_POST数组中的一个值可能不完整,并且可能会丢失更多值。我试图首先在 Apache 列表中提出相同的问题,并得到一个答案,即 Apache 不缓冲请求正文并将其作为 PHP 模块传递给巨大的斑点。

这是我的示例 POST 请求:

POST /test.php HTTP/1.0
Connection: close
Content-Length: 10000
Content-Type: multipart/form-data; boundary=ABCDEF
--ABCDEF
Content-Disposition: form-data; name="a"
A
--ABCDEF

您可以看到Content-Length10000 字节,但我只发送一个 var a=A.

PHP 脚本是:

<?php print_r($_REQUEST); ?>

Web 服务器等待大约 10 秒来完成我的其余请求(但我不发送任何内容(,然后返回以下响应:

HTTP/1.1 200 OK
Date: Wed, 27 Nov 2013 19:42:20 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.4-14+deb7u3
Vary: Accept-Encoding
Content-Length: 23
Connection: close
Content-Type: text/html
Array
(
     [a] => A
)

所以我的问题来了:如何在 PHP 中验证 post 请求是否已完全收到? $_SERVER['CONTENT_LENGTH']会显示请求标头中的10000,但是有没有办法检查收到的实际内容长度?

我猜远程客户端实际上是一个带有 HTML 页面的浏览器。 否则,请告诉我,我会尝试调整我的解决方案。

例如,您可以添加字段<input type="hidden" name="complete">作为最后一个参数。 PHP首先检查此参数是否从客户端发送。 如果此参数已发送 - 您可以确定您获得了整个数据。

现在,我不确定参数的顺序是否必须根据RFC(HTML和HTTP(保留。 但是我已经尝试了一些变化,我看到顺序确实保持不变。

更好的解决方案是计算(在客户端(参数的哈希并将其作为另一个参数发送。 因此,您可以绝对确定您获得了整个数据。但这听起来开始很复杂...

我所知,当使用multipart/form-data作为Content-Type时,无法检查接收内容的大小是否与Content-Length标头的值匹配,因为您无法掌握原始内容。

1(如果可以更改Content-Type(例如更改为application/x-www-form-urlencoded(,则可以读取php://input,它将包含请求的原始内容。php://input的大小应与Content-Length匹配(假设 Content-Length 的值正确(。如果存在匹配项,您仍然可以使用 $_POST 来获取已处理的内容(常规帖子数据(。在此处阅读有关php://input的信息。

2(或者您可以在客户端上序列化数据并将其作为text/plain发送。服务器可以采用与上述相同的方式检查大小。服务器需要反序列化接收到的内容才能使用它。如果客户端生成序列化数据的哈希值并将其发送到标头中(例如X-Content-Hash(,服务器还可以生成哈希并检查它是否与标头中的哈希匹配。您无需检查哈希,并且可以 100% 确定内容正确。

3(如果您无法更改Content-Type,则需要与大小不同的内容来验证内容。客户端可以使用额外的标头(类似于 X-Form-Data-Fields (来汇总您要发送的内容的字段/键/名称。然后,服务器可以检查标题中提到的所有字段是否都存在于内容中。

4( 另一种解决方案是让客户端将预定义的键/值作为内容中的最后一个条目。像这样:

--boundary
Content-Disposition: form-data; name="_final_field_"
TRUE
--boundary--

服务器可以检查内容中是否存在该字段,如果是,则内容必须完整。

更新

当您需要传递二进制数据时,不能使用选项 1,但仍可以使用选项 2:

客户端可以base64对二进制条目进行编码,序列化数据(使用您喜欢的任何技术(,生成序列化数据的哈希,将哈希作为标头发送,将数据作为正文发送。服务器可以生成接收内容的哈希,检查哈希与标头中的哈希(并报告不匹配(,反序列化内容,base64解码二进制条目。

这比使用multipart/form-data要多一些工作,但服务器可以 100% 保证内容与客户端发送的内容相同。

如果您可以将 enctype 更改为

multipart/form-data-alternate

你可以检查

strlen(file_get_contents('php://input'))

与。

$_SERVER['CONTENT_LENGTH']

这是 PHP 中的一个已知错误,需要在那里修复 - https://bugs.php.net/bug.php?id=61471

它们可能会被 Apache 或 PHP 中的限制切断。我相信 Apache 也有一个配置变量。

以下是 PHP 设置;

PHP.ini

post_max_size=20M
upload_max_filesize=20M

.htaccess

php_value post_max_size 20M
php_value upload_max_filesize 20M

对于由于连接问题而完全丢失的表单值,您可以检查它们是否已设置:

if(isset($_POST['key']){
    //value is set
}else{
    //connection was interrupted
}

对于大型表单数据(例如图像上传(,您可以使用以下命令检查收到的文件的大小

$_FILES['key']['size']

一个简单的解决方案可以使用 JavaScript 在客户端计算文件大小,并将该值作为表单提交的隐藏输入附加到表单中。 您可以使用类似的东西在 JS 中获取文件大小

var filesize = input.files[0].size;

参考:JavaScript 文件上传大小验证

然后在文件上传

时,如果隐藏表单输入的值与上传文件的大小匹配,则请求不会因网络连接问题而中断。

也许您可以使用有效的变量进行检查,但不能检查长度,例如:

// client
$clientVars = array('var1' => 'val1', 'otherVar' => 'some value');
ksort($clientVars);  // dictionary sorted
$validVar = md5(implode('', $clientVars));
$values = 'var1=val1&otherVar=some value&validVar=' . $validVar;
httpRequest($url, values);
// server
$validVar = $_POST['validVar'];
unset($_POST['validVar']);
ksort($_POST);  // dictionary sorted
if (md5(implode('', $_POST)) == $validVar) {
    // completed POST, do something
} else {
    // not completed POST, log error and do something
}

我也建议使用hidden值,或者像MeNa提到的那样进行哈希处理。(问题是某些算法在平台上的实现方式不同,因此 js 中的 CRC32 可能与 PHP 中的 CRC32 不同。但是通过一些测试,您应该能够找到兼容的(

我建议使用对称加密,只是因为它是一种选择。(我不相信它比哈希更快(。加密提供,除了机密性还有完整性,即。收到的消息是发送的消息吗?

虽然流密码非常快,但

像AES这样的分组密码也可以非常快,但这取决于你的系统,你使用的语言等(同样在这里,不同的实现意味着并非所有加密都是平等的(

如果您无法解密消息(或者它会产生乱码(,则消息不完整。

但说真的,使用哈希。 在客户端上对 POST 进行哈希处理,首先检查服务器上哈希的长度。(一些?哈希是固定长度,因此如果长度不匹配,则为错误。然后对收到的 POST 进行哈希处理,并与 POST 哈希进行比较。如果在整个开机自检期间执行此操作,则按指定的顺序(因此撤消任何重新排序(,则开销最小。

所有这一切,假设您无法检查帖子消息以查看字段是否丢失并且is_set==True,长度> 0,!empty((...

我认为您要查找的是$HTTP_RAW_POST_DATA,这将为您提供真实的POST长度,然后您可以将其与$_SERVER['CONTENT_LENGTH']进行比较。

我认为不可能从 $_REQUEST 超全局计算原始内容大小,至少对于多部分/表单数据请求。

我会在您的 http 请求中添加一个自定义标头,其中包含所有参数=值哈希,以供服务器端检查。标头肯定会到达,因此您的哈希标头始终存在。请务必按相同的顺序联接参数,否则哈希将有所不同。还要注意编码,客户端和服务器上必须相同。

如果可以配置 Apache,则可以添加具有 mod_proxy 的虚拟主机,配置为在同一服务器上的另一个虚拟主机上代理。这应该过滤不完整的请求。请注意,您以这种方式为每个请求浪费了 2 个套接字,因此如果您想这样做,请留意资源使用情况。

其他一些可能有用的解决方案...如果另一端的连接速度很慢,只需删除执行帖子的限制即可。

set_time_limit(0);

而且您将确保将发送孔柱数据。

如果计算内容长度不合理,您可能会对客户端发送的数据进行签名。

使用 javascript,在提交之前以合理理智的方式将表单数据序列化为 json 字符串或等效项(即根据需要对其进行排序(。使用一个或两个相当快的算法(例如crc32,md5,sha1(对这个字符串进行哈希处理,并将这个额外的哈希数据添加到即将作为签名发送的内容中。

在服务器上,从 $_POST 请求中去除此额外的哈希数据,然后在 PHP 中重做相同的工作。相应地比较哈希:如果哈希匹配,则在翻译中不会丢失任何内容。(如果要消除误报的微小风险,请使用两个哈希。

我敢打赌,有一种合理的方法可以对文件做类似的事情,例如在JS中获取它们的名称和大小,并将附加信息添加到签名的数据中。

这与一些PHP框架为避免篡改会话数据所做的工作有关,当后者被管理和存储在客户端cookie中时,因此您可能会找到一些现成的代码来在后一种情况下执行此操作。


原答案:

据我所知,发送 GET 或 POST 请求之间的区别或多或少与发送以下内容的数量不同:

GET /script.php?var1=foo&var2=bar
headers

与发送类似以下内容:

POST /script.php
headers
var1=foo&var2=bar              <— content length is the length of this chunk

因此,对于每个部分,您可以计算长度并检查该长度与内容长度标头通告的长度。

  • $_FILES条目都有一个方便的大小字段,您可以直接使用。
  • 对于$_POST数据,请重新生成发送的查询字符串并计算其长度。

需要注意的要点:

  1. 您需要知道在某些情况下预期如何发送数据,例如 var[]=foo&var[]=bazvar[0]=foo&var[1]=baz
  2. 在后一种情况下,您处理的是 C 字符串长度而不是多字节长度。(不过,如果得知一个奇怪的浏览器在这里和那里的行为不一致,我不会感到惊讶。

延伸阅读:

  • http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13
  • http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4