LITCTF2025

[TOC]

web/LexMACS’ Secret

F12一下,发现以下文本:

1
<div><div><div><div>L<div><div><div><div>I<div><div><div><div>T<div><div><div><div>C<div><div><div><div>T<div><div><div><div>F<div><div><div><div>{<div><div><div><div>S<div><div><div><div>e<div><div><div><div>C<div><div><div><div>R<div><div><div><div>3<div><div><div><div>t<div><div><div><div>_<div><div><div><div>h<div><div><div><div>A<div><div><div><div>s<div><div><div><div>-<div><div><div><div>B<div><div><div><div>3<div><div><div><div>e<div><div><div><div>n<div><div><div><div>-<div><div><div><div>l<div><div><div><div>E<div><div><div><div>4<div><div><div><div>K<div><div><div><div>d<div><div><div><div>}</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></body>

去除div标签即可得到flag:LITCTF{SeCR3t_hAs-B3en-lE4Kd}

web/file viewer

app.py:

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
from flask import Flask, request, send_file, abort, render_template_string
import os

app = Flask(__name__)

HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>My File View</title>
</head>
<body>
<h1>Welcome to my file viewer!</h1>
<p>Click on the button below to view a random sample file.</p>
<button onclick="window.location.href='/view-file?file=' + getRandomFile()">View Random File</button>
<script>
function getRandomFile() {
const files = ["sample1.txt", "sample2.txt", "sample3.txt"];
return files[Math.floor(Math.random() * files.length)];
}
</script>
</body>
</html>
"""

@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)

@app.route('/view-file')
def view_file():
filename = request.args.get('file', '')
filepath = os.path.join('files', filename)

if not os.path.exists(filepath):
return abort(404)

return send_file(filepath)

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

一眼,有目录穿越。
但尝试读取有用的文件,都没有什么信息。
Linux文件知识+1:/proc/self/cwd符号链接指向当前进程的工作目录

20250825184056

通过这个目录以及对flag文件名猜测最终得到flag:LITCTF{o0ps_f0rg0t_t0_s3cur3_my_dir3ct0ry}

web/file viewer 2

app.py:

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
from flask import Flask, request, send_file, abort, render_template_string
import os

app = Flask(__name__)

HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>My File View</title>
</head>
<body>
<h1>Welcome to my file viewer!</h1>
<p>Click on the button below to view a random sample file. Also, you can't look at my secrets anymore -- I secured that directory this time around ;)</p>
<button onclick="window.location.href='/view-file?file=' + getRandomFile()">View Random File</button>
<script>
function getRandomFile() {
const files = ["sample1.txt", "sample2.txt", "sample3.txt"];
return files[Math.floor(Math.random() * files.length)];
}
</script>
<p>Oh also -- here are some pictures from my most recent vacation. Take a look at these, since you can't look at my secrets now anyway :)</p>
<img src="/view-file?file=images/sailboat.jpg" width="200" height="auto">
<img src="/view-file?file=images/seagulls.jpg" width="200" height="auto">
<img src="/view-file?file=images/sunset.jpg" width="200" height="auto">
<img src="/view-file?file=images/beach.jpg" width="200" height="auto">
</body>
</html>
"""

@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)

@app.route('/view-file')
def view_file():
filename = request.args.get('file', '')

if filename[0:2] == '..':
return abort(400, "Stop trying to look at my secrets >:(")

filepath = os.path.join('files', filename)

print(filepath)

if not os.path.exists(filepath) or not os.path.isfile(filepath):
return abort(404)

return send_file(filepath)

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

题目描述说他把什么目录加密了,但同样的打法出了,没懂它加密了什么。可能是非预期了。

20250825185228

web/tabled

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from flask import Flask, request, redirect, url_for, render_template_string, session
import sqlite3
import random
import string
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)
flag = "LITCTF{[redacted]}"

def init_db():
conn = sqlite3.connect("users.db")
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)''')
characters = string.ascii_letters + string.digits
randomstring = 'a' + ''.join(random.choice(characters) for _ in range(100))
c.execute("CREATE TABLE IF NOT EXISTS " + randomstring + " (flag TEXT)")
c.execute("INSERT INTO " + randomstring + " (flag) VALUES ('" + flag + "')")
conn.commit()
conn.close()

init_db()

login_page = """
<!doctype html>
<title>Login</title>
<h2>Login</h2>
<form method="post">
Username: <input type="text" name="username"><br><br>
Password: <input type="password" name="password"><br><br>
<input type="submit" value="Login">
</form>
<p>Don't have an account? <a href="{{ url_for('register') }}">Register here</a></p>
{% if error %}
<p style="color:red">{{ error }}</p>
{% endif %}
"""

register_page = """
<!doctype html>
<title>Register</title>
<h2>Register</h2>
<form method="post">
Username: <input type="text" name="username"><br><br>
Password: <input type="password" name="password"><br><br>
<input type="submit" value="Register">
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login here</a></p>
{% if error %}
<p style="color:red">{{ error }}</p>
{% endif %}
"""

home_page = """
<!doctype html>
<title>Home</title>
<h2>Welcome, {{ user }}!</h2>
<a href="{{ url_for('logout') }}">Logout</a>
"""

@app.route("/", methods=["GET", "POST"])
def login():
error = None
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]

conn = sqlite3.connect("users.db")
c = conn.cursor()
c.execute("SELECT username FROM users WHERE username='" + username + "' AND password='" + password + "'")
user = c.fetchone()
conn.close()

if user:
session["username"] = user[0]
return redirect(url_for("home"))
else:
error = "Invalid username or password"
return render_template_string(login_page, error=error)

@app.route("/register", methods=["GET", "POST"])
def register():
error = None
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]

if not username or not password:
error = "Both fields are required"
else:
try:
conn = sqlite3.connect("users.db")
c = conn.cursor()
c.execute("INSERT INTO users (username, password) VALUES ('" + username + "', '" + password + "')")
conn.commit()
conn.close()
return redirect(url_for("login"))
except sqlite3.IntegrityError:
error = "Username already exists"
return render_template_string(register_page, error=error)

@app.route("/home")
def home():
if "username" in session:
return render_template_string(home_page, user=session["username"])
return redirect(url_for("login"))

@app.route("/logout")
def logout():
session.pop("username", None)
return redirect(url_for("login"))

if __name__ == "__main__":
app.run(debug=True)

一个简单的登录框,源码中有sql语句,推测是sql注入,sqlmap一把梭:

20250825193031