记一次借助Burpsuite的Access数据库手工SQL注入 – 作者:MactavishMeng

环境介绍

目标系统是之前在网上偶然下载的一个CMS系统,在本地装了一个 Windows Server 的虚拟机,开启 IIS。因为是Access数据库,所以不需要安装相关的数据库服务,再其根目录的/data文件夹下有个data.mdb文件,即为该数据库文件。c1f22dd5988d8a7a1e4ef1d1e8bf23448f2f2178.jpg

发现注入点

在测试环境里看了一下站点的情况,发现这个CMS在读取新闻内容的时候,URL为http://172.22.10.239/info.asp?id=240 的形式。顺手在id后面加了个单引号,结果竟然报了500的错误!

2020-01-14_091107.png

进一步进行确认,考虑到这里的id应该是数据库中的行号,猜想一下相关的查询语句可能的构成应该为:

SELECT {col_name} FROM {table_name} WHERE `id` = {id}

查询语句里面的id大概率是通过拼接的方式写进去的。为了证实这个猜想,构造一个payload:

?id=240+and+1=1+

+号在URL中是等同于空格的,为了避免问题空格就用+代替。

这时候发现依然是500,难道判断错了?还是后面还有别的什么语句吗?不甘心的又尝试了一下or进行拼接:

?id=240+or+1=1+

这下可以正常显示出页面了。那基本就能确定此处存在注入点,而且大概率是and字符被替换了。

问题分析

既然发现了注入点,就可以尝试注入。正常的手段是去试过滤规则,但是因为目标是一个CMS,我们手里有后台的源码,因此直接开始对相关页面的源码进行分析,看看它到底用了什么规则去拦截我们的输入。

先打开info.asp文件,找到处理id参数的地方。在开头的第 4 行就是一个赤裸裸的 SQL 拼接,语句与我们猜测的完全一致:

2020-01-14_103116.png

那这里的rqs()又是啥?在library.asp文件中找到了这个函数的定义:2020-01-14_103455.png

str_safe函数定义:2020-01-14_103659.png

看到这里,其实一切规则就都明白了,我们输入的 {空格}and 是在过滤列表中的。那么下一步就是如何绕过并且进行注入了。

尝试注入

ACCESS 数据库与 Mysql 数据库注入的区别主要体现在 ACCESS 是没有  information_schema 这种“总表”,也就是说数据表的名称是无法通过注入的方法读取到,因此需要去猜解。

绕过过滤

在猜解之前需要先绕过过滤。因为有了看到源码的便利,知道了过滤的原理,CMS替换的字符都是带空格的,如{空格}and, select{空格}这种,那需要找到绕过空格的方法。

首先想到的是用大小写绕过,比如AnD这样,但是发现Replace函数的参数最后是1, -1, 1。这三个参数分别表示的替换字符的起始、终止和检查类型。检查类型默认为0,设置为1的时候是大小写不敏感的,因此大小写绕过便没有作用了。

那么换个思路,尝试用一些特殊字符来代替空格。存在一些除了%20+ 以外的字符也可以被 ACCESS 认作空格的作用。这时需要使用 Burpsuite 的 Intruder 工具来进行尝试。

在 Position 界面把要替换的字符,当然这里根据源码里面的规则,将空格字符替换掉,使用 URL 转义的方式填充 %xx ,将除了 ‘%’的部分进行标记。Intruder工具中的 Attack type选择 Battering ram,这样所有的标记处都会同步的进行变化2020-01-14_143350.png

在 Payload 的部分,类型type选择 Brute forcer,即暴力破解,字符集填上 HEX 的字符集,即 0~9,A~F,最大和最小位数都是 2 位,这样就能遍历 0x00 ~ 0xFF 的全部字符了。2020-01-14_143714.png

瞬间跑完结果,将结果按照 Response 的状态 Status 排序,是 200 的那几条就是返回了正确页面的。此时就能 初步 得到可以成功跑出结果的字符集。2020-01-14_144213.png

框中的是初步筛选的结果。这里能够执行成功,不代表成功代替了空格,有可能是引起了其他转义(比如截断等),所以还要将筛选出来的字符用 id=240%{xx}and%{xx}1=2再跑一次确认一下。因为数量不多,所以用 Simple list 模式将这些 Payload 再做一次测试:2020-01-14_144650.png

