【问题标题】:Why are there binary safe AND binary unsafe functions in php?为什么 php 中有二进制安全和二进制不安全函数?
【发布时间】:2016-08-24 08:22:57
【问题描述】:

这种行为/实施是否有任何原因?
示例:

$array = array("index_of_an_array" => "value");
class Foo {
    private $index_of_an_array;
    function __construct() {}   
}
$foo = new Foo();
$array = (array)$foo;
$key = str_replace("Foo", "", array_keys($array)[0]);
echo $array[$key];

给我们一个错误这是完整的

注意未定义索引:第 9 行

示例 #2:

echo date("Y\0/m/d");

输出:

2016

但是! 例如,echovar_dump() 和其他一些函数会“按原样”输出字符串,只有 \0 字节被浏览器隐藏。

$string = "index-of\0-an-array";
$strgin2 = "Y\0/m/d";
echo $string;
echo $string2;
var_dump($string);
var_dump($string2);

输出:

数组索引
“年/月/日”
string(18) "数组索引"
string(6) "年/月/日"

注意,$string 长度为 18,但显示了 17 个字符。

编辑

来自possible duplicatephp manual

键可以是整数或字符串。该值可以是任何类型。 包含有效整数的字符串将被转换为整数类型。例如。键“8”实际上将存储在 8 下。另一方面,“08”不会被强制转换,因为它不是有效的十进制整数。所以简而言之,任何字符串都可以是键。一个字符串可以包含任何二进制数据(最大 2GB)。因此,键可以是任何二进制数据(因为字符串可以是任何二进制数据)。

来自php string details:

字符串可以包含的值没有限制; 特别是,任何地方都允许值为 0 的字节(“NUL 字节”) 在字符串中(但是,有几个函数,在本手册中说不是 “二进制安全”,可能会将字符串交给忽略数据的库 在一个 NUL 字节之后。)

但我还是不明白为什么语言是这样设计的?这种行为/实施是否有原因?为什么 PHP 不会在任何地方都将输入作为二进制安全处理,而只是在某些函数中处理?

来自comment:

原因很简单,很多像printf这样的PHP函数在后台使用了C库的实现,因为PHP开发者很懒。

不是echo, var_dump, print_r 吗?换句话说,输出一些东西的函数。如果我们看一下我的第一个示例,它们实际上是二进制安全的。为输出实现一些二进制安全和二进制不安全函数对我来说毫无意义。或者只是使用 C 中的 std lib 中的一些,并编写一些全新的函数。

【问题讨论】:

  • 嗯,\0 表示字符串的结尾...如果将其放在双引号之间,它将被解释。你试过把它放在单引号里吗?
  • 因为它便宜且易于编写,因为 C 可以完成大部分工作。
  • @Alnitak - 你的评论有什么意义?我在这里偶然发现使用谷歌,现在我正在阅读一个 200k 代表成员使用任何事实来抨击一种语言。当 SO 的老牌成员在没有任何事实的情况下四处游荡时,我们这些普通人是否应该信任他们?
  • 在预期只使用长度的例程中,“C”空终止字符串的问题可能是“尴尬”的。一些较旧的“安全”程序可能存在问题。想象一下有人使用的密码中包含一个空字节?空字节之后的所有内容都将被忽略?

标签: php string io binary-data


【解决方案1】:

问题中的拳头示例

您的第一个示例令人困惑,因为错误消息是在空字符处终止的部分,而不是因为数组未正确处理字符串。您发布的带有错误消息的原始代码如下:

$array = array("index-of-an-array" => "value");
$string = "index-of\0-an-array";
echo $array[$string];

注意:未定义的索引:index-of in

请注意,上面的错误消息由于空字符而被截断index-of,数组按预期工作,因为如果您尝试这种方式,它将正常工作:

$array = array("index-of\0-an-array" => "value");
$string = "index-of\0-an-array";
echo $array[$string];

错误消息正确识别出两个键错误,这 他们是

"index-of\0-an-array" != "index-of-an-array"

问题是错误消息打印出所有内容,直到空字符。如果是这样,那么它可能会被某些人认为是一个错误。

第二个例子是开始深入 PHP :)

我已经添加了一些代码,这样我们就可以看到发生了什么

<?php
class Foo {
  public    $index_public;
  protected $index_prot;
  private   $index_priv;
  function __construct() {
    $this->index_public = 0;
    $this->index_prot   = 1;
    $this->index_priv   = 2;
  }   
}
$foo = new Foo();
$array = (array)$foo;
print_r($foo);
print_r($array);
//echo $array["\0Foo\0index_of_an_array2"];//This prints 2
//echo $foo->{"\0Foo\0index_of_an_array2"};//This fails
var_dump($array);
echo array_keys($array)[0]       . "\n";
echo $array["\0Foo\0index_priv"] . "\n";
echo $array["\0*\0index_prot"]   . "\n";

以上代码输出为

Foo Object
(
    [index_public] => 0
    [index_prot:protected] => 1
    [index_priv:Foo:private] => 2
)
Array
(
    [index_public] => 0
    [*index_prot] => 1
    [Fooindex_priv] => 2
)
array(3) {
  'index_public' =>
  int(0)
  '\0*\0index_prot' =>
  int(1)
  '\0Foo\0index_priv' =>
  int(2)
}
index_public
2
1

PHP 开发人员选择使用\0 字符作为拆分成员变量类型的一种方式。注意,受保护的字段使用* 表示成员变量实际上可能属于许多类。它还用于保护私人访问,即此代码不起作用。

echo $foo->{"\0Foo\0index_priv"}; //This fails

但是一旦你把它转换成一个数组,那么就没有这样的保护了,也就是说,这是可行的

echo $array["\0Foo\0index_priv"]; //This prints 2

