探究:宽字节注入中gbk编码到底是指谁的?

本文最后更新于 2024年10月23日 晚上

前言

想必师傅们都知道宽字节注入的前提必须要目标使用了GBK编码,才能够加上一些字符结合转义字符构造成汉字,吞掉了转义字符,使得引号逃逸出来,导致注入。但是这里的”使用了GBK编码“具体指的是谁的编码呢?数据库的编码(数据库服务器的默认字符集、数据库的字符集、数据表的字符集、列的字符集)吗,网站的编码吗,还是什么编码?还是这两个都是?或者是其他的什么编码呢?

既然不知道,那么我先查查网上其他师傅怎么说吧:

资料查询

查文章

先是查了大量的有关宽字节注入的文章,发现都没有得到想要的答案:

image-20241020165148510

image-20241020164709440

image-20241020165109918

image-20241020165214528

image-20241020165309713

发现绝大多数文章,要么没有提到宽字节的利用前提,文章重心一直放在怎么利用上,要么文章的相关措辞都是”mysql使用了GBK编码”,也就是说这个编码前提是指的数据库的gbk编码,但是都没有指明该gbk编码具体到底在哪里,因为mysql配置编码的地方很多

拷打ChatGPT

image-20241020170539840

ok,首先GPT排除了网站编码的可能性,它也认为是数据库的编码影响的,继续拷打

image-20241020170705776

image-20241020170725536

啊嘞?GPT给我说这四个配置项共同决定了数据库在处理字符时的编码行为,咋越来越离谱了呢,有点懵圈……

没事我直接写个宽字节注入的Demo自己做实验,看看到底是哪个编码影响的,不就行了吗?

动手实验

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "security";
$conn = new mysqli($servername, $username, $password, $dbname);
$conn->set_charset("gbk");
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$db_encoding = $conn->character_set_name();
$website_encoding = ini_get("default_charset");
echo "<p>Debug: Database Encoding - <strong>" . htmlspecialchars($db_encoding) . "</strong></p>";
$id = addslashes($_GET['id']);
$sql = "SELECT * FROM users WHERE id = '$id'";
echo "<p>Debug: Executing SQL Query - <strong>" . htmlspecialchars($sql) . "</strong></p>";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo "ID: " . $row["id"] . " - Username: " . $row["username"] . " - Password: " . $row["password"] . "<br>";
}
} else {
echo "0 results";
}
$conn->close();
?>

这段demo实现了宽字节注入的场景,并且添加了一些调试输出,打印了执行的sql语句,打印了数据库连接时采用的字符集编码

image-20241021085941477

实验变量-各种gbk编码

首先看看有哪些地方能配置gbk编码,也就是首先找出所有的影响实验结论的变量,再逐一控制变量实验,就能找到到底哪一个才是宽字节注入的真正前提

1. 网站编码

image-20241021090120356

网站编码(如 <meta charset="UTF-8">)主要用于控制前端显示,这个编码定义了网页上显示内容的字符编码方式,比如是 UTF-8、GBK 等。但它还会影响浏览器向服务器发送数据时的编码

比如,当用户在网页上填写表单并提交时,浏览器会根据网页的编码(如 GBK)对数据进行编码后,再发送到服务器。这意味着网页编码决定了浏览器如何对用户输入进行编码。

服务器收到数据后,会根据约定的编码方式对数据进行解码。如果服务器预期收到的数据是 UTF-8 编码,而前端传过来的实际是 GBK 编码的数据,那么解码可能会失败。

关键点在于:无论前端数据传输使用的是什么编码,服务器端应用在接收到数据后可以根据需要进行重新编码和转换。所以,前端编码的影响主要是在数据进入服务器的那一刻,之后数据的编码转换完全由服务器端控制。

image-20241021094429428

那么,从这里就已经可以看出,网站编码影响的是网站前端到服务器端之间传输的数据编码,并没有真正影响到数据库,因为和数据库直接交互的是服务器端,那么由此推测:网站编码不是宽字节注入的真正前提

我使用这个谷歌插件修改网站编码

2. 数据库字符集

image-20241021090526505

这个gbk编码又是干什么的呢?这是数据库整理字符集编码:

数据存储设置数据库(整理)字符集编码会影响存储的数据的实际编码方式。例如,如果表的字符集是 utf8mb4,插入数据时会按照 utf8mb4 编码存储,读取时也会按 utf8mb4 解码。

查询和排序整理规则影响查询时的比较和排序方式。例如,如果表的整理规则是 utf8_general_ci,那么查询时会忽略大小写进行比较。

image-20241021094846097

