编码的设计哲学:从传输到安全

编码的设计哲学:从传输到安全

2026年04月30日·9 分钟阅读·1 次阅读·0 点赞·0 条评论

本文旨在厘清 URL 编码、HTML 实体、Base64、Unicode、十六进制的本质与边界,并从安全角度切入,理解各种编码的区别以及编码的防御原则

051131df-b3d4-4b06-9781-2d28a80312d9.png

一、编码存在的根本原因

所有编码的设计出发点只有一个核心矛盾:

在特定场景下,某些字符拥有"特殊含义"。当你想把这些字符当作普通数据传递时,就需要编码来进行转义,让数据和控制符号区分开来。

这些编码并非由同一个团队在同一时间设计,而是不同时代、不同团队面对不同问题时各自发明的解法,因此才形成了今天这套看似繁杂的体系。

二、五种编码详解

1. URL 编码(百分号编码)

设计场景: HTTP 传输层

URL 本身有保留字符用于划分结构,? & = # / 各有语义。当数据本身包含这些字符时,就需要编码:

正常 URL:
?q=hello&page=1

如果搜索词含有 &,不编码:
?q=hello&world     ← 服务端误解为两个参数

编码后:
?q=hello%26world   ← %26 是纯数据,不再有分隔含义

空格有两种编码方式,适用场景不同:

编码 含义 适用场景
+ 空格 查询字符串(表单编码规范)
%20 空格 URL 路径(RFC 3986 规范)
%2B 加号本身 区别于空格的+

设计目的: 让数据安全通过 URL 结构,不干扰 URL 语法。

2. HTML 实体编码

设计场景: 浏览器渲染层

HTML 解析时,< > & " ' 是结构符号。如果内容本身含有这些字符,需要转义:

<!-- 原始内容:3 < 5 是正确的 -->

<!-- 不转义 → 浏览器解析出错 -->
<p>3 < 5 是正确的</p>

<!-- 转义后 → 正常渲染 -->
<p>3 < 5 是正确的</p>

常用 HTML 实体对照:

原字符 实体编码
< &lt;
> &gt;
& &amp;
" &quot;
' &#39;

设计目的: 让数据安全嵌入 HTML,不被解析为标签结构。这也是防御 XSS 的核心机制。

3. Base64 编码

设计场景: 二进制数据通过纯文本信道传输

早期 Email 协议只支持 ASCII 纯文本,无法传输图片、附件等二进制数据。Base64 把任意二进制映射到 64 个可打印字符(A-Z、a-z、0-9、+、/):

原始:   你好
UTF-8:  E4 BD A0 E5 A5 BD
Base64: 5L2g5aW9   ← 纯 ASCII,可安全传输

设计目的: 让二进制数据在只支持文本的信道中安全传输。

4. Unicode / UTF-8

设计场景: 全球多语言字符统一表示

ASCII 只有 128 个字符,各国各自开发编码标准(GBK、Shift-JIS 等),相互不兼容。Unicode 为世界上所有字符分配唯一码点,UTF-8 是其中一种变长存储方式:

'A'  → U+0041  → UTF-8: 1字节  (0x41)
'你' → U+4F60  → UTF-8: 3字节  (0xE4 0xBD 0xA0)
'😀' → U+1F600 → UTF-8: 4字节  (0xF0 0x9F 0x98 0x80)

设计目的: 统一全球字符集,解决多语言乱码问题,不是为了传输安全。

5. 十六进制(0x)

设计场景: 数据库、编程语言的底层数据表示

十六进制是二进制的紧凑表示形式:1 字节 = 8 位二进制 = 2 位十六进制。在 MySQL 中可直接表示字节序列:

0x61646d696e = 'admin'
-- 直接表示字节序列,不需要引号

设计目的: 底层数据的直接表示,是编程和数据库的基础语法。

三、各层编码对照总览

层级          编码类型                    解码方式
─────────────────────────────────────────────────────
浏览器渲染层   HTML 实体 < &        浏览器渲染时自动处理
HTTP 传输层    URL 编码 %27 %23,+         Web服务器 / PHP 自动解码
应用层         Base64                      需显式调用 base64_decode()
数据库层       十六进制 0x,char()          MySQL 内部识别
操作系统层     UTF-8 / Unicode             透明处理,开发者无感知

核心规律:每一层只负责解码自己的编码,跨层的编码不会被自动处理。

四、从安全角度看编码

编码本身是中性的技术机制,但在攻防场景中,它成为了绕过过滤和检测的重要手段。

编码不等于安全

一个常见误区是认为 URL 编码能防止 SQL 注入。实际上:

攻击者输入:  admin' or 1=1 #
浏览器编码:  admin%27+or+1%3D1+%23
PHP 解码:    admin' or 1=1 #     ← 还原成原始恶意字符串
SQL 拼接:    WHERE username = 'admin' or 1=1 #'

URL 编码是传输格式,不是安全机制。 服务端自动解码后,攻击 payload 原样还原,对 SQL 注入毫无防御效果。

双重编码绕过过滤

正常编码:  ' → %27         服务端解码一次 → '  被过滤拦截
双重编码:  ' → %27 → %2527 服务端只解码一次 → %27(字符串,非引号)
                             过滤器看到 %27,认为安全,放行
                             但某些框架会二次解码,最终变成 '

数据库层十六进制绕过 WAF

-- WAF 过滤了单引号,但没过滤十六进制:
SELECT * FROM users WHERE username = 'admin'       -- 被拦截
SELECT * FROM users WHERE username = 0x61646d696e  -- 绕过

各编码的安全风险汇总

编码类型 安全风险 典型攻击场景
URL 编码 双重编码绕过 WAF %2527%27'
HTML 实体 未转义导致 XSS 输出<script> 未转义时执行
Base64 混淆 payload eval(atob("...")) 绕过关键字检测
十六进制 绕过引号过滤 0x61646d696e 替代 'admin'
Unicode 同形字混淆 Admin(全角 A)绕过用户名检测

防御的核心原则

攻击者利用的正是跨层传递时各层解码行为不一致——过滤在 A 层做了,但攻击 payload 在 B 层才展开。

错误的防御思路:
过滤输入中的 '  →  攻击者用 %27 或 0x27 绕过

正确的防御思路:
在数据被使用的那一层进行参数化处理

SQL 层    → Prepared Statement(预编译语句)
HTML 层   → htmlspecialchars() 转义输出
文件路径  → 白名单验证

总结

编码不是由某一个人统一设计的,而是各个时代的工程师面对各自问题时独立发明的解法。URL 编码解决 HTTP 传输冲突,HTML 实体解决渲染冲突,Base64 解决二进制传输问题,Unicode 统一全球字符,十六进制是底层的自然表达。

从安全角度看,每一种编码都是一把双刃剑:它解决了数据与语法的冲突问题,但也为攻击者提供了绕过过滤的手段。理解每种编码属于哪一层、由谁解码、何时展开,是做好 Web 安全防御的基础。

标签:
©

版权声明:本文采用 CC BY-NC-SA 4.0 协议授权,转载请注明出处并保留原始链接。

原文链接:https://www.jerrygao.cn//blog/E7BC96E7A081E79A84E8AEBEE8AEA1E593B2E5ADA6E4BB8EE4BCA0E8BE93E588B0E5AE89E585A8

评论 0

💬

还没有评论,成为第一个留言的人吧!