第101篇 AI全栈 · 不同数据库的SQL注入不同手工注入方法的汇总\_各大数据库手
原创
陈看山
陈看山
安全诸子
2026年10月14日 07:30
上海
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
做安全测试的人,多少都遇到过这种尴尬:在 MySQL 上练得滚瓜烂熟的注入手法,换到 PostgreSQL 靶场直接失灵;刚摸清 MSSQL 的报错注入套路,转头面对 DB2 又不知道从哪个系统表开始查。不同数据库的 SQL 方言、元数据存储结构、注释符和报错机制差异巨大,手工注入的思路必须跟着数据库引擎走,一套万能 payload 打天下的时代早就不存在了。 这篇文章是对各大数据库手工注入思路的一次系统性梳理,重点放在 MySQL、PostgreSQL、DB2 三种主流数据库上,也会涉及 MSSQL 和 Oracle 的差异点。内容基于公开靶场环境的解题思路汇总,部分案例来自墨者靶场的经典题目,仅用于授权测试、漏洞研究、企业自测和防御体系建设场景,请勿用于未授权目标。
为什么手工注入必须区分数据库类型 自动化扫描工具能帮你快速发现注入点,但到了手工验证和数据提取阶段,工具往往不够用
原因在于不同数据库对 SQL 语法的解析差异,直接影响注入语句的构造方式。 首先是注释符的差异。MySQL 支持 #、-- (后面必须跟空格)以及 /\ \/;PostgreSQL 和 DB2 支持 -- 注释;Oracle 只认 --。一个不小心,注释符选错,整个 payload 直接报错。 其次是元数据存储方式。MySQL 5.0 以上版本把表名、列名都存在 information\_schema 库里;PostgreSQL 也提供 information\_schema,但更高效的表名获取方式是查 pg\_stat\_user\_tables;DB2 则把表名记录在 syscat.tables,列名记录在 sysibm.columns。不知道这些差异,连表名都爆不出来。 最后是报错机制。MySQL 有 updatexml、extractvalue 这类报错注入函数;MSSQL 有 convert(int, @@version) 的经典报错姿势;PostgreSQL 则没有直接对应的报错函数,通常要靠联合查询或布尔盲注。如果拿 MySQL 的报错注入语句去打 PostgreSQL,结果只能是空手而归。
MySQL 手工注入的基本套路 MySQL 注入的成熟度最高,网上教程也最多,这里只做一个快速串联,方便和后续的 PostgreSQL、DB2 做对比
判断注入点:在参数后加单引号、and 1=1、and 1=2 看页面差异。对于数字型参数,也可以直接尝试 id=-1 union select 1,2,3--+ 来判断字段数。 确定字段数:用 order by n 逐步增加 n 的值,直到报错,就能确认 SELECT 语句的字段总数。假设 order by 4 报错,说明只有 3 个字段。 获取库名:MySQL 5.0 以上可以直接用 database() 函数。 获取表名: sql union select 1,group\_concat(table\_name),3 from information\_schema.tables where table\_schema=database() 获取列名: sql union select 1,group\_concat(column\_name),3 from information\_schema.columns where table\_name='表名' 提取数据: sql union select 1,username,password,3 from users MySQL 的优势在于 information\_schema 结构统一、函数丰富,加上 group\_concat 可以一次性把多行数据拼成一行输出,效率很高。但要注意,MySQL 的 -- 注释符后面必须跟一个空格或控制字符,URL 编码中通常写成 --+ 或 --%20,写错了注释就不生效。
PostgreSQL 手工注入的完整流程 PostgreSQL 和 MySQL 都提供 information\_schema,但实际注入时的取数路径差异明显
PostgreSQL 中更推荐直接查询 pg\_stat\_user\_tables 来获取用户表名,这个表记录了当前数据库中所有用户表的统计信息,其中 relname 字段就是表名。
第一步
确定版本和注入点 先用联合查询确认字段数,然后尝试获取当前数据库名。PostgreSQL 获取当前库名用 current\_database() 函数。 sql id=-1 union select '1',current\_database(),'3','4'-- - 需要注意的是,PostgreSQL 的注释符是 --,后面必须跟一个空格。URL 编码中写成 --%20 或 --+ 都可能出问题,最稳妥的是 -- -(两个短横线加空格再加一个短横线)。
第二步
获取表名 sql id=-1 union select '1',relname,'3','4' from pg\_stat\_user\_tables limit 1 offset 1 这里有两个关键点。第一,pg\_stat\_user\_tables 会返回多行记录,必须用 limit 1 offset n 逐行遍历。第二,offset 后面的数字从 0 开始递增,直到页面报错说明已经遍历完所有表。如果目标表不止一张,可以配合 where 条件过滤,但靶场环境中通常直接遍历即可。
第三步
获取列名 拿到表名(假设为 reg\_users)之后,从 information\_schema.columns 中查询该表的所有列名: sql id=-1 union select '1',column\_name,'3','4' from information\_schema.columns where table\_name='reg\_users' limit 1 offset 1 offset 从 0 开始逐步递增,当数字加到 4 时报错,说明 reg\_users 表共有 4 个字段。逐个遍历就能拿到 name、password 等关键列名。
第四步
提取数据 sql id=-1 union select '1',name,'3','4' from reg\_users limit 1 offset 0 id=-1 union select '1',password,'3','4' from reg\_users limit 1 offset 0 也可以把多个字段拼接到一个输出位: sql id=-1 union select null,'null','用户名:'||name||',密码:'||password||',状态:'||status||',id:'||id,null from reg\_users PostgreSQL 支持 || 字符串拼接符,这一点和 MySQL 的 concat() 函数不同,从 MySQL 转过来的同学容易在这里踩坑。
limit 与 offset 的区分 PostgreSQL 支持 limit y offset x 的写法,意思是跳过 x 条数据后读取 y 条
很多人会把 limit 0,1 这种 MySQL 写法直接搬到 PostgreSQL,结果语法报错。PostgreSQL 不支持 limit x, y 的简写形式,必须写成 limit y offset x。这个细节在手工注入时很容易忽略,一旦报错就要马上反应过来是语法差异导致的。
DB2 手工注入的独特路径 DB2 在企业级数据库中使用广泛,但安全测试中的曝光率远低于 MySQL 和 PostgreSQL
DB2 的注入思路和 PostgreSQL 类似,核心差异在元数据表的名称和结构上。
判断注入点和字段数 DB2 中判断字段数和 MySQL 类似,用 order by
但联合查询有一个硬性要求:union 后面必须跟一个真实存在的表名。DB2 不像 MySQL 那样允许 union select 1,2,3 不带 from 子句,所以要用系统自带的表来凑数。 sql id=-1 union select 1,2,3,4 from sysibm.systables sysibm.systables 是 DB2 系统表,存储了数据库中所有表、视图和别名的元数据。只要这个表存在,联合查询就能正常执行。
获取当前模式(Schema) DB2 中获取当前模式用 current schema
sql id=-1 union select 1,current schema,3,4 from sysibm.sysdummy1 sysibm.sysdummy1 是 DB2 的伪表,类似于 Oracle 的 dual,专门用来执行不涉及实际数据表的查询。这一步拿到当前模式名(通常是数据库用户名),后续查询表名和列名时要用它来过滤。
获取表名 DB2 的表名元数据存储在 syscat.tables 中,用 tabname 字段获取
sql id=-1 union select 1,tabname,3,4 from syscat.tables where tabschema=current schema limit 0,1 这里有一个容易混淆的地方:DB2 的 limit 0,1 是合法的,和 MySQL 写法一致,表示跳过 0 条读取 1 条。这一点和 PostgreSQL 不同,PostgreSQL 必须写 limit 1 offset 0。不同数据库对 limit 语法的支持程度不同,手工注入时一定要先确认目标数据库类型,再决定用哪种分页写法。
获取列名 DB2 的列名元数据存储在 sysibm.columns 中
sql id=-1 union select 1,column\_name,3,4 from sysibm.columns where table\_schema=current schema and table\_name='GAME\_CHARACTER' limit 0,4 注意 table\_schema 和 table\_name 两个过滤条件都要写对,否则查出来的列名可能是其他表…
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论