文章目录
- 参考
- 环境
- MD5
- 韧性
- 脆弱性
- md5()
- 隐式类型转换
- 字符串连接
- 数学运算
- 布尔判断
- 相等运算符
- 科学计数法
- 科学计数法
- 前缀 0E 与 0e
- PHP8 与 PHP 其他版本下字符串转化为数值的具体规则
- PHP8
- 数值字符串
- 优化
- 其他版本
- 更为详细的讲解
- 字符串与字符串的弱比较
- 字符串与数值的弱比较
- 0e215962017
参考
项目 | 描述 |
---|---|
搜索引擎 | Bing、Google |
AI 大模型 | 文心一言、通义千问、讯飞星火认知大模型、ChatGPT |
PHP 手册 | PHP Manual |
wsfgrdgh | PHP8中字符串与数字的比较更智能 |
PHP RFC 文档 | Saner string to number comparisons |
环境
项目 | 描述 |
---|---|
PHP | 5.5.0 、5.6.8 、7.0.0 、7.2.5 、7.4.9 、8.0.0 、8.2.9 |
MD5
MD5(Message Digest Algorithm 5)
是一种常用的哈希函数算法,用于将 任意长度
的数据转换为 固定长度
的哈希值。MD5
是 MD4
算法的改进版本,由 Ronald Rivest
在 1992
年设计并广泛使用。
MD5 算法的 输出结果
是一个 128
位,即长度为 16
字节的哈希值,通常表示为一个 32 位
的 十六进制数
。
韧性
-
不可逆性
MD5 是一种单向哈希函数
,具有不可逆性的特性。这意味着无法
通过已知的 MD5 哈希值
来准确地确定原始输入数据
。 -
雪崩效应
MD5 的雪崩效应是指对输入数据微小改变
会导致输出哈希值发生巨大变化
的特性。具体来说,即使输入数据只有一个比特位的改变,其计算得到的 MD5 哈希值也会产生全局性的变化
,而不仅仅在修改的位置上体现出差异。 -
唯一性
理想情况下
,对于不同的输入数据,MD5 算法生成的哈希值应该是唯一的。也就是说
,不同的输入数据应该产生不同的哈希值。 -
快速性
MD5 是一种快速计算哈希值的算法,适用于对大量数据进行哈希计算
的场景。
脆弱性
MD5 算法的 哈希输出空间
相对较小,仅有 128
位(仅能容纳 128 位二进制数据),而 输入空间是无限的
,这种 输入与输出的不匹配性
导致了哈希碰撞的 可能性
。攻击者可以使用 巧妙构造的输入数据
,通过精心选择的 碰撞攻击算法
,找到 具有相同哈希值的不同输入
。
-
碰撞攻击
由于 MD5 的设计特点
和算法结构
,使得攻击者能够使用巧妙构造的输入数据来找到碰撞。碰撞攻击的发现使得 MD5 不再适用于对数据完整性和身份验证进行可靠保护。 -
预计算攻击
由于 MD5 的计算速度较快
,攻击者可以预先计算常见输入数据的 MD5 哈希值并将其存储在哈希表中
。这样,在实际攻击过程中,攻击者可以通过将需要进行破解的哈希值与预先计算的哈希值进行比对
来实现对哈希值的快速破解。
md5()
在 PHP 中,md5()
函数用于计算给定字符串的 MD5 哈希值
,该函数采用一个字符串作为输入并返回其 对应的哈希值
。
md5(string $string, bool $binary = false): string
其中:
项目 | 描述 |
---|---|
$string | 要计算 MD5 哈希值的字符串。 |
$binary | 参数值为一个 布尔类型 的数据,用于指定返回的哈希值是 二进制格式 还是 十六进制格式 。默认为 false ,表示返回 十六进制格式 的哈希值,如果设置为 true ,则返回 二进制格式 的哈希值。 |
举个栗子
<?php
// 尝试将字符串 Hello World 转换为 MD5 哈希值
var_dump(md5('Hello World'));
// 尝试将字符串 12 转换为 MD5 哈希值
var_dump(md5('12'));
// 当函数 md5() 的输入值为数值时将自动将非数值数据转换为数值数据
var_dump(md5(12));
执行效果
string(32) "b10a8db164e0754105b7a99be72e3fe5"
string(32) "c20ad4d76fe97759aa27a0c99bff6710"
string(32) "c20ad4d76fe97759aa27a0c99bff6710"
注:
md5
函数的 $binary
参数的值设置为 true
,此时若将转换结果输出至终端中将出现 乱码
的现象。这是因为 PHP 会 自动尝试
将 二进制数据
转换为 可以显示的文本信息
。PHP 会将二进制数据中的 每一个字节
转化为对应的 ASCII 字符
,而转化结果中包含了 部分不可打印字符(回车符、空字符等)
,这些不可见字符将以 乱码
的形式展现。其中,回车符
虽然为 不可见字符
,但在文本中起着将文本内容换行的作用。对此,请参考如下示例:
<?php
// 尝试将哈希值以二进制方式输出至终端中
var_dump(md5('Hello World', true));
// 尝试将哈希值的二进制表达转换为十六进制后输出至终端中
var_dump(bin2hex(md5('Hello World', true)));
执行效果
由于二进制数据转化为文本的结果中包含不可见字符 换行符
,所以 var_dump(md5('Hello World', true));
的输出结果中以 两行
的形式呈现。
string(16) "�
��d�uA����.?�"
string(32) "b10a8db164e0754105b7a99be72e3fe5"
隐式类型转换
在 PHP 中,隐式类型转换(Implicit Type Conversion)
是指在某些操作中,PHP 会 自动
将数据 由一种数据类型转换为另一个数据类型
,而 无需显式
地编写 类型转换
代码。
PHP 的隐式类型转换会按照一定规则(具体情况具体分析)对操作数进行转换,以使得相关操作 能够正常进行
下去。
字符串连接
在通过使用句点运算符 .
进行字符串连接操作时,PHP 将会尝试将其他数据类型 转换为字符串数据类型
。对此,请参考如下示例:
<?php
// 尝试将两个字符串进行拼接
var_dump('Hello ' . 'World');
// 尝试将数值与字符串进行拼接
var_dump('1 + 1 = ' . 2);
// 尝试将两个数值进行拼接
var_dump(1 . 1);
执行效果
string(11) "Hello World"
string(9) "1 + 1 = 2"
string(2) "11"
数学运算
在通过 数学运算符
进行数学运算时,PHP 将会尝试将其他数据类型的数据 转换为数值类型
。对此,请参考如下示例:
<?php
// 尝试对布尔值 true 与数值 1 进行减法运算
var_dump(true - 1);
// 尝试对布尔值 true 与 false 进行加法运算
var_dump(true + false);
// 尝试进行字符串之间的乘法运算
var_dump('2' * '150');
// 字符串 100djdj 将被转换为 100
var_dump('100djdj' / 10);
// 字符串 djdj100 将被转换为零
var_dump('djdj100' / 10);
执行效果
int(0)
int(1)
int(300)
int(10)
int(0)
布尔判断
在需要使用布尔值的位置,PHP 将尝试将非布尔值的数据 转换为布尔类型的数据
。对此,请参考如下示例:
<?php
// 尝试将空字符串转换为布尔值
if(''){
print('Hello World' . "\n");
}
// 尝试将字符串 Hello World 转换为布尔值
if('Hello World'){
print('Hello China' . "\n");
}
// 尝试将数值 999 转换为布尔值
if(999){
print('久久久' . "\n");
}
执行效果
Hello China
久久久
相等运算符
在 PHP 中存在两种相等运算符,即弱类型相等运算符 ==
和强类型相等运算符 ===
,两者都可以用于判断两个操作数是否相等,但存在一些区别。
两者的 区别
在于,弱类型相等运算符
在对操作数进行比较之前,将 自动
进行类型转换以 使两者所属的数据类型相同
。而 强类型相等运算符
在进行比较时,要求两个值的 类型
和 值
都必须 完全相同
,不进行类型转换
。对此,请参考如下示例:
<?php
// 在通过弱类型比较运算符对数值与字符串进
// 行比较时,PHP 优先将字符串转换为数值。
// 由于两者转换为同一类型后,值相同,
// 故将返回 true。
var_dump('123' == 123);
// 由于两者的数据类型及值均不相同,故
// 将返沪 false。
var_dump('123' === 123);
执行效果
bool(true)
bool(false)
科学计数法
科学计数法
在 PHP 中,e
与 E
均表示 科学计数法(Scientific Notation)
。科学计数法由 基数
和 指数
两部分组成,常用于
表示非常大或非常小的数值。
在科学计数法中,基数 通常
是一个浮点数,介于 1
到 10
之间,而指数是一个整数,表示要将基数乘以 10
的多少次方。基数与指数之间以字符 e
或 E
进行分隔。
举个栗子
<?php
// 3.78 * 10 ^ 3
var_dump(3.78e3);
// 3 * 10 ^ -1
var_dump(3E-1);
执行效果
float(3780)
float(0.3)
前缀 0E 与 0e
零的任何指数次幂都为零
,因此以 0E
或 0e
为前缀的科学计数法表示的数值的结果都将为数值零。对此,请参考如下示例:
<?php
var_dump(0e3280);
var_dump((float)'0e30284083');
var_dump((float)'0esjlfjsld');
执行效果
float(0)
float(0)
float(0)
PHP8 与 PHP 其他版本下字符串转化为数值的具体规则
PHP8
数值字符串
数值字符串
是指一个包含数字字符的字符串,数值字符串可以用于直接表示一个数值。
举个栗子
"123"
"-42"
"+384"
"3.14"
"-0.5"
"0.0000"
"00000000.0000"
"2.5e3"
"1.2e-2"
"+42.0E0"
"0004746"
"0305940"
" 484748 "
" 4847 "
" 3847"
注:
- 包含
代表其他进制(非十进制)数值的符号
的字符串不能被称为数值字符串(数学中通常不使用这些符号来标识其他进制的数值
),如0x1F
、0b10101
等字符串。在 PHP 中,八进制数值
通过前导零
来表示,但在数值字符串中
,前导零将被视为普通数字,不具备标识八进制数值的功能
。 - 如果存在将其他进制(非十进制)的字符串转化为数值的需求,可以考虑使用
intval()
等函数进行显式类型转化。 包含空格等空白字符的字符串
为什么也可以是数值字符串?你可以理解为一眼望去是数值的就是数值字符串
😔。
优化
PHP8 仍旧保留了隐式类型转换
这一特性,但在字符串与数值的弱比较方面做出了优化。在 字符串与数值的弱比较
过程中,PHP 将 依据字符串的不同
选择 将字符串转换为数值
或 将数值转换为字符串
后再进行比较。具体规则如下:
- 若字符串
符合数值字符串的定义
,则 PHP 尝试将字符串转化为数值
后再进行比较。 - 若字符串
不符合数值字符串的定义
,则 PHP 尝试将数值转化为字符串
后再进行比较。
其他版本
在 PHP 的 隐式类型转换过程
中,字符串转化为数值的具体规则如下:
- 若字符串的
首个字符不为数字且不为空格等空白字符
,则将该字符串转化为零。 - 若字符串的
首个字符不为数字但为空格等空白字符
,则尝试读取其余字符,将遇到数字前的所有空白字符均转化为零,将遇到数字后的所有空白字符视为非数字字符;在遇到非数字字符时停止对字符串的读取并将已读取字符转化为数值
。 - 若字符串的
首个字符为数字
,则尝试读取其余字符,在遇到非数字字符(除符合科学计数法格式的字符 e 或 E外)时停止对字符串的读取并将已读取字符转化为数值
。
举个栗子
目标字符串 | 转化结果 |
---|---|
Hello123 | 0 |
1Hell2o3 | 1 |
0x8aHello123 | 0 |
9.384Hello | 9.384 |
0008743738Hello948 | 8743738 |
1.223e100 | 122.3 |
注:
PHP 在执行字符串到数值的隐式类型转换过程,均 以十进制表示法
为依据。同上述例子一般,0x8aHello123
中的 0x8a
并不会被识别为十六进制数,由于十进制中不存在 x
,故 PHP 在识别到字符 x
时就将立即停止读取并将读取到的字符串 0
转化为数值,故最终的转化结果为零。
更为详细的讲解
如果对 PHP8 与 PHP 其他版本下字符串转化为数值的规则希望能有更为详细的讲解,可以参考我的另一篇博客 PHP 变动:PHP 8 版本下字符串与数值的弱比较。
字符串与字符串的弱比较
在 PHP 中,若弱比较运算符的两个操作数 均为字符串
。PHP 将 依据不同 PHP 版本下字符串转化为数值的规则
将两个操作数转化为数值。
在 MD5 的绕过过程中,常常使用 MD5 加密结果符合如下格式
的字符串:
前缀为 `0e` 或 `0E` (后续格式说明仅针对 PHP 8 及以上版本)且后续字符均为数字的字符串。
而 byGcY
和 0e215962017
就是符合这一规则的字符串。
攻击目标中若存在 包含 MD5 解密的弱比较环节
,我们便可以使用这么一些字符串尝试 使得判断结果朝符合我们的预期
。
举个栗子
<?php
# 变量 $user_input 中存放着用户的输入
$user_input = '0e215962017';
# != 为弱类型不等运算符,强类型不等运算符为 !==
if (md5('byGcY') != md5($user_input)) {
print("Come to my city" . "\n");
} elseif ("Hack Me" == md5($user_input)) {
print("Be my king" . "\n");
} else {
print("Look at my invincible defense" . "\n");
}
# byGcY 与 0e215962017 的 MD5
# 加密结果均以 0e 为前缀,且 md5() 函数的返回
# 结果的数据类型为字符串,故将发生字符串到数值的
# 隐式类型转化。
var_dump(md5('byGcY'));
var_dump(md5($user_input));
执行效果
Look at my invincible defense
string(32) "0e591948146966052067035298880982"
string(32) "0e291242476940776845150308577824"
注:
在 PHP 中,若弱类型比较运算符的操作数 均为字符串
,当且仅当字符串操作数在当前 PHP 版本中均能够转化为数值时
,PHP 才会将其转化为数值进行比较。否则,按照字符串的比较规则进行比较。
字符串与数值的弱比较
在 PHP 中,若弱比较运算符的两个操作数分别为 数值
与 字符串
。PHP 将 依据不同 PHP 版本的转化规则
将两个操作数中的一个转化为另一种数据类型。
同 字符串与字符串的弱比较一般
,当攻击目标中存在 包含 MD5 解密的弱比较环节
,可以通过构造字符串利用弱比较规则 使得判断结果朝符合我们的预期
。
举个栗子
<?php
$user_input = '0e215962017';
$hello = 'Hello WOrld';
if (0.000 != md5($user_input)) {
print("Come to my city" . "\n");
} elseif (68 == md5($hello)) {
print("Be my king" . "\n");
} else {
print("Look at my invincible defense" . "\n");
}
# 由于 $hello 中的 MD5 加密结果以数字 68 开头,在
# PHP8 以下版本中,该字符串将被转化为数值 68。
var_dump(md5($hello));
执行效果
上述示例在 PHP8 以下版本
中的运行结果为:
Be my king
string(32) "68c131c6982a0bbbbae667624d8eca7d"
上述示例在 PHP8 版本
中的运行结果为:
Look at my invincible defense
string(32) "68c131c6982a0bbbbae667624d8eca7d"
0e215962017
0e215962017
这个字符串比较特殊,因为 该字符串及该字符串的 MD5 加密结果均以 0e
为前缀。在参加有关 MD5 绕过的安全竞赛时,你可能将遇到与如下类似的题目:
<?php
$user_input = '0e215962017';
if ($user_input == md5($user_input)) {
print("Come to my city,Be my king" . "\n");
} else {
print("Look at my invincible defense" . "\n");
}
var_dump(md5($user_input));
执行结果
Come to my city,Be my king
string(32) "0e291242476940776845150308577824"