MySQL条件查询进阶:从AND到参数化查询的安全实践
这次我们来看「零基础入门到 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_DATABASEsec_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判断 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 INSELECT 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列表来自子查询而子查询结果中出现了 NULLNOT IN的结果会变得不符合直觉。原因放在第 8 节排查表中解释你只要先记住遇到 NULL 必须使用IS NULL不能指望IN或NOT IN自动处理。5. 模糊查询与空值判断LIKE、BETWEEN、IS NULL5.1 范围查询 BETWEEN ANDBETWEEN ... 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 模糊查询 LIKELIKE里最常用的是%通配符表示任意长度的字符。比如要查“来源 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
上一篇/下一篇内容由系统自动关联
返回资讯列表 →