这次的结果中,状态为 500 的返回值说明这个字符是成功的解析成空格的(因为后面注入的是and 1=2,预期返回的是false)。从 200 的结果也可以看出,%00%16 这两个字符可以用作截断,即 %16后面的字符不会被当做 SQL 语句进行解析。2020-01-14_144904.png

所以目前可以确定,当前可用的替代空格的字符为 %09, %0A, %0D

猜解表名

因为知道了过滤规则,并找到了可替代的字符,就可以尝试猜解表名了。

ACCESS 没有记录数据表名称的系统表,所以不能像 Mysql 那样直接读取,唯一的方法就是暴力猜解表名。

首先构建Payload。在上一步的基础上,将 AND 关键字后面的条件换成尝试表名是否存在的语句即可。这里使用 exists 关键字来完成这个任务:

EXISTS(SELECT NULL FROM `{表名}`)

如果表名不存在,那么这个查询会报错,返回 500;若存在表名,则EXISTS函数会返回 True,自然页面也会返回 200。

关于表名的猜测,网上有一些类似于 Top 100 的列表可以下载,或者盲猜。一般系统都会有包含 admin 这个名称的表,无非是前面加上前缀,比如国内的CMS会喜欢加上CMS的缩写,或者如 “t_admin” 这种一个字母的形式。这样可以根据目标的特点去构建猜测的列表,用 Intruder 工具将表名列表导入 Simple list中进行暴力破解。

这里这个CMS嘛,因为本地有源码,就开了个挂,直接看到了它的表名,比如管理员表叫做CMS_ADMIN

进行注入

现在万事具备,只欠注入了。这里尝试使用 UNION 关键字来把想要的数据给拿出来。使用 UNION 关键字的时候需要知道原查询的字段数,否则会报列数不匹配的问题,自然返回的就是500。预期构建出的完整 SQL 查询如下:

SELECT * FROM `cms_info` WHERE id = 1 UNION SELECT 1,1,1, ... ,1 FROM cms_info

现在需要知道 UNION 中的 1 的个数需要多少个。还是用 Intruder 来尝试,先构建出一条初始的payload: id=1%0Dunion%0Dselect%0D1%0Dfrom%0Dcms_info,使用%0D作为空格的替代。插入标记之后的样子如下:2020-01-14_162407.png

此处 Payload type 选择 Character blocks,Min length设置为2,就是 “,1″的长度。我们想尝试 1~50列,Max length 就设置为 50 * 2 = 100 即可:2020-01-14_162627.png

将结果按照 Status 排序,200的结果序号是 34,那么说明当前这个新闻页面查询的表的结果一共 35 列(因为初始的时候已经有了 UNION SELECT 1 FROM...,因此这里列数要根据序号 +1)2020-01-14_162753.png

但是虽然这里是 200,但是页面并没有返回正常的数据,使用 Repeter 发送这个数据,发现是要跳转到 login.asp 页面:2020-01-14_163908.png

那么说明返回的字段中是有控制页面显示的功能。各个字段分别的功能需要通过修改字段的值来判断,此处大体就是 0, 1, NULL 三种不同的值排列组合,具体分析方法不再赘述。最终得到注入的结果,比如读到管理员账号密码:2020-01-14_165030.png

总结分析

这个注入点因为做了替换,将一些主要的 SQL 关键字进行了转义,导致使用 SQLMap 无法直接跑出数据,因此使用手工注入的方式查看。其实也可以通过在 SQLMap 中引入脚本的方式自动替换来达到目的,将空格全部替换成%9D即可自动化跑出内容(没有试过,但是原理上可行)。

对于 ACCESS 数据库,对于表名、列名都需要盲猜,其实注入难度会很大,但是对于 CMS 系统来说,表名和字段名是公开的,一般用户在使用的时候又不会特别去进行修改,一旦出现注入点会有很大的隐患。

对于防止 SQL 注入,像这种替换显然是有问题的,关键字 + 空格的识别还是有很多方法可以进行绕过,如果担心匹配太严格影响业务,也可以从允许的变量类型、长度等方面进行局部限制,来消减 SQLI 的隐患。

来源:freebuf.com 2020-01-14 20:47:37 by: MactavishMeng

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论