这种行为是否有任何原因/实施

是的。在您需要与之交互的任何系统上,您都需要制作系统 通话,如果您想要当前时间或转换日期等,您需要交谈 到操作系统,这意味着调用操作系统 API,在 Linux 的情况下 这个 API 在C

PHP 最初是作为 C 很多语言的精简包装器开发的 以这种方式开始并发展,PHP 也不例外。

这种行为/实现有什么原因吗?

在没有任何向后兼容性问题的情况下,我会说一些选择不是最佳选择,但我怀疑向后兼容性是一个重要因素。

但我还是不明白为什么语言是这样设计的?

向后兼容性几乎总是人们不喜欢的功能保留在语言中的原因。随着时间的推移,语言会进化并删除一些东西,但它是增量的和优先的。如果您询问所有 PHP 开发人员,他们是否希望对某些函数或 JIT 编译器进行更好的二进制字符串处理,我认为 JIT 可能会胜出,就像它在 PHP 7 中所做的那样。注意,从事实际工作的人最终决定他们的工作内容和使用 JIT 编译器比修复以看似奇怪的方式做事的库更有趣。

我不知道有任何语言实现者不希望他们从一开始就做一些不同的事情。任何在 a 之前实现编译器的人 语言很流行 在获得适合的东西方面承受着很大的压力 他们,这意味着偷工减料,并非今天存在的所有语言都有 支持他们的大公司,通常是一个小的敬业团队,他们 犯了错误,有些人很幸运能得到报酬。称他们为懒惰 有点不公平。

所有语言都有暗角疣和疖子,以及你最终会讨厌的特征。比其他人多一些,PHP 的名声不好,因为它比大多数人拥有/拥有更多。请注意,PHP 5 是 PHP 4 的一个巨大飞跃。我想 PHP 7 会改进更多。

任何认为他们最喜欢的语言没有问题的人都是妄想症,几乎可以肯定他们没有深入了解他们使用的工具的深度。

【讨论】:

  • 抱歉,示例令人困惑。检查我的编辑以查看您可以面对\0 格式错误的字符串的真实情况。 the error message is incomplete 是什么意思?
  • 您的原始示例实际上突出了错误消息打印的一个特殊问题,即它在\0 字符上被截断。请注意,这可能不是fixed 的原因。阵列工作正常。
【解决方案2】:

“为什么”的简短回答就是历史

PHP 最初是作为一种编写 C 函数脚本的方式编写的,因此可以在生成 HTML 时轻松调用它们。因此 PHP 字符串只是 C 字符串,它是一组任意字节。所以用现代 PHP 术语来说,我们会说没有什么是二进制安全的,仅仅是因为 it wasn't planned to be anything else

早期的 PHP 并没有打算成为一种新的编程语言,而是有机地成长起来,Lerdorf 回想起来:“我不知道如何阻止它,从来没有任何意图编写编程语言 [...]完全不知道如何编写编程语言,我只是不断添加下一个逻辑步骤。”

随着时间的推移,该语言逐渐支持更精细的字符串处理函数,其中许多函数将字符串的特定字节考虑在内并变得“二进制安全”。根据最近写的formal PHP specification

关于字符串中的字节如何转换为字符,我们并没有说明。尽管字符串的用户可能会选择将特殊语义赋予具有值 \0 的字节,但从 PHP 的角度来看,这样的空字节没有特殊含义。 PHP 不假定字符串包含任何特定数据或为任何字节或序列分配特殊值。

作为一种有机发展的语言,并没有以不同于 C 的方式普遍处理字符串。因此,函数和库在逐个案例的基础上都是二进制安全的。

【讨论】:

    【解决方案3】:

    PHP 中使用 C 字符串进行内部操作的函数在 PHP 术语中是“非二进制安全的”。 C 字符串是以字节 0 结尾的字节数组。当 PHP 函数内部使用 C 字符串时,它会逐个读取字符,当遇到字节 0 时,它会将其视为字符串结尾。字节 0 告诉 C 字符串函数字符串的结尾在哪里,因为 C 字符串不包含有关字符串长度的任何信息。

    “非二进制安全”的意思是,如果以某种方式向使用 C 字符串操作的函数传递一个未以字节 0 终止的 C 字符串,则行为是不可预测的,因为函数将读取/写入超出字符串末尾的字节,从而将垃圾添加到字符串和/或可能导致 PHP 崩溃。

    例如,在 C++ 中,我们有字符串对象。此对象还包含一个字符数组,但它还有一个长度字段,它会在任何长度更改时更新。所以它不需要字节 0 来告诉它结束在哪里。这就是字符串对象可以包含任意数量的 0 字节的原因,尽管这通常是无效的,因为它应该只包含有效字符。

    为了纠正这个问题,整个 PHP 核心,包括任何使用 C 字符串操作的模块,都需要重写,以便将“非二进制安全”函数发送到历史记录。为此所需的工作量是巨大的,所有模块的创建者都需要为他们的模块生成新代码。这可能会给整个故事带来新的错误和不稳定性。

    字节 0 和“非二进制安全”函数的问题对于证明重写 PHP 和 PHP 模块代码的合理性并不那么重要。也许在一些新的 PHP 版本中,有些东西需要从头开始编码,纠正这个问题是有意义的。

    在此之前,您只需要知道使用二进制安全函数放入某个字符串的任意二进制数据都需要在末尾添加字节 0。通常,当字符串末尾出现意外垃圾或 PHP 崩溃时,您会注意到这一点。

    【讨论】:

      猜你喜欢
      • 2011-03-16
      • 2018-08-28
      • 2013-07-16
      • 2018-12-02
      • 1970-01-01
      • 1970-01-01
      • 2016-11-02
      • 1970-01-01
      • 2018-07-04
      相关资源
      最近更新 更多