也就是这个数据库编码是纯只在数据库中的,只影响数据库中的数据存储、排序、整理的,也和网站编码一样,没有与服务器直接交互,仅仅影响数据库本身,那么推测:数据库编码也不是宽字节注入的真正前提

3. 表字符集

如果上述推测正确,那么同理可得,表的字符集仅仅影响表中数据以什么编码进行存储和读取,并不会直接影响与服务器的交互或者导致宽字节注入

image-20241021095615598

4. 列字符集

如果推测正确,同上

image-20241021095729850

那么到此为止,根据我们的推测,其实大概能知道了,真正的宽字节注入的前提,也就是那个gbk编码不能是数据存储编码,因为这个数据存储编码只是影响数据在数据库中怎么存储、排序、整理等等;那么我们继续大胆推测:真正影响宽字节注入的是数据传输编码,也就是服务器与数据库进行数据传输时的那个编码,只有这个编码才是既经过了服务器又经过了数据库的,并且不是数据库存储编码!

5. character set配置

image-20241021103927801

  1. character set client
  • 作用:指定客户端(如应用程序)发送到服务器的数据的字符集。客户端会按照这个字符集对数据进行编码。
  • 影响宽字节注入:如果客户端使用 GBK 编码发送数据,而服务器连接的编码是 GBK,可能导致宽字节注入的风险。
  1. character set connection
  • 作用:指定服务器如何解码从客户端接收的数据。它决定了数据在进入数据库之前的解码方式。
  • 影响宽字节注入:这非常关键。如果连接编码是 GBK,那么无论客户端发送的数据是什么编码,服务器都会按照 GBK 解码,这会影响到宽字节注入的可能性。
  1. character set database
  • 作用:指定数据库内部使用的字符集,决定数据如何存储在数据库中。
  • 影响宽字节注入:这不会直接影响宽字节注入,因为它只影响数据存储方式,与数据的传输过程无关。
  1. character set filesystem
  • 作用:指定文件系统字符集,通常用于处理文件名。
  • 影响宽字节注入:一般不会影响数据库操作或宽字节注入。
  1. character set results
  • 作用:指定服务器返回给客户端的数据的字符集。
  • 影响宽字节注入:这主要影响客户端接收到数据的编码方式,不会影响宽字节注入。
  1. character set server
  • 作用:指定数据库服务器内部使用的字符集,影响数据库内部的操作和存储。
  • 影响宽字节注入:同样,影响数据的存储和处理,但与数据传输无关。
  1. character set system
  • 作用:表示系统默认的字符集,通常用于内部系统操作。
  • 影响宽字节注入:不会直接影响数据库操作或宽字节注入。

由此可见,这些 character set 变量中实际影响宽字节注入的是character set connection和character set client编码,而其他的编码都是数据存储类的编码,只会影响数据库本身

6. 数据库传输编码配置

demo

image-20241021133642813

sqli-labs-less-32-源码

image-20241021133739650

可以看到这两个宽字节的源码,都有这一个配置

1
2
mysql_query("SET NAMES gbk");
$conn->set_charset("gbk");

mysql_query("SET NAMES gbk");$conn->set_charset("gbk"); 都是用于设置 MySQL 数据库连接的字符集,但它们在使用的上下文和方法上有所不同。

  1. mysql_query("SET NAMES gbk");
  • 作用:这是一个旧的 MySQL 函数,用于在建立连接后设置字符集,告诉 MySQL 使用 gbk 编码来处理接下来的有查询。
  • 上下文:它通常在使用 mysql_* 函数(已废弃)时使用。这个设置会影响当前会话的字符集。
  1. $conn->set_charset("gbk");
  • 作用:这是一个面向对象的方法,属于 MySQLi(MySQL Improved)扩展,用于设置连接的字符集。
  • 上下文:用于使用 MySQLi 连接数据库时,推荐使用这个方法,因为它是现代 PHP 版本中使用的更安全、更高效的接口。

关键关系

  • 两者的影响:无论是使用 SET NAMES gbk 还是 $conn->set_charset("gbk"),它们都将连接的字符集设置为 gbk,这将影响到从客户端发送到服务器的所有数据的编码和解码。
  • 宽字节注入的影响:无论使用哪种方法,只要连接的字符集被设置为 gbk,数据在传输时将会按 gbk 编码解析,可能导致宽字节注入问题。

也就是这两个配置都是设置连接数据库时的编码的,只是版本不同写法不同,也就是我们前面说的数据传输编码,而这才是影响宽字节注入的真正前提。

与character set配置的关系

