SQL注入中利用limit子句后的注入点进行绕过限制,是一种在特定场景下极为有效的攻击手法。当开发者错误地认为在limit之后拼接参数是安全的,或者应用只对where子句等常见位置进行了过滤时,攻击者便能在分页、数据截取等功能处找到突破口。其核心原理在于,MySQL等数据库的limit子句可以接受一个或两个参数,语法为LIMIT offset, row_count或LIMIT row_count OFFSET offset。如果后端代码直接将用户输入拼接在limit关键字之后,例如$sql = "SELECT * FROM products LIMIT " . $_GET['limit'];,那么攻击者就可以通过注入union select等语句,绕过原本的数据行数限制,从而窃取数据库中的其他敏感数据。
理解Limit子句的语法与可注入点
要利用这种注入,首先必须透彻理解limit子句的语法细节。在MySQL中,LIMIT 5, 10表示从第6行开始(偏移量5),返回10行数据。关键在于,limit子句通常位于SQL语句的末尾。许多开发者会认为,在语句末尾进行参数化查询或过滤不那么紧迫,或者简单地用intval()函数将输入转为整数就万事大吉。然而,如果转换不彻底或存在逻辑漏洞,攻击者可以输入1 union select 1,2,database()这样的 payload。原始语句可能变为SELECT * FROM news LIMIT 1 union select 1,2,database()。但这通常会引发语法错误,因为union查询需要前后列数一致,且limit的位置不对。真正的突破口在于,limit的参数可以是一个表达式,甚至是一个子查询。例如,LIMIT 1 OFFSET (SELECT 1)是合法的。这就为注入创造了条件。
利用Procedure Analyse进行盲注与数据提取
在MySQL 5.x版本中,一个经典的绕过技巧是利用PROCEDURE ANALYSE()子句。这个子句通常用于分析列的数据分布,但它可以跟在一个select语句的末尾,并接受参数。攻击者可以构造如下的注入payload:1 PROCEDURE ANALYSE((SELECT 1 FROM (SELECT 2) tmp), 1)。其原理是将原本的limit参数“1”扩展为一个完整的子查询。通过这种方式,可以执行任意的select查询,并将结果以错误信息或分析结果的形式返回。例如,通过提取数据库版本:1 PROCEDURE ANALYSE((SELECT @@version FROM information_schema.tables LIMIT 1), 1)。虽然这个方法在MySQL 5.7及以上版本中受到限制(默认禁止在子查询中使用limit),但在旧版本或特定配置的数据库中仍然有效,是安全测试中必须检查的向量之一。
通过分页Offset参数进行时间盲注
当limit子句使用LIMIT offset, row_count格式,且offset参数可控时,时间盲注就成为可能。攻击者可以构造一个基于睡眠函数的offset值,通过页面响应时间的延迟来判断注入是否成功。例如,一个分页查询可能是:SELECT id, title FROM articles LIMIT 10 OFFSET $_GET['page']。如果开发者仅对page参数做了简单的数字检查,攻击者可以输入:0 UNION SELECT IF(substring(database(),1,1)='a', sleep(5), 0), 2。但这里需要注意列数匹配和limit上下文。更直接的利用方式是,因为offset可以是一个子查询,所以可以构造:10 OFFSET (SELECT IF(1=1, sleep(2), 0))。实际攻击中,需要不断调整payload,因为语法限制较为严格。这种方法不依赖于错误回显,在基于时间的盲注场景下尤为有用,可以逐位提取数据库名、表名、字段内容等信息。
结合Into Outfile进行文件写入与提权
如果数据库用户拥有FILE权限,且secure_file_priv配置允许,那么limit后的注入点甚至可以用于写入Webshell,从而获取服务器控制权。其思路是利用INTO OUTFILE或INTO DUMPFILE子句。通常,into outfile需要跟在select语句的末尾,而limit子句恰好也在末尾。攻击者可以尝试终止limit子句,然后追加into outfile。例如,假设原始查询为SELECT file FROM uploads LIMIT 1,注入点位于“1”处。攻击者可以提交:1 INTO OUTFILE '/var/www/html/shell.php' LINES TERMINATED BY '<?php eval($_POST[cmd]);?>'。最终执行的SQL变为:SELECT file FROM uploads LIMIT 1 INTO OUTFILE '/var/www/html/shell.php' LINES TERMINATED BY '<?php eval($_POST[cmd]);?>'。这将把查询结果(以及恶意代码)写入到Web目录中。关键在于理解limit和into outfile的执行顺序以及语法兼容性,这需要攻击者对MySQL语法有深入的掌握。
利用花括号注释绕过过滤与WAF
在实际渗透测试中,应用程序或Web应用防火墙(WAF)可能会过滤union、select、sleep等关键词。此时,可以利用MySQL特有的花括号注释/*! ... */来绕过过滤。这种注释内的代码在特定版本的MySQL中会被执行。例如,可以构造:1 /*!UNION*/ /*!SELECT*/ 1,2,@@version。在limit后的注入上下文中,可以尝试:1 /*!PROCEDURE ANALYSE*/((/*!SELECT*/ 1 FROM (/*!SELECT*/ 2) tmp), 1)。此外,还可以使用内联注释/*!50000 ... */来指定代码只在MySQL 5.0.0及以上版本执行,从而增加payload的兼容性和隐蔽性。通过灵活组合注释、空白符和大小写变种,攻击者常常能绕过简单的关键词过滤机制。
防御策略:参数化查询与严格白名单
防御limit子句后的SQL注入,最根本的方法是使用参数化查询(预编译语句)。但需要注意的是,limit子句的参数在大多数数据库接口中(如PDO、MySQLi)不能直接作为参数绑定,因为参数绑定通常只适用于值,而不适用于SQL关键字或表达式。因此,最佳实践是:首先,对用户输入进行严格的类型转换,确保其为非负整数。例如在PHP中使用intval($_GET['limit'])并强制范围为合理值。其次,如果业务需要复杂的offset/limit逻辑,应使用白名单机制,只允许特定的几个值(如10, 20, 50)。最后,在数据库层面,遵循最小权限原则,确保应用数据库用户没有FILE、PROCESS等敏感权限,并升级MySQL到较新版本(5.7+),以默认禁用一些危险的特性。代码审计时应特别注意所有拼接SQL的地方,尤其是那些位于语句末尾、容易被忽视的参数。
自动化检测与手工验证方法
在安全评估中,检测limit注入点可以使用自动化工具如sqlmap,配合--technique指定注入技术。例如,对于可能存在limit注入的URL:/news.php?limit=10,可以运行:sqlmap -u "/news.php?limit=10" --technique=T(时间盲注)。但自动化工具可能无法很好地处理复杂的语法环境,因此手工验证不可或缺。手工测试步骤包括:
1. 尝试输入非数字字符,观察是否报错;
2. 输入如1 and 1=1、1 and 1=2,观察页面内容变化(适用于limit影响结果集的情况);
3. 尝试使用payload如1 /*!sleep(5)*/,测量响应时间;
4. 逐步测试union select的列数,并尝试在limit后构造子查询。通过结合工具效率与手工精度,可以更全面地发现和验证此类漏洞。
总之,limit后的SQL注入是一种需要特定条件但危害性不小的漏洞。它考验着开发者对SQL语法边界的理解深度,也考验着防护措施是否覆盖了“角落”场景。对于攻击者而言,掌握这种技巧意味着多了一种绕过防御、获取数据的手段;对于防御者而言,则提醒我们安全防护必须细致入微,任何用户输入的点都可能是攻击面,即便是SQL语句的最后一个参数也不例外。在架构设计和代码编写阶段就贯彻安全原则,远比事后修补更为有效。
