这次我们来看「零基础入门到 SEC 挖洞实战」系列里的第 26 课,内容是 MySQL 不同条件查询的第三部分。标题看着偏课程向,实际解决的是一个很现实的问题:当你要从数据库里挑出某些数据时,WHERE 条件到底该怎么写,写错一个符号结果就差很多。很多刚开始学网络安全的同学有一个误区,觉得 SQL 会SELECT * FROM xxx就够了。真到日志分析、SRC 漏洞挖掘和编写报告时才发现,大量时间其实花在“从表里精确抓出想要的数据”这一步:某个时间段?某种登录状态?某个 IP 段?失败次数超过多少?这些需求背后都是同样的基本功——MySQL 条件查询。
这一课不会去讲任何未授权下的漏洞利用,只讲数据库查询本身,同时把动态条件查询的安全写法讲清楚。核心知识点包括:多条件组合查询时的 AND、OR 逻辑控制;IN、BETWEEN AND、LIKE、IS NULL等条件运算符的常见坑;排序与分页如何配合条件查询;分组统计后HAVING和WHERE的过滤差异;以及为什么动态拼接 SQL 不安全、应该用什么方式替代。文章会提供一套可直接在本地 MySQL 执行的演示表和模拟数据,从建库、造数到跑通每一个查询,方便你一边看一边敲命令验证。
这篇文章适合几类读者:一是零基础入门网络安全,正在补 SQL 基础的学习者;二是准备做 SRC 漏洞挖掘、渗透测试、安全日志分析,但总觉得“会看数据表但不会筛数据”的人;三是刚转行 Web 开发或测试,想把 MySQL 条件查询温习一遍的新手。建议直接收藏,遇到查询条件拿不准时翻出来对照。
1. 这一课学什么:MySQL 条件查询进阶清单
先给一张学习地图。不同条件查询系列到了第 3 部分,不再是单纯问“某列等于某值怎么写”,而是要处理组合条件、模糊匹配、空值、分组统计、排序分页,以及动态条件查询的工程化写法。
| 知识点 | 典型写法 | 一句话说明 |
|---|---|---|
| 多条件组合 | WHERE 条件1 AND 条件2 OR 条件3 | 注意逻辑优先级,条件多时用括号提高可读性 |
| 集合条件 | WHERE 字段 IN (...)/NOT IN (...) | 简洁,但子查询结果含 NULL 时容易踩坑 |
| 闭区间查询 | WHERE 字段 BETWEEN 值1 AND 值2 | 两边都包含,是>=加<=的简化写法 |
| 模糊查询 | WHERE 字段 LIKE 'xxx%' | 通配符%和_的区别必须分清 |
| 空值判断 | WHERE 字段 IS NULL | 不能用= NULL,结果永远是未知 |
| 排序 | ORDER BY 字段 ASC/DESC | 条件查询结果配合排序才有分析价值 |
| 分页 | LIMIT 偏移量, 数量 | 深分页时性能会明显下降 |
| 分组统计 | GROUP BY 字段 HAVING 条件 | HAVING 专门过滤分组后的聚合结果 |
| 参数化查询 | 占位符?/%s | 代替字符串拼接,从根上防注入风险 |
前两部分应该已经讲过了基础比较运算和简单条件过滤。这一部分的重心是“不同条件到底怎么组合”。这里先默认你已经会登录 MySQL,会建一个临时表,会最简单的SELECT ... FROM ... WHERE 字段 = 值。如果还不够熟练,也不用慌,第 3 节会给出完整的建表脚本和造数语句,直接复制到命令行执行即可。
从网络安全学习路线来看,把 MySQL 查询放在入门阶段是有道理的。后续学 Web 漏洞时,要理解参数如何进入 SQL;做日志分析时,要能从一堆访问记录里筛出可疑来源;写 SRC 漏洞报告时,要能统计影响范围。这些场景都要求你对条件查询有肌肉记忆,不需要现场翻文档。
2. 为什么做安全测试要扎实掌握条件查询
先看一个常见场景:你拿到一份导出的登录日志表,里面记录了账号、来源 IP、登录时间、登录状态。如果怀疑某个来源 IP 在过去几个小时内持续尝试登录,能不能用一条 SQL 快速把所有失败记录捞出来?
再比如写 SRC 漏洞报告时,经常要写“该接口在测试窗口内累计触发异常请求 N 次,影响账号 M 个”。这个 N 和 M 不是拍脑袋拍的,而是用条件查询和聚合统计算出来的。如果只会一条基础SELECT,光整理数据就要浪费大量时间,而且报告中的数据还没法复核。
还有一个更容易被忽略的点:想理解 SQL 注入的原理,前提是知道一条正常 SQL 的条件部分是如何拼接出来的。很多初学者看到别人的注入演示,第一反应是背 Payload,这是错误的学习路径。正确顺序是先搞明白业务代码里 SQL 怎么构建,用户输入进入哪一个位置,为什么一条未经过滤的拼接语句会改变整个查询语义。当你懂了参数化查询为何能解决问题,才算真正迈过了 Web 安全的第一道门槛。
要特别强调一件事:这篇文章所有操作都建立在“你自己创建的模拟表”基础上。真实系统、真实数据库、他人服务器,都必须在获得明确授权后才可以排查,SRC 平台也只允许在官方声明范围内测试。超出授权范围的测试,无论技术高低,都属于违规行为。本文涉及 SQL 注入的讨论只停留在“理解风险、掌握防御”的层面,不提供任何绕过和利用手段。
3. 环境准备:装好 MySQL 并准备演示数据
3.1 三种可用的 MySQL 环境
想跟着跑通本文的 SQL,先要有一个能执行 MySQL 命令的环境。推荐三种方式:
第一种,本地安装 MySQL。如果你是 Windows,可以去官方下载安装包,安装时记住 root 密码;macOS 可以用 Homebrew 安装;Linux 根据发行版包管理器安装。装好以后,在终端登录:
mysql -u root -p输入密码后能进入mysql>提示符即可。
第二种,用 Docker 启动一个临时 MySQL,最适合不想污染本机环境的读者。下面是常见模板,实际使用需要替换密码和端口:
docker run -d --name mysql-sec-lab \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -e MYSQL_DATABASE=sec_lab \ mysql:8.0启动后进入容器连接:
docker exec -it mysql-sec-lab mysql -u root -p第三种,使用你已经有的测试库。只要有权限建表、查询,就不需要额外安装。
3.2 建库建表
为了贴近网络安全学习场景,我用一张“登录日志表”作为演示对象。这张表模拟的是一个 Web 系统的登录日志,字段包括账号、来源 IP、登录时间、登录状态、设备类型等。
CREATE DATABASE IF NOT EXISTS sec_lab DEFAULT CHARACTER SET utf8mb4; USE sec_lab; DROP TABLE IF EXISTS login_log; CREATE TABLE login_log ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键', user_name VARCHAR(50) NOT NULL COMMENT '登录账号', ip VARCHAR(64) NOT NULL COMMENT '来源IP', login_time DATETIME NOT NULL COMMENT '登录时间', status VARCHAR(20) NOT NULL COMMENT '登录状态: success/fail/lock', device_type VARCHAR(30) DEFAULT NULL COMMENT '设备类型', source_ip VARCHAR(64) DEFAULT NULL COMMENT '备用来源IP,可空,用于演示NULL判断' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模拟登录日志表';3.3 插入模拟数据
下面插入 20 条模拟数据。这里的 IP、账号全部是虚构内容,单位是测试环境,不代表任何真实业务。我特意设置了部分source_ip为 NULL,方便后面演示空值陷阱。
INSERT INTO login_log (user_name, ip, login_time, status, device_type, source_ip) VALUES ('alice', '192.168.1.101', '2025-06-01 08:01:22', 'success', 'Windows', '192.168.1.101'), ('bob', '192.168.1.102', '2025-06-01 08:05:10', 'fail', 'Linux', '192.168.1.102'), ('carol', '192.168.1.103', '2025-06-01 08:07:45', 'success', 'Windows', '192.168.1.103'), ('dave', '192.168.1.104', '2025-06-01 09:00:03', 'fail', 'Android', '192.168.1.104'), ('alice', '192.168.1.101', '2025-06-01 09:10:25', 'success', 'Android', '192.168.1.101'), ('service_backup', '10.0.0.8', '2025-06-01 10:00:00', 'success', 'Linux', NULL), ('bob', '192.168.1.102', '2025-06-01 10:20:11', 'fail', 'Linux', '192.168.1.102'), ('carol', '192.168.1.103', '2025-06-01 11:05:50', 'fail', 'Windows', '192.168.1.103'), ('bob', '192.168.1.102', '2025-06-01 11:34:09', 'fail', 'Linux', '192.168.1.102'), ('alice', '192.168.1.101', '2025-06-02 08:02:01', 'success', 'Windows', NULL), ('dave', '192.168.1.104', '2025-06-02 08:30:00', 'lock', 'Android', '192.168.1.104'), ('carol', '192.168.1.103', '2025-06-02 09:12:33', 'success', 'Windows', '192.168.1.103'), ('bob', '192.168.1.102', '2025-06-02 09:45:00', 'fail', 'Linux', '192.168.1.102'), ('service_backup', '10.0.0.8', '2025-06-02 10:00:00', 'success', 'Linux', NULL), ('carol', '192.168.1.103', '2025-06-02 10:22:00', 'fail', 'Windows', '192.168.1.103'), ('carol', '192.168.1.103', '2025-06-02 11:30:00', 'fail', 'Windows', '192.168.1.103'), ('dave', '192.168.1.104', '2025-06-03 08:45:00', 'success', 'Android', '192.168.1.104'), ('alice', '192.168.1.101', '2025-06-03 09:05:00', 'lock', 'Windows', '192.168.1.101'), ('service_backup', '10.0.0.8', '2025-06-03 10:00:00', 'success', 'Linux', NULL), ('bob', '192.168.1.102', '2025-06-03 10:20:00', 'success', 'Linux', '192.168.1.102');插入完成后,先看一眼全表:
SELECT id, user_name, ip, login_time, status, device_type, source_ip FROM login_log;看到 20 行数据后,说明环境已经就绪。后面所有查询示例都可以直接基于这张表跑。
4. 组合条件查询:AND、OR 与括号的重要性
4.1 使用 AND 缩小查询范围
AND 表示“同时满足”,是条件查询里最常用的组合方式。例如要查“2025-06-01 当天登录成功”的全部记录,就需要同时限定日期和状态:
SELECT user_name, ip, login_time, status FROM login_log WHERE login_time >= '2025-06-01 00:00:00' AND login_time < '2025-06-02 00:00:00' AND status = 'success';这里我特意没写date(login_time) = '2025-06-01',因为日期函数包住字段后,在数据量大时索引会失效。当前只是教学数据,看不出性能差别,但从一开始养成习惯更好。
4.2 AND 与 OR 的优先级陷阱
OR 表示“满足其中一个即可”。把 AND 和 OR 混在一起写时,最容易翻车的是优先级问题:MySQL 里 AND 的优先级高于 OR。
来看一个例子。我想查询“账号是 alice,或者是 fail 状态且使用 Windows 设备”的记录,写成下面这样其实是错的:
SELECT user_name, ip, status, device_type FROM login_log WHERE user_name = 'alice' OR status = 'fail' AND device_type = 'Windows';由于 AND 先执行,这段 SQL 的真实逻辑是:
WHERE user_name = 'alice' OR (status = 'fail' AND device_type = 'Windows');这会把所有 Alice 的记录都查出来,同时还会把非 Alice 但状态是 fail 且设备是 Windows 的记录查出来。如果想查询的是“(账号是 alice 或者状态是 fail)并且设备是 Windows”,必须加括号:
SELECT user_name, ip, status, device_type FROM login_log WHERE (user_name = 'alice' OR status = 'fail') AND device_type = 'Windows';建议是:只要 AND 和 OR 混用,就把每一组逻辑用括号框起来。括号不会带来性能损失,但能避免线上数据查错、报告数字算错。
4.3 集合条件:IN 与 NOT IN
当判断条件不是“等于某一个值”,而是“等于一组值中的任意一个”时,用IN会比一串 OR 简洁得多。比如要查来源 IP 属于内网常见三台机器的记录:
SELECT user_name, ip, login_time, status FROM login_log WHERE ip IN ('192.168.1.101', '192.168.1.102', '192.168.1.103');想排除这些 IP,可以写成NOT IN:
SELECT user_name, ip, login_time, status FROM login_log WHERE ip NOT IN ('192.168.1.101', '192.168.1.102', '192.168.1.103');这里要提前说一个坑:如果IN列表来自子查询,而子查询结果中出现了 NULL,NOT IN的结果会变得不符合直觉。原因放在第 8 节排查表中解释,你只要先记住:遇到 NULL 必须使用IS NULL,不能指望IN或NOT IN自动处理。
5. 模糊查询与空值判断:LIKE、BETWEEN、IS NULL
5.1 范围查询 BETWEEN AND
BETWEEN ... AND ...适合表达“大于等于左边界且小于等于右边界”。比如查询 2025-06-01 到 2025-06-02 之间产生的登录记录:
SELECT user_name, ip, login_time, status FROM login_log WHERE login_time BETWEEN '2025-06-01 00:00:00' AND '2025-06-02 23:59:59';要注意它包含左右边界。如果业务要表达“从 6 月 1 日零点到 6 月 2 日零点之间”,写成BETWEEN '2025-06-01 00:00:00' AND '2025-06-02 00:00:00'会多包含 6 月 2 日 0 点整这一秒。精确到秒的统计尤其要当心。
5.2 模糊查询 LIKE
LIKE里最常用的是%通配符,表示任意长度的字符。比如要查“来源 IP 属于 192.168.1 网段”的记录:
SELECT user_name, ip, login_time, status FROM login_log WHERE ip LIKE '192.168.1.%';这里%放在末尾,能匹配192.168.1.101,也能匹配192.168.1.102。如果把%放在开头,写成LIKE '%.101',同样能查询到 IP 以.101结尾的记录。
另一个通配符是下划线_,它只匹配一个字符。比如LIKE 'bob_'会匹配 `bob