这个配置其实就是设置了 MySQL 数据库连接的字符集,也就是 character set connection这会直接覆盖掉默认的连接字符集设置,并且直接影响数据在传输和解析时的编码方式。

关系说明

  1. 覆盖默认设置:
    • 当使用 $conn->set_charset("gbk"); 时,实际上是动态修改了当前连接的 character set clientcharacter set connection。这意味着无论服务器或数据库的默认字符集配置是什么,这个设置会优先应用于当前连接。
    • 这样做的效果是,所有从客户端(如 PHP 应用程序)发送的数据都会按照 GBK 编码进行解析,这可能会导致宽字节注入风险。
  2. 对其他配置的影响:
    • character set clientcharacter set connection 被设置为 GBK 后,意味着从 PHP 发送的数据会以 GBK 编码发送,并且在服务器接收时,也会按照 GBK 编码进行解码。
    • character set results 会自动跟随 GBK 设置,这意味着服务器返回的结果也会以 GBK 编码传输。
    • 其他配置如 character set databasecharacter set server 不会直接受此影响,因为这些配置影响的是数据库内部的数据存储,而非连接传输。

总结

使用 $conn->set_charset("gbk"); 会改变当前连接的字符集,使其按 GBK 编码进行传输和解析数据。这种配置直接影响到宽字节注入的可行性,因为宽字节注入的关键在于数据库连接时编码的特性(如 GBK 允许双字节字符的解析)。其他配置如数据库字符集则不会直接影响到这一点。

实验

以上内容都是基于理论推测得出的结论,还不能就此确定宽字节注入的关键是否真的是数据传输(连接)编码,于是下面做实验验证:

1. 把所有的上述变量都设置为非gbk编码,比如utf-8编码

  • 网站编码-utf-8

image-20241021132824882

  • 数据库编码-utf-8

image-20241021133047363

  • 表编码-utf-8

image-20241021133239317

  • 列编码–utf-8

image-20241021133346689

  • character set配置–utf-8

image-20241021133502852

  • demo中的数据库传输(连接)编码-utf-8

image-20241021133642813

2. 用demo尝试宽字节注入,做空白对照

image-20241021134047974

毫无疑问,这是不可能成功的,因为现在我们把所有能配置的编码都设置为了非gbk编码:utf-8编码

3. 修改网站编码为gbk,排除该变量

image-20241021134338308

image-20241021134413120

ok,排除成功,成功验证了”网站编码是gbk”并不是宽字节注入的关键

4. 还原网站编码,修改数据库编码为gbk,排除该变量

还原网站编码为utf-8,修改数据库编码为gbk

image-20241021134943450

image-20241021135435176

ok,排除成功,成功验证了”数据库编码是gbk”并不是宽字节注入的关键

5. 还原数据库编码,修改表编码为gbk,排除该变量

1
ALTER TABLE users CONVERT TO CHARACTER SET gbk COLLATE gbk_chinese_ci;

image-20241021135937223

image-20241021135435176

ok,排除成功,成功验证了”表编码是gbk”并不是宽字节注入的关键

6. 还原表编码,修改列编码为gbk,排除该变量

1
2
ALTER TABLE users 
CONVERT TO CHARACTER SET gbk COLLATE gbk_chinese_ci;

image-20241021140427954

image-20241021135435176

ok,排除成功,成功验证了”列编码是gbk”并不是宽字节注入的关键

7. 还原列编码,修改character set配置为gbk

1
2
3
4
5
6
SET NAMES 'gbk';
SET character_set_client = 'gbk';
SET character_set_connection = 'gbk';
SET character_set_results = 'gbk';
SET character_set_database = 'gbk';
SET character_set_server = 'gbk';

image-20241021141633267

image-20241021141811287

???为什么呢,按照前文的推论,这里的character_set_client、gbk character_set_connection 就是数据库连接时的编码啊,也就是数据库传输的编码,为什么都设置为了gbk编码还是不能宽字节注入呢??

请看VCR:

来到phpmyadmin,虽然编码都设置为了gbk,但是会话值仍然是utf-8!那么估计就是这个原因,后来我尝试了把所有能修改的地方全部改为了gbk编码,但是仍然还是不能将这个会话值修改为我们想要的gbk编码(会话值到底是个啥呀……)

  • 会话值

在 MySQL 中,会话值是与当前数据库连接(会话)相关的配置参数。这些参数只在该会话期间有效,不会影响其他会话或全局设置。当你连接到 MySQL 服务器时,会启动一个独立的会话,此时的会话值决定了这个连接中的具体行为,比如字符集、排序规则等。

image-20241021142225884

这是为什么呢?

原来,在MySQL 中,字符集的设置分为会话级别和全局级别。会话级别的字符集会覆盖全局设置,如果会话值仍然是 utf8,可能是因为如下原因:

  1. 在客户端连接时指定了字符集:如果客户端(如 PHP、命令行等)在连接 MySQL 时明确指定了 utf8,那么会话级别的设置就会覆盖全局设置。
  2. MySQL 配置文件没有完全配置:即使修改了 my.cnfmy.ini 中的配置项,也需要确保所有相关的字符集设置都配置正确。

也就是这个会话值的优先级高于全局配置,如果在客户端连接时指定了字符集,那么会话值就是指定的字符集,且会直接覆盖全局配置

至于这里为什么会话值仍然是utf-8,我推测会话值仍可能压根就直接默认使用 utf8,尤其是当客户端(如 PHP)连接时没有指定字符集的情况下,也就是只有我在php源码中手动指定为其他编码(gbk)时才会短暂改变!

问下GPT看看对不对?

image-20241023160123715

破案了,推测正确!所以,像这样在数据库全局配置中修改character set配置为gbk,其实不起实际作用,因为会话值会配盖全局配置,且会话值不能通过手动在数据库层面修改配置来改变,因为会话值却决于当前具体会话,却决于当先客户端实际连接指定的编码,而不是静态的全局配置,于是第七个变量其实就不起实验参考作用了,真正起决定性作用的是第八个实验变量–数据库传输(连接)编码为gbk

8. 还原character set配置,修改数据库传输(连接)编码为gbk

image-20241021143024890

单引号成功逃逸出来,宽字节注入成功!

image-20241021143057096

得出结论

所以,真正决定宽字节注入的成功与否的gbk编码是数据库连接时的那个gbk编码,也可以说是数据传输编码(可以在服务端中指定配置)

1
2
3
//php中可以这样配置,区别是版本不同而已
mysql_query("SET NAMES gbk");
$conn->set_charset("gbk");

探讨原理

前面先是进行了有理推测,后进行了控制变量的实验,得出了结论,但是为什么呢。为什么是数据库传输(连接)编码而不是其他的编码呢?其中的原理是什么?

为什么是数据库传输编码影响宽字节注入?

  1. 数据解析与解释的关键点
    • 当客户端发送数据给数据库时,传输编码(即连接字符集)决定了数据库如何解析接收到的数据。如果传输编码设置为 GBK,数据库会把所有从客户端接收到的数据当作 GBK 编码的数据来处理。
    • 这种解析过程直接影响数据库如何理解和解析用户输入中的字符。例如,如果输入中有 \x81\x27(其中 \x27 是单引号 ' 的 ASCII 编码),GBK 编码下,可能被解析为一个合法的多字节字符,而不是一个单独的单引号字符。
  2. 宽字节注入的原理
    • 在宽字节编码(如 GBKShift-JIS 等)中,有些字符是由两个字节组成的。如果用户输入了某个特定的字节序列,数据库传输编码将会决定这个字节序列如何被解析。
    • 如果传输编码是 GBK,特定的两个字节可能会被解释为一个合法的多字节字符,即使其中的一个字节是 ASCII 字符 \x27(单引号 '),而这就可能让恶意输入避开转义和检测,形成 SQL 注入。
  3. 传输编码 vs. 其他编码
    • 字符集 client:这是客户端如何编码发送到数据库的查询文本的方式。传输编码决定了数据库如何解释这些字符。如果编码和数据库解析不匹配,字符就可能会被错误地解释。
    • 字符集 connection(传输编码):在数据库会话中起到了最重要的作用,因为它直接决定了数据库如何从客户端接收和解析输入的数据。
    • 字符集 server/database这些字符集主要影响数据在数据库中如何存储和处理,但与实时数据解析无关。因此,即使数据库存储使用了 latin1 或其他字符集,传输编码仍决定了如何解析传输的数据。
  4. 实际效果
    • 如果数据库连接时使用 GBK,并且客户端发送的字符数据按 GBK 解析,攻击者就可以构造利用宽字节编码特性的攻击字符串。
    • 如果连接时使用 UTF-8,因为 UTF-8 的编码规则不同,就不会有类似的问题(即单字节不会被解析为多字节字符),这类攻击就难以成功。

综上

数据库传输编码决定了数据库如何解析客户端发送的数据。而宽字节注入攻击的本质是利用特定字符在多字节编码下的解析规则。因此,只有在传输编码设置为 GBK 或类似的多字节编码时,攻击者才能构造出可以绕过常规转义和检查的输入,从而实现注入攻击。这也是为什么在防御宽字节注入时,需要特别关注和控制数据库连接的传输编码。